首頁 > web前端 > H5教程 > 主體

基於HTML5新特性Mutation Observer實作編輯器的撤銷與回退操作_html5教學技巧

WBOY
發布: 2016-05-16 15:46:11
原創
1876 人瀏覽過

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:第二個參數則是這個觀察者物件本身.

比如這樣:

     

複製程式碼
程式碼如下:


程式碼如下:



程式碼如下:

程式碼如下:

程式碼如下:

程式碼如下: mutations.forEach(function(mutation) { console.log(mutation.type);

});

});


observer的方法 實例observer有三種方法: 1: observe  ;2: disconnect ; 3: takeRecords   ;
observe方法


observe方法:給當前觀察者物件註冊需要觀察的目標節點,在目標節點(還可以同時觀察其後代節點)發生DOM變化時收到通知;

這個方法需要兩個參數,第一個為目標節點, 第二個參數為需要監聽變化的類型,是一個json對象,  實例如下:

       

複製程式碼

程式碼

'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,
'characterDataOld'Value : 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({ _this.disconnect();
_this.addChildren(target, record.removedNodes ,indexs );
_this.reObserve();
}, _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.removeChild(target, re. addedNodes );
_this.reObserve();
},
"redo" : function () {
_this.disconnect();
_this.addldldren(target, record. indexs);
_this.reObserve();
}
});
};
//@desc characterData是什麼鬼;
//ref : http:// baike.baidu.com/link?url=Z3Xr2y7zIF50bjXDFpSlQ0PiaUPVZhQJO7SaMCJXWHxD6loRcf_TVx1vsG74WUSZ_0-7wq4_oq0Ci-8ghUAG8a 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({
"실행 취소" : 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" : 함수( 대상, 노드 ) {
for( var i= 0, len= node.length; i target.removeChild( node[i] );
};
},
"addChildren": 함수( 대상, 노드, 인덱스) {
for(var i= 0, len= 노드.길이; 나는 if(target.children[ indexs[i] ]) {
target.insertBefore( 노드[i] , target.children[ 인덱스[i] ]) ;
}else{
target.appendChild( 노드[i] );
};
};
},
//快捷방법,사용来判断child재父元素的哪个节点上;
" indexOf": 함수(target, obj) {
return Array.prototype.indexOf.call(target, obj)
},
"getIndexs": 함수(target, objs) {
var 결과 = [];
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" : 함수 () {
this.muta.observe( this.dom, this.options );
},
/**
*@desc는 DOM 작업을 기록하지 않으며 이 함수 내의 모든 작업은 실행 취소 및 다시 실행 목록에 기록되지 않습니다.
**/
"without" : function ( fn ) {
this.disconnect();
fn&fn ();
this.reObserve();
},
/**
* @desc 모니터링 취소;
**/
"disconnect" : function () {
return this.muta.disconnect() ;
},
/**
* @desc Mutation 작업을 목록에 저장합니다.
**/
"save" : function ( obj ) {
if(!obj.undo)return warning("传进来的第一个参数必须 유 실행 취소 방법 실행");
if(!obj.redo)return 경고("传进来的第一个参数必须유재실행 방법 실행");
this.list.push(obj) ;
},
/**
* @desc ;
**/
"reset" : function () {
//清空数组;
this.list = [];
this .index = 0;
},
/**
* @desc 지정된 인덱스 뒤의 작업을 삭제합니다.
**/
"splice" : 함수( 인덱스 ) {
this.list.splice( 인덱스 );
},
/**
* @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": 함수() {
return this.index !== -1;
},
/**
* @desc는 재작동 가능 여부를 결정합니다.
**/
"canRedo": 함수() {
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,Google瀏覽器,IE11上面運作完全正常:


複製代碼
代碼如下:

br />











MutationObserver是為了取代原來Mutation Events的一系列事件, 瀏覽器會監聽指定Element下所有元素的新增,刪除,替換等;



;
;

;


div>
<script><br /> window.onload = function () {<br /> window.mu = new nono.MutationJs();<br /> //取消監聽<br /> mu.disconnect();<br /> //取消監聽<br /> mu.disconnect();<br /> //取消監聽<br /> mu.disconnect();<br /> //重新監聽<br /> mu.reObserve();<br /> document.getElementById("b0").addEventListener("click", function ( ev ) {<br /> div = document.createElement("""""""" );<br /> div.innerHTML = document.getElementById("value").value;<br /> document.getElementById("div").appendChild( div );<br /> });<br /> doc "prev").addEventListener("click", function ( ev ) {<br /> mu.undo();<br /> });<br /> document.getElementById("next").addEventListenerener("click", function ( ev ) {<br /> mu.redo();</script>
}); };
相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板