早期的項目爲了下降兼容瀏覽器的成本使用了jQuery,在項目使用期間,也逐漸暴露了一些問題。因爲咱們是sdk項目,須要集成進公司其餘部門的產品,因此對適配以及兼容性要求比較高。在應用過程當中,就發生了與移動端三方庫zepto變量$衝突的問題。當時爲了解決這個問題,新建了一個變量提供給jQuery以免衝突。後期有了充裕的時間,便考慮寫一個基類用來代替jQuery,同時還能減小一次網絡請求,何樂而不爲呢。css
我把這個基類起名叫作BaseElement
,因爲項目中實際使用的dom節點不是不少,不須要考慮渲染的性能消耗,因此只實現了一些節點操做必備的方法。html
用一個基類來封裝dom節點,必然會有不一樣的節點類型和不一樣的樣式,有時也會有文本,因此要給構造函數加入幾個參數:css3
constructor (tag, className, text) {
this._element = null;
if (typeof tag === 'string') {
this._element = document.createElement(tag);
} else if (tag instanceof HTMLElement) {
this._element = tag;
}
if (this._element) {
if (typeof className === 'string' && className !== '') {
this._element.className = className;
}
if (typeof text === 'string' && text !== '') {
this._element.innerText = text;
}
}
}
get element () {
return this._element;
}
複製代碼
這樣BaseElement
基類就用_element
這個變量持有了dom節點,接下來的一系列方法也都與dom節點有關。瀏覽器
首先是顯示與隱藏的方法,並加入了變量_visible
用於記錄當前節點的顯示狀態,節點默認爲顯示:網絡
constructor () {
this._visible = true;
}
show () {
this._element.style.display = '';
this._visible = true;
return this;
}
hide () {
this._element.style.display = 'none';
this._visible = false;
return this;
}
get visible () {
return this._visible;
}
複製代碼
這兩個方法都返回了當前節點基類,方便外部調用時用一行代碼實現多種功能,例如:app
let element = new BaseElement('div');
element.show().hide();
複製代碼
基類光有顯示和隱藏方法並不夠,還須要把這個節點加入到節點樹中才可以真正的顯示出來,這裏實現了與jQuery方法名相同的append
和preAppend
方法:dom
append (child) {
if (this._element instanceof HTMLElement) {
if (child instanceof BaseElement) {
this._element.appendChild(child.element);
} else if (child instanceof HTMLElement) {
this._element.appendChild(child);
}
return this;
}
return null;
}
preAppend (child) {
if (this._element instanceof HTMLElement) {
if (child instanceof BaseElement) {
this._element.prepend(child.element);
} else if (child instanceof HTMLElement) {
this._element.prepend(child);
}
return child;
}
return null;
}
複製代碼
再來一個移除的方法:ide
remove (child) {
if (this._element instanceof HTMLElement)
if (child instanceof BaseElement) {
this._element.removeChild(child.element);
} else if (child instanceof HTMLElement) {
this._element.removeChild(child);
}
return this;
}
return null;
}
複製代碼
再實現一個移除自身全部子節點的方法:函數
empty () {
if (this._element instanceof HTMLElement) {
let children = this._element.children;
while (children.length) {
this._element.removeChild(children[0]);
}
}
}
複製代碼
實現這些方法以後,節點基類已經能夠自由的添加與移除節點,接下來就須要處理節點的行間內容與樣式了。性能
節點行間內容用兩個存取器能夠輕鬆實現:
set text (value) {
if (typeof value === 'string' && this._element instanceof HTMLElement) {
this._element.innerText = value;
}
}
get text () {
if (this._element instanceof HTMLElement) {
return this._element.innerText;
}
return '';
}
set html (value) {
if (typeof value === 'string' && this._element instanceof HTMLElement) {
this._element.innerHTML = value;
}
}
get html () {
if (this._element instanceof HTMLElement) {
return this._element.innerHTML;
}
return '';
}
複製代碼
至於樣式,也提供了不一樣的方法來操做:
css (key, value) {
if (value !== null && this._element instanceof HTMLElement) {
this._element.style[key] = value;
}
return this;
}
addClass (className) {
if (this._element instanceof HTMLElement) {
let tmpClassName = this._element.className;
if (className !== '' && !tmpClassName.split(' ').includes(className)) {
tmpClassName += ' ' + className;
this._element.className = tmpClassName;
}
return this;
}
return null;
}
removeClass (className) {
if (this._element instanceof HTMLElement) {
let tmpClassName = this._element.className;
if (className !== '') {
this._element.className = tmpClassName.split(' ').filter((value) => {
return value !== className && value !== '';
}).join(' ');
}
return this;
}
return null;
}
hasClass (className) {
if (this._element instanceof HTMLElement) {
if(className !== '') {
return this._element.className.indexOf(className) >= 0;
}
}
return false;
}
複製代碼
css方法能夠直接設置節點的某項樣式,例如:
let element = new BaseElement('div');
element.css('width', '100%');
複製代碼
而addClass
方法則直接給節點設置一個class,須要寫相應的css來配合。
有時咱們還須要獲取節點的left
與top
值進行相應的計算,再提供一個_offset
方法來獲取這些值,_offset
方法經過循環調用offsetParent
來獲取當前節點與頁面左上角的偏移,關於offsetParent
的詳情請查看這裏:
_offset (direction) {
if (this._element instanceof HTMLElement) {
let offsetDir: string = 'offset' + direction[0].toUpperCase() + direction.substring(1);
let realNum: number = this._element[offsetDir];
var positionParent: Element = (this._element as HTMLElement).offsetParent;
while(positionParent !== null) {
realNum += positionParent[offsetDir];
positionParent = (positionParent as HTMLElement).offsetParent;
}
return realNum;
}
return 0;
}
get left () {
return this._offset('left');
}
get top () {
return this._offset('top');
}
複製代碼
在jQuery中,動畫是依照計時器來計算節點屬性的,實現起來須要必定的代碼量,而項目應用環境也都是現代瀏覽器,因此動畫方面就使用css3來實現了。這裏有一個小技巧,若是須要實現重複性的動畫,好比popup效果,能夠寫兩個css,好比animate_0
和animate_1
,再加上這樣的代碼就沒問題了:
this._animateIndex = 0;
applyAnimate () {
let animateName = `animate_${this._animateIndex}`;
this._element.removeClass(animateName);
this._animateIndex = 1 - this._animateIndex;
this._element.addClasss(animateName);
}
複製代碼
最後須要給節點添加一些事件偵聽,畢竟如今純展現性的項目不多,絕大多數的項目都須要與用戶產生交互,方法以下:
on (type, callback) {
if (this._element instanceof HTMLElement) {
if (this._element.addEventListener) {
this._element.addEventListener(type, callback);
} else if (this._element['attachEvent']) {
this._element['attachEvent']('on' + type, callback);
} else {
this._element['on' + type] = callback;
}
}
}
複製代碼
還有一個在交互中喜聞樂見的hover
方法:
hover (inCallback, outCallback) {
// mouseover和mouseout在進入子節點時也會觸發,這裏使用mouseenter和mouseleave
this.on('mouseenter', inCallback);
this.on('mouseleave', outCallback);
}
複製代碼
節點基類到這裏,支持一般的項目開發已經足夠了,若是有一些特殊的節點類型,好比video之類,再自行擴展便可。
在項目中用BaseElement
構建dom節點,因爲使用的都是封裝好的方法,也可使全部dom節點的操做都有相對統一的入口,這自己就秉持着面向對象的思想。
也許這個基類的適用面並無很廣,但只要可以起到拋磚引玉的做用,那也是很好的結果了。