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实现的undo和redo(撤销回退的管理)的管理插件MutationJS, 可以作为一个单独的插件引入:(http://files.cnblogs.com/files/diligenceday/MutationJS.js):
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
/** * @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
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