手把手帶你用原生js實現css屬性的set和get

Icarus發表於2017-02-05

作者:Icarus
原文連結:手把手帶你用原生js實現css屬性的set和get

上一篇博文介紹了getComputedStyle方法,接下來,我們就來實現一個簡易版的小外掛,能夠在不借助jQuery的情況下實現css屬性的獲取和設定。

Let's start

首先建立一個 css-tool.js 檔案,一開始他是這個樣子的:

;(function (window,undefined) {
  "use strict";

  var cssTool = function () {
    return new cssTool.prototype.init();
  }

  cssTool.prototype = {
    init: function() {
      console.log('init success');
      return this;
    },
  }

  cssTool.prototype.init.prototype = cssTool.prototype;
  // 暴露介面
  window.cssTool = cssTool;

})(window);複製程式碼

全域性作用域可以看作是一棟公寓樓,我們建立一個立即執行的匿名函式,相當於是公寓樓中的一間公寓,我們在屋子裡做的事情就都是隱祕的,也就是起到隔離作用域的作用,避免和外部變數產生衝突。把 window 作為引數拿到屋子裡來,以後就不用再重複去外面找 window 來用。最前面的分號是為了保證在檔案合併壓縮後不產生語法錯誤。 undefined 在老版本瀏覽器中不被支援,因此考慮到相容性新增一個形參。

我們建立了一個叫 cssTool 的私有方法,相當於我們在屋子裡找了一個小房間來放 getset 等方法。接下來我們在原型上新增一個 init 方法,用於初始化。之後我們仿照 jQuery 的方式,將initprototype 指向 cssToolprototype ,這樣我們在用init作為建構函式創造例項時,可以使外掛擁有兩種呼叫方式:

  • var ct = new cssTool()構建 cssTool 例項
  • 直接呼叫cssTool(),一樣返回 cssTool 例項

get方法

通用方式

現代瀏覽器和IE9+

window.getComputedStyle(elem,null).getPropertyValue(attr)複製程式碼

IE678

elem.currentStyle.getAttribute(camelCase(attr))複製程式碼

相容處理

駝峰命名法轉換-camelCase

currentStyle 來說,在IE6瀏覽器中他很專一,只喜歡以駝峰命名法命名的變數,而IE78中就有點朝三暮四,駝峰命名法和中間帶'-'的都照單全收,為了相容和操作的簡便,我們統一轉換為駝峰命名法。

/**
 * 駝峰命名法轉換,IE678使用
 * font-size --> fontSize
 * @param {String} attr
 * @param {String} match  匹配到的字串,如-c
 * @param {String} originText (\w)是一個捕獲,這裡是捕獲到的字元,如c
 * @return 返回駝峰命名方式的css屬性名
 */
function camelCase (attr){
  return attr.replace(/\-(\w)/g, function (match,originText) {
    return originText.toUpperCase();
  });
}複製程式碼

透明度獲取-getFilter

IE678的透明度是通過 filter:alpha(opacity=0) 來設定的,我們利用正規表示式匹配到此時透明度的值,由於此時得到的是0-100之間的數值,所以需要換算為我們常見的0-1的形式。

/**
 * IE678下獲取透明度的值
 * @param  elem 獲取值的 dom
 * @return {Number} 透明度的值,預設為1
 * IE678下設定透明度 filter: alpha(opacity=0) 取值為0-100
 */
function getFilter(elem) {
  var _filter = elem.currentStyle.getAttribute('filter').match(/alpha\(opacity=(.*)\)/i);
  var value = parseFloat(_filter[1]);
  if(!isNaN(value)){
    // 轉化為0-1
    return value ? value/100 : 0;
  }
  return 1;
}複製程式碼

float 值的獲取

上一篇部落格中提到,由於 float 是 ECMAScript 的一個保留字。所以在各瀏覽器中都會有代替的寫法,比如說在現代瀏覽器中為 cssFloat,而在 IE678 中為 styleFloat 。經測試,在現代瀏覽器中直接使用 getPropertyValue("float") 也可以獲取到 float 的值。而 IE678 則不行,所以針對 float ,需要簡單的hack。

width | height 樣式的獲取

對於一個沒有設定高寬的元素而言,在 IE678 下直接獲取得到的值是 auto 。而現代瀏覽器會直接返回它的 px 值,我們的目標就是在 IE 下也返回 px 值。

// 直接獲取外部樣式表未設定的 width 和 height 返回值為 auto
// 為了獲取精確的 px 值,使用 getBoundingClientRect 方法
// getBoundingClientRect 可以獲得元素四個點相對於文件檢視左上角
// 的 top、left、bottom、right值,進行簡單計算即可
var condition = attr === 'width'
             || attr === 'height'
             && elem.currentStyle[attr] === 'auto';
if(condition){
  var clientRect = elem.getBoundingClientRect();
  return (attr === 'width' ?
          clientRect.right - clientRect.left :
          clientRect.bottom - clientRect.top
         ) + 'px';
}複製程式碼

set方法

set 方法相較於 get 方法要簡便的多,因為我們有 cssText 這個跨越 IE6+ 和現代瀏覽器的神器。
通過elem.style.cssText可以對元素的樣式進行讀寫,實際上操作的是 html 標籤上的 style 屬性的值。因此不能直接對其賦值,不然就把整個 style 屬性的值給覆蓋掉了。我們採用累加的方式來修改屬性。
另外,在IE瀏覽器還有個小坑,如果 cssText 不為空,返回值最後一個分號會被刪掉,因此我們需要在累加的屬性前加上一個分號。

/**
 * 設定元素css樣式值
 * @param elem 設定值的dom元素
 * @param {String} attr 設定樣式名稱,如font-size
 * @param {String} value 設定樣式的值,如16px
 */
set: function (elem, attr, value){
  // IE78 設定透明度需特殊處理
  if(attr === 'opacity'){
    // 針對 IE7 的 hack
    // filter 濾鏡要求 hasLFooout=true 才能執行
    // IE瀏覽器且 hasLFooout=false 時執行
    if(!!elem.currentStyle && !elem.currentStyle.hasLFooout){
      elem.style.zoom = 1;
      attr = 'filter';
      value = 'alpha(opacity=' + value * 100 + ')';
    }
  }
  // 通用方式
  elem.style.cssText += ';' + (attr + ':' + value) + ';';
}複製程式碼

補充

簡單解釋new操作符的作用

var Foo = function() {
  return new Foo.prototype.init();
}

Foo.prototype = {
  init: function() {
    this.age = 18;
    return this;
  },
  age: 20
}

console.log(Foo().age); // 18
console.log(Foo.prototype.age); // 20複製程式碼
var Foo = function() {
  return Foo.prototype.init();
}

Foo.prototype = {
  init: function() {
    this.age = 18;
    return this;
  },
  age: 20
}

console.log(Foo().age); // 18
console.log(Foo.prototype.age); // 18複製程式碼

使用 new 操作符時,是把 init 當成建構函式來呼叫,在 init 內部會建立一個隱式物件並用 this 指向它,此時的 this.age=18 表示在這個隱式物件上增加一個 age 屬性,最後 return this 不是必需的,建構函式預設會返回 this。此時Foo.prototype.age不受影響。
當不使用 new 操作符時,相當於一個​普通物件上的函式呼叫,this 指向了 init 所屬的物件,即 Foo.prototypethis.age=18相當於對 Foo.prototype.age 賦值,和使用 new 操作符是有本質區別的。

小結

到這裡,教程也就要告一段落了。一個 jQuery 中常見的 css() 方法背後涵蓋了非常多的知識點,跨瀏覽器的相容性也是我們此次討論的重點,這次只是實現了一個非常簡易的 css 操作外掛。學問尚淺,如果有不清楚或者有錯誤的地方,歡迎各位留言或者提issue來幫助我改進這個小外掛。
最後,完整的專案地址:github.com/xdlrt/css-t…
求一波star~

相關文章