基于HTML5新特性Mutation Observer實(shí)現(xiàn)編輯器的撤銷(xiāo)和回退操作
來(lái)源:易賢網(wǎng) 閱讀:1302 次 日期:2016-07-09 10:01:45
溫馨提示:易賢網(wǎng)小編為您整理了“基于HTML5新特性Mutation Observer實(shí)現(xiàn)編輯器的撤銷(xiāo)和回退操作”,方便廣大網(wǎng)友查閱!

Mutation Observer(變動(dòng)觀(guān)察器)是監(jiān)視DOM變動(dòng)的接口。當(dāng)DOM對(duì)象樹(shù)發(fā)生任何變動(dòng)時(shí),Mutation Observer會(huì)得到通知,本文給大家分享基于HTML5新特性Mutation Observer實(shí)現(xiàn)編輯器的撤銷(xiāo)和回退操作,感興趣的朋友參考下

MutationObserver介紹

MutationObserver給開(kāi)發(fā)者們提供了一種能在某個(gè)范圍內(nèi)的DOM樹(shù)發(fā)生變化時(shí)作出適當(dāng)反應(yīng)的能力.該API設(shè)計(jì)用來(lái)替換掉在DOM3事件規(guī)范中引入的Mutation事件.

Mutation Observer(變動(dòng)觀(guān)察器)是監(jiān)視DOM變動(dòng)的接口。當(dāng)DOM對(duì)象樹(shù)發(fā)生任何變動(dòng)時(shí),Mutation Observer會(huì)得到通知。

Mutation Observer有以下特點(diǎn):

•它等待所有腳本任務(wù)完成后,才會(huì)運(yùn)行,即采用異步方式

•它把DOM變動(dòng)記錄封裝成一個(gè)數(shù)組進(jìn)行處理,而不是一條條地個(gè)別處理DOM變動(dòng)。

•它即可以觀(guān)察發(fā)生在DOM節(jié)點(diǎn)的所有變動(dòng),也可以觀(guān)察某一類(lèi)變動(dòng)

MDN的資料: MutationObserver

MutationObserver是一個(gè)構(gòu)造函數(shù), 所以創(chuàng)建的時(shí)候要通過(guò) new MutationObserver;

實(shí)例化MutationObserver的時(shí)候需要一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)會(huì)在指定的DOM節(jié)點(diǎn)(目標(biāo)節(jié)點(diǎn))發(fā)生變化時(shí)被調(diào)用,

在調(diào)用時(shí),觀(guān)察者對(duì)象會(huì) 傳給該函數(shù) 兩個(gè)參數(shù):

1:第一個(gè)參數(shù)是個(gè)包含了若干個(gè)MutationRecord對(duì)象的數(shù)組;

2:第二個(gè)參數(shù)則是這個(gè)觀(guān)察者對(duì)象本身.

比如這樣:

代碼如下:

var observer = new MutationObserver(function(mutations) { 

mutations.forEach(function(mutation) { 

console.log(mutation.type); 

}); 

});

observer的方法

實(shí)例observer有三個(gè)方法: 1: observe  ;2: disconnect ; 3: takeRecords   ;

observe方法

observe方法:給當(dāng)前觀(guān)察者對(duì)象注冊(cè)需要觀(guān)察的目標(biāo)節(jié)點(diǎn),在目標(biāo)節(jié)點(diǎn)(還可以同時(shí)觀(guān)察其后代節(jié)點(diǎn))發(fā)生DOM變化時(shí)收到通知;

這個(gè)方法需要兩個(gè)參數(shù),第一個(gè)為目標(biāo)節(jié)點(diǎn), 第二個(gè)參數(shù)為需要監(jiān)聽(tīng)變化的類(lèi)型,是一個(gè)json對(duì)象,  實(shí)例如下:

代碼如下:

observer.observe( document.body, {

'childList': true, //該元素的子元素新增或者刪除

'subtree': true, //該元素的所有子元素新增或者刪除

'attributes' : true, //監(jiān)聽(tīng)屬性變化

'characterData' : true, // 監(jiān)聽(tīng)text或者comment變化

'attributeOldValue' : true, //屬性原始值

'characterDataOldValue' : true 

});

disconnect方法

disconnect方法會(huì)停止觀(guān)察目標(biāo)節(jié)點(diǎn)的屬性和節(jié)點(diǎn)變化, 直到下次重新調(diào)用observe方法;

takeRecords

清空 觀(guān)察者對(duì)象的 記錄隊(duì)列,并返回一個(gè)數(shù)組, 數(shù)組中包含Mutation事件對(duì)象;

MutationObserver實(shí)現(xiàn)一個(gè)編輯器的redo和undo再適合不過(guò)了, 因?yàn)槊看沃付ü?jié)點(diǎn)內(nèi)部發(fā)生的任何改變都會(huì)被記錄下來(lái), 如果使用傳統(tǒng)的keydown或者keyup實(shí)現(xiàn)會(huì)有一些弊端,比如:

1:失去滾動(dòng), 導(dǎo)致滾動(dòng)位置不準(zhǔn)確;

2:失去焦點(diǎn);

....

用了幾小時(shí)的時(shí)間,寫(xiě)了一個(gè)通過(guò)MutationObserver實(shí)現(xiàn)的 undo 和 redo (撤銷(xiāo)回退的管理)的管理插件 MutationJS ,   可以作為一個(gè)單獨(dú)的插件引入:(http://files.cnblogs.com/files/diligenceday/MutationJS.js):

代碼如下:

/**

* @desc MutationJs, 使用了DOM3的新事件 MutationObserve; 通過(guò)監(jiān)聽(tīng)指定節(jié)點(diǎn)元素, 監(jiān)聽(tīng)內(nèi)部dom屬性或者dom節(jié)點(diǎn)的更改, 并執(zhí)行相應(yīng)的回調(diào);

* */

window.nono = window.nono || {};

/**

* @desc

* */

nono.MutationJs = function( dom ) {

//統(tǒng)一兼容問(wèn)題

var MutationObserver = this.MutationObserver = window.MutationObserver ||

window.WebKitMutationObserver ||

window.MozMutationObserver;

//判斷瀏覽器是或否支持MutationObserver;

this.mutationObserverSupport = !!MutationObserver;

//默認(rèn)監(jiān)聽(tīng)子元素, 子元素的屬性, 屬性值的改變;

this.options = {

'childList': true,

'subtree': true,

'attributes' : true,

'characterData' : true,

'attributeOldValue' : true,

'characterDataOldValue' : true

};

//這個(gè)保存了MutationObserve的實(shí)例;

this.muta = {};

//list這個(gè)變量保存了用戶(hù)的操作;

this.list = [];

//當(dāng)前回退的索引

this.index = 0;

//如果沒(méi)有dom的話(huà),就默認(rèn)監(jiān)聽(tīng)body;

this.dom = dom|| document.documentElement.body || document.getElementsByTagName("body")[0];

//馬上開(kāi)始監(jiān)聽(tīng);

this.observe( );

};

$.extend(nono.MutationJs.prototype, {

//節(jié)點(diǎn)發(fā)生改變的回調(diào), 要把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) {

//獲取元素的相對(duì)索引;

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

//獲取元素的相對(duì)索引;

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, 不準(zhǔn)備處理IE789的兼容,所以不用innerText了;

_this.list.push({

"undo" : function() {

_this.disconnect();

target.textContent = oldValue;

_this.reObserve();

},

"redo" : function () {

_this.disconnect();

target.textContent = newValue;

_this.reObserve();

}

});

//如果是屬性變化的話(huà)style, dataset, attribute都是屬于attributes發(fā)生改變, 可以統(tǒng)一處理;

}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();

}

});

};

});

//重新設(shè)置索引;

this.index = this.list.length-1;

},

"removeChildren" : function ( target, nodes ) {

for(var i= 0, len= nodes.length; i<len; i++ ) {

target.removeChild( nodes[i] );

};

},

"addChildren" : function ( target, nodes ,indexs) {

for(var i= 0, len= nodes.length; i<len; i++ ) {

if(target.children[ indexs[i] ]) {

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

}else{

target.appendChild( nodes[i] );

};

};

},

//快捷方法,用來(lái)判斷child在父元素的哪個(gè)節(jié)點(diǎn)上;

"indexOf" : function ( target, obj ) {

return Array.prototype.indexOf.call(target, obj)

},

"getIndexs" : function (target, objs) {

var result = [];

for(var i=0; i<objs.length; i++) {

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

};

return result;

},

/**

* @desc 指定監(jiān)聽(tīng)的對(duì)象

* */

"observe" : function( ) {

if( this.dom.nodeType !== 1) return alert("參數(shù)不對(duì),第一個(gè)參數(shù)應(yīng)該為一個(gè)dom節(jié)點(diǎn)");

this.muta = new this.MutationObserver( this.callback.bind(this) );

//馬上開(kāi)始監(jiān)聽(tīng);

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

},

/**

* @desc 重新開(kāi)始監(jiān)聽(tīng);

* */

"reObserve" : function () {

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

},

/**

*@desc 不記錄dom操作, 所有在這個(gè)函數(shù)內(nèi)部的操作不會(huì)記錄到undo和redo的列表中;

* */

"without" : function ( fn ) {

this.disconnect();

fn&fn();

this.reObserve();

},

/**

* @desc 取消監(jiān)聽(tīng);

* */

"disconnect" : function () {

return this.muta.disconnect();

},

/**

* @desc 保存Mutation操作到list;

* */

"save" : function ( obj ) {

if(!obj.undo)return alert("傳進(jìn)來(lái)的第一個(gè)參數(shù)必須有undo方法才行");

if(!obj.redo)return alert("傳進(jìn)來(lái)的第一個(gè)參數(shù)必須有redo方法才行");

this.list.push(obj);

},

/**

* @desc ;

* */

"reset" : function () {

//清空數(shù)組;

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 判斷是否可以撤銷(xiāo)操作

* */

"canUndo" : function () {

return this.index !== -1;

},

/**

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

* */

"canRedo" : function () {

return this.list.length-1 !== this.index;

}

});

MutationJS如何使用

那么這個(gè)MutationJS如何使用呢?

代碼如下:

//這個(gè)是實(shí)例化一個(gè)MutationJS對(duì)象, 如果不傳參數(shù)默認(rèn)監(jiān)聽(tīng)body元素的變動(dòng);

mu = new nono.MutationJs();

//可以傳一個(gè)指定元素,比如這樣;

mu = new nono.MutationJS( document.getElementById("div0") );

//那么所有該元素下的元素變動(dòng)都會(huì)被插件記錄下來(lái);

Mutation的實(shí)例mu有幾個(gè)方法:

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

2:mu.redo()   撤銷(xiāo)回退;

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

4:mu.canRedo() 是否可以撤銷(xiāo)回退, 返回值為true或者false;

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

6:mu.without() 傳一個(gè)為函數(shù)的參數(shù), 所有在該函數(shù)內(nèi)部的dom操作, mu不做記錄;

MutationJS實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的 undoManager 提供參考,在火狐和chrome,谷歌瀏覽器,IE11上面運(yùn)行完全正常:

代碼如下:

<!DOCTYPE html>

<html>

<head lang="en">

<meta charset="UTF-8">

<title></title>

<script src="http://cdn.bootcss.com/jquery/1.9.0/jquery.js"></script>

<script src="http://files.cnblogs.com/files/diligenceday/MutationJS.js"></script>

</head>

<body>

<div>

<p>

MutationObserver是為了替換掉原來(lái)Mutation Events的一系列事件, 瀏覽器會(huì)監(jiān)聽(tīng)指定Element下所有元素的新增,刪除,替換等;

</p>

<div style="padding:20px;border:1px solid #f00">

<input type="button" value="撤銷(xiāo)操作" id="prev">;

<input type="button" value="撤銷(xiāo)操作回退" id="next">;

</div>

<input type="button" value="添加節(jié)點(diǎn)" id="b0">;

<input value="text" id="value">

<div id="div"></div>

</div>

<script>

window.onload = function () {

window.mu = new nono.MutationJs();

//取消監(jiān)聽(tīng)

mu.disconnect();

//重新監(jiān)聽(tīng)

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();

});

};

</script>

</body>

</html>

更多信息請(qǐng)查看網(wǎng)頁(yè)制作
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢(xún)回復(fù)僅供參考,敬請(qǐng)考生以權(quán)威部門(mén)公布的正式信息和咨詢(xún)?yōu)闇?zhǔn)!
相關(guān)閱讀網(wǎng)頁(yè)制作

2025國(guó)考·省考課程試聽(tīng)報(bào)名

  • 報(bào)班類(lèi)型
  • 姓名
  • 手機(jī)號(hào)
  • 驗(yàn)證碼
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡(jiǎn)要咨詢(xún) | 簡(jiǎn)要咨詢(xún)須知 | 加入群交流 | 手機(jī)站點(diǎn) | 投訴建議
工業(yè)和信息化部備案號(hào):滇ICP備2023014141號(hào)-1 云南省教育廳備案號(hào):云教ICP備0901021 滇公網(wǎng)安備53010202001879號(hào) 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號(hào)
云南網(wǎng)警備案專(zhuān)用圖標(biāo)
聯(lián)系電話(huà):0871-65099533/13759567129 獲取招聘考試信息及咨詢(xún)關(guān)注公眾號(hào):hfpxwx
咨詢(xún)QQ:526150442(9:00—18:00)版權(quán)所有:易賢網(wǎng)
云南網(wǎng)警報(bào)警專(zhuān)用圖標(biāo)