html撤销操作,使用HTML5新特性Mutation Observer实现编辑器的撤销和撤销回退操作

MutationObserver介绍

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

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

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

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

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

比如这样:

var observer = newMutationObserver(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实现的undo和redo(撤销回退的管理)的管理插件MutationJS,可以作为一个单独的插件引入:(http://files.cnblogs.com/files/diligenceday/MutationJS.js):

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/**

* @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

target.removeChild( nodes[i] );

};

},"addChildren" : function( target, nodes ,indexs) {for(var i= 0, len= nodes.length; i

target.insertBefore( nodes[i] , target.children[ indexs[i] ]) ;

}else{

target.appendChild( nodes[i] );

};

};

},//快捷方法,用来判断child在父元素的哪个节点上;

"indexOf" : function( target, obj ) {returnArray.prototype.indexOf.call(target, obj)

},"getIndexs" : function(target, objs) {var result =[];for(var i=0; i

result.push(this.indexOf(target, objs[i]) );

};returnresult;

},/**

* @desc 指定监听的对象

**/

"observe" : function( ) {if( this.dom.nodeType !== 1) return alert("参数不对,第一个参数应该为一个dom节点");this.muta = new this.MutationObserver( this.callback.bind(this) );//马上开始监听;

this.muta.observe( this.dom, this.options );

},/**

* @desc 重新开始监听;

**/

"reObserve" : function() {this.muta.observe( this.dom, this.options );

},/**

*@desc 不记录dom操作, 所有在这个函数内部的操作不会记录到undo和redo的列表中;

**/

"without" : function( fn ) {this.disconnect();

fn&fn();this.reObserve();

},/**

* @desc 取消监听;

**/

"disconnect" : function() {return this.muta.disconnect();

},/**

* @desc 保存Mutation操作到list;

**/

"save" : function( obj ) {if(!obj.undo)return alert("传进来的第一个参数必须有undo方法才行");if(!obj.redo)return alert("传进来的第一个参数必须有redo方法才行");this.list.push(obj);

},/**

* @desc ;

**/

"reset" : function() {//清空数组;

this.list =[];this.index = 0;

},/**

* @desc 把指定index后面的操作删除;

**/

"splice" : function( index ) {this.list.splice( index );

},/**

* @desc 往回走, 取消回退

**/

"undo" : function() {if( this.canUndo() ) {this.list[this.index].undo();this.index--;

};

},/**

* @desc 往前走, 重新操作

**/

"redo" : function() {if( this.canRedo() ) {this.index++;this.list[this.index].redo();

};

},/**

* @desc 判断是否可以撤销操作

**/

"canUndo" : function() {return this.index !== -1;

},/**

* @desc 判断是否可以重新操作;

**/

"canRedo" : function() {return this.list.length-1 !== this.index;

}

});

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下所有元素的新增,删除,替换等;

;;

;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值