博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用HTML5新特性Mutation Observer实现编辑器的撤销和撤销回退操作
阅读量:7251 次
发布时间:2019-06-29

本文共 7159 字,大约阅读时间需要 23 分钟。

   MutationObserver介绍

   MutationObserver给开发者们提供了一种能在某个范围内的DOM树发生变化时作出适当反应的能力.该API设计用来替换掉在DOM3事件规范中引入的Mutation事件.

   MDN的资料:

 

  MutationObserver是一个构造函数, 所以创建的时候要通过 new MutationObserver;

  实例化MutationObserver的时候需要一个回调函数,该回调函数会在指定的DOM节点(目标节点)发生变化时被调用,

  在调用时,观察者对象会传给该函数两个参数:

    1:第一个参数是个包含了若干个MutationRecord对象的数组;    2:第二个参数则是这个观察者对象本身.

 

  比如这样:

var observer = new MutationObserver(function(mutations) {             mutations.forEach(function(mutation) {                 console.log(mutation.type);             });         });

  observer的方法

  实例observer有三个方法: 1: observe  ;2: disconnect ; 3: takeRecords   ;

  observe方法

  observe方法:给当前观察者对象注册需要观察的目标节点,在目标节点(还可以同时观察其后代节点)发生DOM变化时收到通知;

  这个方法需要两个参数,第一个为目标节点, 第二个参数为需要监听变化的类型,是一个json对象,  实例如下:

observer.observe( document.body, {            'childList': true, //该元素的子元素新增或者删除            'subtree': true, //该元素的所有子元素新增或者删除            'attributes' : true, //监听属性变化            'characterData' : true, // 监听text或者comment变化            'attributeOldValue' : true, //属性原始值            'characterDataOldValue' : true         });

  disconnect方法

  disconnect方法会停止观察目标节点的属性和节点变化, 直到下次重新调用observe方法;

    takeRecords

  清空观察者对象的记录队列,并返回一个数组, 数组中包含Mutation事件对象;

 

  MutationObserver实现一个编辑器的redo和undo再适合不过了, 因为每次指定节点内部发生的任何改变都会被记录下来, 如果使用传统的keydown或者keyup实现会有一些弊端,比如:

1:失去滚动, 导致滚动位置不准确;2:失去焦点;....

 

  用了几小时的时间,写了一个通过MutationObserver实现的undoredo(撤销回退的管理)的管理插件MutationJS,  可以作为一个单独的插件引入:(http://files.cnblogs.com/files/diligenceday/MutationJS.js):

/** * @desc MutationJs, 使用了DOM3的新事件 MutationObserve; 通过监听指定节点元素, 监听内部dom属性或者dom节点的更改, 并执行相应的回调; * */window.nono = window.nono || {};/** * @desc * */nono.MutationJs = function( dom ) {    //统一兼容问题    var MutationObserver = this.MutationObserver = window.MutationObserver ||        window.WebKitMutationObserver ||        window.MozMutationObserver;    //判断浏览器是或否支持MutationObserver;    this.mutationObserverSupport = !!MutationObserver;    //默认监听子元素, 子元素的属性, 属性值的改变;    this.options = {        'childList': true,        'subtree': true,        'attributes' : true,        'characterData' : true,        'attributeOldValue' : true,        'characterDataOldValue' : true    };    //这个保存了MutationObserve的实例;    this.muta = {};    //list这个变量保存了用户的操作;    this.list = [];    //当前回退的索引    this.index = 0;    //如果没有dom的话,就默认监听body;    this.dom = dom|| document.documentElement.body || document.getElementsByTagName("body")[0];    //马上开始监听;    this.observe( );};$.extend(nono.MutationJs.prototype, {    //节点发生改变的回调, 要把redo和undo都保存到list中;    "callback" : function ( records , instance ) {        //要把索引后面的给清空;        this.list.splice( this.index+1 );        var _this = this;        records.map(function(record) {            var target = record.target;            console.log(record);            //删除元素或者是添加元素;            if( record.type === "childList" ) {                //如果是删除元素;                if(record.removedNodes.length !== 0) {                    //获取元素的相对索引;                    var indexs = _this.getIndexs(target.children , record.removedNodes );                    _this.list.push({                        "undo" : function() {                            _this.disconnect();                            _this.addChildren(target,  record.removedNodes ,indexs );                            _this.reObserve();                        },                        "redo" : function() {                            _this.disconnect();                            _this.removeChildren(target,  record.removedNodes );                            _this.reObserve();                        }                    });                    //如果是添加元素;                };                if(record.addedNodes.length !== 0) {                    //获取元素的相对索引;                    var indexs = _this.getIndexs(target.children , record.addedNodes );                    _this.list.push({                        "undo" : function() {                            _this.disconnect();                            _this.removeChildren(target,  record.addedNodes );                            _this.reObserve();                        },                        "redo" : function () {                            _this.disconnect();                            _this.addChildren(target,  record.addedNodes ,indexs);                            _this.reObserve();                        }                    });                };                //@desc characterData是什么鬼;                //ref :  http://baike.baidu.com/link?url=Z3Xr2y7zIF50bjXDFpSlQ0PiaUPVZhQJO7SaMCJXWHxD6loRcf_TVx1vsG74WUSZ_0-7wq4_oq0Ci-8ghUAG8a            }else if( record.type === "characterData" ) {                var oldValue = record.oldValue;                var newValue = record.target.textContent //|| record.target.innerText, 不准备处理IE789的兼容,所以不用innerText了;                _this.list.push({                    "undo" : function() {                        _this.disconnect();                        target.textContent = oldValue;                        _this.reObserve();                    },                    "redo" : function () {                        _this.disconnect();                        target.textContent = newValue;                        _this.reObserve();                    }                });                //如果是属性变化的话style, dataset, attribute都是属于attributes发生改变, 可以统一处理;            }else if( record.type === "attributes" ) {                var oldValue = record.oldValue;                var newValue = record.target.getAttribute( record.attributeName );                var attributeName = record.attributeName;                _this.list.push({                    "undo" : function() {                        _this.disconnect();                        target.setAttribute(attributeName, oldValue);                        _this.reObserve();                    },                    "redo" : function () {                        _this.disconnect();                        target.setAttribute(attributeName, newValue);                        _this.reObserve();                    }                });            };        });        //重新设置索引;        this.index = this.list.length-1;    },    "removeChildren" : function ( target, nodes ) {        for(var i= 0, len= nodes.length; i
View Code

   

  MutationJS如何使用

  那么这个MutationJS如何使用呢?

//这个是实例化一个MutationJS对象, 如果不传参数默认监听body元素的变动;mu = new nono.MutationJs();//可以传一个指定元素,比如这样;mu = new nono.MutationJS( document.getElementById("div0") );//那么所有该元素下的元素变动都会被插件记录下来;

 

  Mutation的实例mu有几个方法:

  1:mu.undo()  操作回退;

  2:mu.redo()   撤销回退;

  3:mu.canUndo() 是否可以操作回退, 返回值为true或者false;

  4:mu.canRedo() 是否可以撤销回退, 返回值为true或者false;

  5:mu.reset() 清空所有的undo列表, 释放空间;

  6:mu.without() 传一个为函数的参数, 所有在该函数内部的dom操作, mu不做记录;

  

  MutationJS实现了一个简易的undoManager提供参考,在火狐和chrome,谷歌浏览器,IE11上面运行完全正常: 

    

MutationObserver是为了替换掉原来Mutation Events的一系列事件, 浏览器会监听指定Element下所有元素的新增,删除,替换等;

;
;
;

   DEMO在IE下的截图:

 MutatoinObserver的浏览器兼容性:

Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari
Basic support

18 

26

 (14) 11 15 6.0 

  

  MDN的资料:

作者:   

出处:

QQ:287101329 

 

转载地址:http://mphbm.baihongyu.com/

你可能感兴趣的文章
LeeCode-Sort Colors
查看>>
Snort2.9.2.3 Installation on CentOS 6.2
查看>>
我的友情链接
查看>>
给软件工程师的自学建议
查看>>
Linux下SVN的备份方式
查看>>
hadoop 3.0.0 alpha1 分布式搭建
查看>>
刘宇凡:从吃饭中的道理领悟SEO
查看>>
1.1办公软件概述
查看>>
python中http的一些编码转换
查看>>
5.继续看W3C的 bootstrap
查看>>
PHP中include和require
查看>>
第一次标题
查看>>
H3C ospf router id 重复真的不能建立邻居吗?
查看>>
【symfoware OPEN】数据库基本操作
查看>>
iphone:截屏
查看>>
Mac 下 VirtualBox ubuntu 共享空间
查看>>
CENTOS6.3利用Keepalived构建双主MySQL+双机热备
查看>>
常用算法收集
查看>>
listview的简单使用(Baseadapter)
查看>>
Oracle导入程序Imp的使用详解
查看>>