dom-class模塊是dojo中對於一個元素class特性的操作(特性與屬性的區別),主要方法有:
- contains 判斷元素是否包含某個css class
- add 為元素添加某個css class
- remove 移除某個css class
- replace 用某個css class取代另一個css class
- toggle 開關某個css class
對於支持classList的瀏覽器可以使用calssList提供的方法,但支持這個屬性的瀏覽器很少,貌似只有firefox和chrome支持。dojo這里使用了通用的方法:更改className的值。
// example: // Add two classes at once: // | require(["dojo/dom-class"], function(domClass){ // | domClass.add("someNode", "firstClass secondClass"); // | }); // // example: // Add two classes at once (using array): // | require(["dojo/dom-class"], function(domClass){ // | domClass.add("someNode", ["firstClass", "secondClass"]); // | });
該模塊中的許多方法,比如add、remove、replace既可以添加一個連續的class字符串(類與類之間使用空格相連:"class1 class2 class3")也可以添加class數組。在dojo內部的處理中,全部將“class1 class2 class3”這種形式轉化成數組。就是str2Array方法:
var cls, // exports object spaces = /\s+/, a1 = [""]; function str2array(s){ if(typeof s == "string" || s instanceof String){ // 單個字符串 if(s && !spaces.test(s)){ a1[0] = s; return a1; } var a = s.split(spaces); // 去除前面的空白字符,如:“ string” if(a.length && !a[0]){ a.shift(); } // 去除后面的空白字符,如:“string ” if(a.length && !a[a.length - 1]){ a.pop(); } return a; } // assumed to be an array if(!s){ return []; } // 普通數組 return array.filter(s, function(x){ return x; }); }
按我的理解去除前后空白字符的過程有些啰嗦,dojo/_base/lang模塊有trim方法,就是用來去除前后空白字符。這里完全可以直接調用,看來不是一個人寫的。
contains、add、remove這三個函數屬於這個模塊中的基礎方法,理解這個模塊的代碼還要知道一個核心原理是:這些方法全部為className和新class的頭尾新加空格: " class1 class2 class3 "; " newClass ",利用字符串操作的方式來處理,這樣既可以提高處理效率又能有效避免瀏覽器多次重繪引發的性能問題。
先看一下contains方法:
contains: function containsClass(/*DomNode|String*/ node, /*String*/ classStr){ // summary: // Returns whether or not the specified classes are a portion of the // class list currently applied to the node. // node: String|DOMNode // String ID or DomNode reference to check the class for. // classStr: String // A string class name to look for. // example: // Do something if a node with id="someNode" has class="aSillyClassName" present // | if(dojo.hasClass("someNode","aSillyClassName")){ ... } return ((" " + dom.byId(node)[className] + " ").indexOf(" " + classStr + " ") >= 0); // Boolean },
將className與classStr首尾都添加空格后,利用String類型的indexOf方式來判斷是否存在classStr。
add: function addClass(/*DomNode|String*/ node, /*String|Array*/ classStr){ node = dom.byId(node); //轉化為數組 classStr = str2array(classStr); var cls = node[className], oldLen; // 添加空格字符 cls = cls ? " " + cls + " " : " "; oldLen = cls.length; // classStr挨個判斷,不存在與cls中的就添加進去 for(var i = 0, len = classStr.length, c; i < len; ++i){ c = classStr[i]; if(c && cls.indexOf(" " + c + " ") < 0){ cls += c + " "; } } // cls改變的話就使用新的className if(oldLen < cls.length){ node[className] = cls.substr(1, cls.length - 2);// 去除首尾的空白字符 } }
remove: function removeClass(/*DomNode|String*/ node, /*String|Array?*/ classStr){ node = dom.byId(node); var cls; if(classStr !== undefined){ //這里與add方法中的思路類似 classStr = str2array(classStr); cls = " " + node[className] + " "; for(var i = 0, len = classStr.length; i < len; ++i){ // 將classStr中的class移除掉 cls = cls.replace(" " + classStr[i] + " ", " "); } cls = lang.trim(cls); }else{ // 沒有第二個參數則將所有class都移除掉 cls = ""; } if(node[className] != cls){ node[className] = cls; } }
下面介紹replace方法,顧名思義替換,替換的方式通常都是先刪除再添加。如果對於同一個節點刪除、添加 class會引起瀏覽器重繪,所以這里引入了fakeNode來降低瀏覽器重繪次數,提高性能。
replace: function replaceClass(/*DomNode|String*/ node, /*String|Array*/ addClassStr, /*String|Array?*/ removeClassStr){ node = dom.byId(node); //利用fakeNode避免移除、添加過程中瀏覽器重繪 fakeNode[className] = node[className]; cls.remove(fakeNode, removeClassStr); cls.add(fakeNode, addClassStr); if(node[className] !== fakeNode[className]){ node[className] = fakeNode[className]; } }
toggle方法可以對一組class進行開關控制,存在則刪除,沒有則添加。
toggle: function toggleClass(/*DomNode|String*/ node, /*String|Array*/ classStr, /*Boolean?*/ condition){ node = dom.byId(node); if(condition === undefined){ classStr = str2array(classStr); for(var i = 0, len = classStr.length, c; i < len; ++i){ c = classStr[i]; cls[cls.contains(node, c) ? "remove" : "add"](node, c); } }else{ cls[condition ? "add" : "remove"](node, classStr); } return condition; // Boolean }
看dojo的實現方式,使用toggle對一組class開關操作時會導致瀏覽器多次重繪,我們完全可以對className和classStr做差異融合,然后一次替換,或者像replace中一樣,利用fakeNode來防止多次重繪。
toggle: function toggleClass(/*DomNode|String*/ node, /*String|Array*/ classStr, /*Boolean?*/ condition){ node = dom.byId(node); if(condition === undefined){ classStr = str2array(classStr); fakeNode[className] = node[className];// 利用fakeNode防止多次重繪 for(var i = 0, len = classStr.length, c; i < len; ++i){ c = classStr[i]; cls[cls.contains(node, c) ? "remove" : "add"](fakeNode, c); } // 一次重繪 if(node[className] !== fakeNode[className]){ node[className] = fakeNode[className]; } }else{ cls[condition ? "add" : "remove"](node, classStr); } return condition; // Boolean }
如果您覺得這篇文章對您有幫助,請不吝點擊右下方“推薦”,謝謝~