搜索
首页web前端H5教程基于HTML5新特性Mutation Observer实现编辑器的撤销和回退操作_html5教程技巧

MutationObserver介绍

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

Mutation Observer(变动观察器)是监视DOM变动的接口。当DOM对象树发生任何变动时,Mutation Observer会得到通知。

Mutation Observer有以下特点:

 •它等待所有脚本任务完成后,才会运行,即采用异步方式
 •它把DOM变动记录封装成一个数组进行处理,而不是一条条地个别处理DOM变动。
 •它即可以观察发生在DOM节点的所有变动,也可以观察某一类变动

MDN的资料: MutationObserver

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):


复制代码
代码如下:

/**
* @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 if(target.children[ indexs[i] ]) {
target.insertBefore( nodes[i] , target.children[ indexs[i] ]) ;
}else{
target.appendChild( nodes[i] );
};
};
},
//快捷方法,用来判断child在父元素的哪个节点上;
"indexOf" : function ( target, obj ) {
return Array.prototype.indexOf.call(target, obj)
},
"getIndexs" : function (target, objs) {
var result = [];
for(var i=0; i result.push( this.indexOf(target, objs[i]) );
};
return result;
},
/**
* @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;
}
});

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



;
;

;



<script><br /> window.onload = function () {<br /> window.mu = new nono.MutationJs();<br /> //取消监听<br /> mu.disconnect();<br /> //重新监听<br /> mu.reObserve();<br /> document.getElementById("b0").addEventListener("click", function ( ev ) {<br /> div = document.createElement("div");<br /> div.innerHTML = document.getElementById("value").value;<br /> document.getElementById("div").appendChild( div );<br /> });<br /> document.getElementById("prev").addEventListener("click", function ( ev ) {<br /> mu.undo();<br /> });<br /> document.getElementById("next").addEventListener("click", function ( ev ) {<br /> mu.redo();<br /> });<br /> };<br /></script>


DEMO在IE下的截图:

MutatoinObserver的浏览器兼容性:

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

webkit

26

14(14) 11 15 6.0WebKit

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
HTML中的H5标签是什么?HTML中的H5标签是什么?May 09, 2025 am 12:11 AM

HTML中的H5标签是第五级标题,用于标记较小的标题或子标题。1)H5标签帮助细化内容层次,提升可读性和SEO。2)结合CSS可定制样式,增强视觉效果。3)合理使用H5标签,避免滥用,确保内容结构逻辑性。

H5代码:Web结构的初学者指南H5代码:Web结构的初学者指南May 08, 2025 am 12:15 AM

HTML5构建网站的方法包括:1.使用语义化标签定义网页结构,如、、等;2.嵌入多媒体内容,使用和标签;3.应用表单验证和本地存储等高级功能。通过这些步骤,你可以创建一个结构清晰、功能丰富的现代网页。

H5代码结构:组织内容以实现可读性H5代码结构:组织内容以实现可读性May 07, 2025 am 12:06 AM

通过合理的H5代码结构可以让页面在众多内容中脱颖而出。1)使用语义化标签如、、等组织内容,使结构清晰。2)通过CSS布局如Flexbox或Grid控制页面在不同设备上的呈现效果。3)实现响应式设计,确保页面在不同屏幕尺寸上自适应。

H5与较旧的HTML版本:比较H5与较旧的HTML版本:比较May 06, 2025 am 12:09 AM

HTML5(H5)与旧版本HTML的主要区别包括:1)H5引入了语义化标签,2)支持多媒体内容,3)提供离线存储功能。H5通过新标签和API增强了网页的功能和表现力,如和标签,提高了用户体验和SEO效果,但需注意兼容性问题。

H5与HTML5:澄清术语和关系H5与HTML5:澄清术语和关系May 05, 2025 am 12:02 AM

H5和HTML5的区别在于:1)HTML5是网页标准,定义结构和内容;2)H5是基于HTML5的移动网页应用,适用于快速开发和营销。

HTML5特征:H5的核心HTML5特征:H5的核心May 04, 2025 am 12:05 AM

HTML5的核心特性包括语义化标签、多媒体支持、表单增强和离线存储与本地存储。1.语义化标签如、等提高了代码可读性和SEO效果。2.多媒体支持通过和标签简化了嵌入媒体内容的过程。3.表单增强引入了新的输入类型和验证属性,简化了表单开发。4.离线存储和本地存储通过ApplicationCache和localStorage等提高了网页性能和用户体验。

H5:探索最新版本的HTMLH5:探索最新版本的HTMLMay 03, 2025 am 12:14 AM

html5isamajorrevisionofthehtmlStandardThatRevolutionsWebDevelopmentBybyIntroDucingNewSemanticeLementSemelementsandAndCapabilities.1)itenhancesCodereAdabilityAndSeowitability andSeowithelientsLike,and.2)

超越基础:H5代码中的高级技术超越基础:H5代码中的高级技术May 02, 2025 am 12:03 AM

H5的高级技巧包括:1.利用进行复杂图形绘制,2.使用WebWorkers提升性能,3.通过WebStorage增强用户体验,4.实现响应式设计,5.利用WebRTC实现实时通信,6.进行性能优化和最佳实践。这些技巧帮助开发者构建更动态、互动和高效的Web应用。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具