html5 observer api,基于HTML5新特性Mutation Observer实现编辑器的撤销和回退操作

MutationObserver介绍

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

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

Mutation Observer有以下特点:

•它等待所有脚本任务完成后,才会运行,即采用异步方式

•它把DOM变动记录封装成一个数组进行处理,而不是一条条地个别处理DOM变动。

•它即可以观察发生在DOM节点的所有变动,也可以观察某一类变动

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

;

;

;

window.onload = function () {

window.mu = new nono.MutationJs();

//取消监听

mu.disconnect();

//重新监听

mu.reObserve();

document.getElementById("b0").addEventListener("click", function ( ev ) {

div = document.createElement("div");

div.innerHTML = document.getElementById("value").value;

document.getElementById("div").appendChild( div );

});

document.getElementById("prev").addEventListener("click", function ( ev ) {

mu.undo();

});

document.getElementById("next").addEventListener("click", function ( ev ) {

mu.redo();

});

};

DEMO在IE下的截图:

e4dcb0f3e5350cc37ebc79aaebc39b7f.png

MutatoinObserver的浏览器兼容性:

Feature

Chrome

Firefox (Gecko)

Internet Explorer

Opera

Safari

Basic support

14(14)

11

15

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/430824.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Maven(五)使用Nexus搭建Maven私服

文章装载于:http://blog.csdn.net/jun55xiu/article/details/39497089 Nexus介绍 Nexus是Maven仓库管理器,如果你使用Maven,你可以从Maven中央仓库下载所需要的构件(artifact),但这通常不是一个好的做法&am…

python mean dropna_小丸子踏入python之路:python_day05(用Pandas处理泰坦尼克船员获救数据titanic_train.csv)...

泰坦尼克船员获救数据:titanic_train.csv用excel打开数据集。显示如下:写在前边:为了方便以后运用numpy和pandas的库,分别造它们的别名np和pd.import pandas as pd #造pandas的别名为pdimport numpy as np #造numpy的别名为np一、…

计算机学测打多少字,速度测试,一分钟能打多少字?

六、速度测试——检验学习效果经过一段时间的练习,输入速度提高了不少吧,赶快来测试一下现在一分钟可以输入多少英文或汉字。金山打字通2010的“速度测试”功能不仅有基本的“屏幕对照”速度测试,还有“书本对照”测试及要求较高的“同声录入…

HDFS入门(1)

2015.07.12笔记 1.HDFS Distributed File System(操作系统实现人机交互,最重要的功能是文件管理,使用文件管理系统,windows、Linux文件管理系统有共性:用户可创建文件/夹,删除,修改权限&#xf…

竞赛图 计算机网络 应用题,我校学子获2020年“中国高校计算机大赛-网络技术挑战赛”全国总决赛一等奖(图)...

近日,2020年“中国高校计算机大赛-网络技术挑战赛”全国总决赛在温州浙南科技城落下帷幕。我校计算机与信息安全学院陈俊彦、雷晓春老师指导的“智载车队”团队(成员:林楷浩、陈澳格、黄湖)在创业先锋C系列中获得全国一等奖,在创新创意A系列中…

python string模块template_Python - 定制pattern的string模板(template) 详解

定制pattern的string模板(template) 详解本文地址: http://blog.csdn.net/caroline_wendy/article/details/28625179string.Template的pattern是一个正则表达式, 可以通过覆盖pattern属性, 定义新的正则表达式.如: 使用新的定界符"{{", 把{{var}}作为变量语法.代码:#…

建立远程桌面连接计算机无密码,win7远程桌面空密码的步骤_win7系统如何设置让远程桌面登录无需密码-win7之家...

在日常工作中,可能经常会使用到远程连接桌面功能,我们要远程桌面的话,通常是要输入密码才可以的,但是有些用户觉得麻烦,那么win7系统如何设置让远程桌面登录无需密码呢?带着大家的这个问题,本文…

android 请求网络异步载入

/*** 封装ProecssDialog对话框**/ public class LoadDialog extends ProgressDialog {private String title "进度对话框";private String message "载入数据中....";public LoadDialog(Context context, int theme) {super(context, theme);}/*** 用默认…

tkinter如何lable重复显示到同一行中_如何创建包含 CAD 导入和选择的仿真 App

在使用 COMSOL 软件二次开发的过程中,你可能会遇到这样的问题:如何使用 App 开发器创建可以处理 CAD 导入并能让用户交互式选择边界条件的仿真 App?我需要了解编程吗?今天我们将为您介绍在 COMSOL 软件中创建包含 CAD 导入和选择的…

计算机科学导论课后单词,计算机科学导论课后总结

计算机科学导论课后总结1老师上课给我们演示了一个迷宫的程序,然后我上网查找了一下,大致学习了一下这个程序的思想。迷宫这个题目和数据结构—图有关迷宫的随机生成和路径搜索主要和图的遍历有关,一般来说图的遍历主要有两种方式&#xff1a…

InnerClass内部类

1,内部类概述 定义:把A类定义在B类内部,则A类是内部类。如下所示: class Outer1{外部类String name1;public void show(){System.out.println("Outer1.show()"new Inner1().name);}class Inner1{//内部类String name;pu…

表单内如何直接贴图而不用上传图片_表单如何添加图片?

表单其实就是我们平常经常看到的收集信息的框框,如姓名、电话等,当然易表单就远不止这些哦,还可制作调查问卷,报名登记、投票评选和考试测评。平时大家都擅长于制作表单的文字内容,实际上表单里边添加一些图片会让表单…

Android应用的安全的攻防之战

一、前言 在前两篇破解的文章中,我们介绍了如何使用动态调试来破解apk,一个是通过调试smali源码,一个是通过调试so代码来进行代码的跟踪破解,那么今天我们就这两篇文章的破解方法,来看看Android中开发应用的过程中如何…

标准正态分布表_表达矩阵的归一化和标准化,去除极端值,异常值

我们阅读量破万的综述:RNA-seq这十年(3万字长文综述)给粉丝朋友们带来了很多理解上的挑战,所以我们开辟专栏慢慢介绍其中的一些概念性的问题,上一期:RNA-seq的counts值,RPM, RPKM, FPKM, TPM 的异同在数据挖掘过程&…

html input p,我想在input.phtml中创建下拉框。 (不要使用zend_form)

案例1:在你看来:echo $this->formSelect(name, Option 1, array(),array(Option 1, Option 2));1 ARG:选择,第二个arg的名称:值;第三个参数:attributs,第4 ARG:选择案例2&#xf…

HTML5学习

1. HTML5是W3C和WHCTWG合作的结果。 2. W3C 指 World Wide Web Consortium,万维网联盟。 3. HTML5新规则: 新特性应该基于 HTML、CSS、DOM 以及 JavaScript。减少对外部插件的需求(比如 Flash)更优秀的错误处理更多取代脚本的标记…

计算机网络关于封装成帧题目,上海第二工业大学-计算机网络通信期中试卷答案...

上海第二工业大学 (试卷编号: )2014-2015学年第2学期 计算机网络与通信 期中考试试卷姓名: 学号: 班级: 成绩:一、 填空题 (每空1分,共18分)1. 当前广泛提及的三网融合中的三网分别是 电信网络 、 电视网络…

python全套学习方法_python学习方法总结(内附python全套学习资料)

种一棵树最好的时间是十年前,第二好的时间是现在(文末有资料获取方法)先聊聊互联网转行那些事儿很多人是小白,想从事或转行互联网,想以python为切入口,那么恭喜你,这可能是你离互联网工作最捷径的一条道路了&#xff0…

2016.03.04,英语,《Vocabulary Builder》Unit 04

vor: 来自拉丁动词vorare,指to eat,-ivorous指吃某种食物的eater。carn肉,肉欲vore吃→吃肉的;carnival狂欢节,谢肉节voracious a 狼吞虎咽的(voracious多…..的--吃多的) voracity n 贪食(voracilty) devour v 吞吃&a…

计算机培训简报膜报,信息技术培训工作简报(第4期).doc

信息技术培训工作简报(第4期)东原香山实验小学 (第四期) 2016年3月1日信息技术培训研修总结通过培训学习,使我进一步地掌握了有关现代信息技术的知识,提高了认识。随着计算机、多媒体、现代通讯网络为代表的信息技术的迅猛发展,信息技术已经渗透到了教育…