摘要:方法也在讀源碼之內部方法有過分析。不太明白為什么要用全局變量來接收,用局部變量不是更好點嗎保存當前類的字符串,使用函數獲得。這是的依然是全局變量,但是接收的是當前元素的當前樣式類字符串為什么不用局部變量呢。
這篇依然是跟 dom 相關的方法,側重點是操作樣式的方法。
讀Zepto源碼系列文章已經放到了github上,歡迎star: reading-zepto
源碼版本本文閱讀的源碼為 zepto1.2.0
內部方法 classREclassCache = {} function classRE(name) { return name in classCache ? classCache[name] : (classCache[name] = new RegExp("(^|s)" + name + "(s|$)")) }
這個函數是用來返回一個正則表達式,這個正則表達式是用來匹配元素的 class 名的,匹配的是如 className1 className2 className3 這樣的字符串。
calssCache 初始化時是一個空對象,用 name 用為 key ,如果正則已經生成過,則直接從 classCache 中取出對應的正則表達式。
否則,生成一個正則表達式,存儲到 classCache 中,并返回。
來看一下這個生成的正則,"(^|s)" 匹配的是開頭或者空白(包括空格、換行、tab縮進等),然后連接指定的 name ,再緊跟著空白或者結束。
maybeAddPxcssNumber = { "column-count": 1, "columns": 1, "font-weight": 1, "line-height": 1, "opacity": 1, "z-index": 1, "zoom": 1 } function maybeAddPx(name, value) { return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value }
在給屬性設置值時,猜測所設置的屬性可能需要帶 px 單位時,自動給值拼接上單位。
cssNumber 是不需要設置 px 的屬性值,所以這個函數里首先判斷設置的值是否為 number 類型,如果是,并且需要設置的屬性不在 cssNumber 中時,給值拼接上 px 單位。
defaultDisplayelementDisplay = {} function defaultDisplay(nodeName) { var element, display if (!elementDisplay[nodeName]) { element = document.createElement(nodeName) document.body.appendChild(element) display = getComputedStyle(element, "").getPropertyValue("display") element.parentNode.removeChild(element) display == "none" && (display = "block") elementDisplay[nodeName] = display } return elementDisplay[nodeName] }
先透露一下,這個方法是給 .show() 用的,show 方法需要將元素顯示出來,但是要顯示的時候能不能直接將 display 設置成 block 呢?顯然是不行的,來看一下 display 的可能會有那些值:
display: none display: inline display: block display: contents display: list-item display: inline-block display: inline-table display: table display: table-cell display: table-column display: table-column-group display: table-footer-group display: table-header-group display: table-row display: table-row-group display: flex display: inline-flex display: grid display: inline-grid display: ruby display: ruby-base display: ruby-text display: ruby-base-container display: ruby-text-container display: run-in display: inherit display: initial display: unset
如果元素原來的 display 值為 table ,調用 show 后變成 block 了,那頁面的結構可能就亂了。
這個方法就是將元素顯示時默認的 display 值緩存到 elementDisplay,并返回。
函數用節點名 nodeName 為 key ,如果該節點顯示時的 display 值已經存在,則直接返回。
element = document.createElement(nodeName) document.body.appendChild(element)
否則,使用節點名創建一個空元素,并且將元素插入到頁面中
display = getComputedStyle(element, "").getPropertyValue("display") element.parentNode.removeChild(element)
調用 getComputedStyle 方法,獲取到元素顯示時的 display 值。獲取到值后將所創建的元素刪除。
display == "none" && (display = "block") elementDisplay[nodeName] = display
如果獲取到的 display 值為 none ,則將顯示時元素的 display 值默認為 block。然后將結果緩存起來。display 的默認值為 none? Are you kiding me ? 真的有這種元素嗎?還真的有,像 style、 head 和 title 等元素的默認值都是 none 。將 style 和 head 的 display 設置為 block ,并且將 style 的 contenteditable 屬性設置為 true ,style 就顯示出來了,直接在頁面上一邊敲樣式,一邊看效果,爽!!!
關于元素的 display 默認值,可以看看這篇文章 Default CSS Display Values for Different HTML Elements
funcArgfunction funcArg(context, arg, idx, payload) { return isFunction(arg) ? arg.call(context, idx, payload) : arg }
這個函數要注意,本篇和下一篇介紹的絕大多數方法都會用到這個函數。
例如本篇將要說到的 addClass 和 removeClass 等方法的參數可以為固定值或者函數,這些方法的參數即為形參 arg。
當參數 arg 為函數時,調用 arg 的 call 方法,將上下文 context ,當前元素的索引 idx 和原始值 payload 作為參數傳遞進去,將調用結果返回。
如果為固定值,直接返回 arg
classNamefunction className(node, value) { var klass = node.className || "", svg = klass && klass.baseVal !== undefined if (value === undefined) return svg ? klass.baseVal : klass svg ? (klass.baseVal = value) : (node.className = value) }
className 包含兩個參數,為元素節點 node 和需要設置的樣式名 value。
如果 value 不為 undefined(可以為空,注意判斷條件為 value === undefined,用了全等判斷),則將元素的 className 設置為給定的值,否則將元素的 className 值返回。
這個函數對 svg 的元素做了兼容,如果元素的 className 屬性存在,并且 className 屬性存在 baseVal 時,為 svg 元素,如果是 svg 元素,取值和賦值都是通過 baseVal 。對 svg 不是很熟,具體見文檔: SVGAnimatedString.baseVal
.css()css: function(property, value) { if (arguments.length < 2) { var element = this[0] if (typeof property == "string") { if (!element) return return element.style[camelize(property)] || getComputedStyle(element, "").getPropertyValue(property) } else if (isArray(property)) { if (!element) return var props = {} var computedStyle = getComputedStyle(element, "") $.each(property, function(_, prop) { props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop)) }) return props } } var css = "" if (type(property) == "string") { if (!value && value !== 0) this.each(function() { this.style.removeProperty(dasherize(property)) }) else css = dasherize(property) + ":" + maybeAddPx(property, value) } else { for (key in property) if (!property[key] && property[key] !== 0) this.each(function() { this.style.removeProperty(dasherize(key)) }) else css += dasherize(key) + ":" + maybeAddPx(key, property[key]) + ";" } return this.each(function() { this.style.cssText += ";" + css }) }
css 方法有兩個參數,property 是的 css 樣式名,value 是需要設置的值,如果不傳遞 value 值則為取值操作,否則為賦值操作。
來看看調用方式:
css(property) ? value // 獲取值 css([property1, property2, ...]) ? object // 獲取值 css(property, value) ? self // 設置值 css({ property: value, property2: value2, ... }) ? self // 設置值
下面這段便是處理獲取值情況的代碼:
if (arguments.length < 2) { var element = this[0] if (typeof property == "string") { if (!element) return return element.style[camelize(property)] || getComputedStyle(element, "").getPropertyValue(property) } else if (isArray(property)) { if (!element) return var props = {} var computedStyle = getComputedStyle(element, "") $.each(property, function(_, prop) { props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop)) }) return props } }
當為獲取值時,css 方法必定只傳遞了一個參數,所以用 arguments.length < 2 來判斷,用 css 方法來獲取值,獲取的是集合中第一個元素對應的樣式值。
if (!element) return return element.style[camelize(property)] || getComputedStyle(element, "").getPropertyValue(property)
當 property 為 string 時,如果元素不存在,直接 return 掉。
如果 style 中存在對應的樣式值,則優先獲取 style 中的樣式值,否則用 getComputedStyle 獲取計算后的樣式值。
為什么不直接獲取計算后的樣式值呢?因為用 style 獲取的樣式值是原始的字符串,而 getComputedStyle 顧名思義獲取到的是計算后的樣式值,如 style = "transform: translate(10px, 10px)" 用 style.transform 獲取到的值為 translate(10px, 10px),而用 getComputedStyle 獲取到的是 matrix(1, 0, 0, 1, 10, 10)。這里用到的 camelize 方法是將屬性 property 轉換成駝峰式的寫法,該方法在《讀Zepto源碼之內部方法》有過分析。
else if (isArray(property)) { if (!element) return var props = {} var computedStyle = getComputedStyle(element, "") $.each(property, function(_, prop) { props[prop] = (element.style[camelize(prop)] || computedStyle.getPropertyValue(prop)) }) return props }
如果參數 property 為數組時,表示要獲取一組屬性的值。isArray 方法也在《讀Zepto源碼之內部方法》有過分析。
獲取的方法也很簡單,遍歷 property ,獲取 style 上對應的樣式值,如果 style 上的值不存在,則通過 getComputedStyle 來獲取,返回的是以樣式名為 key ,value 為對應的樣式值的對象。
接下來是給所有元素設置值的情況:
var css = "" if (type(property) == "string") { if (!value && value !== 0) this.each(function() { this.style.removeProperty(dasherize(property)) }) else css = dasherize(property) + ":" + maybeAddPx(property, value) } else { for (key in property) if (!property[key] && property[key] !== 0) this.each(function() { this.style.removeProperty(dasherize(key)) }) else css += dasherize(key) + ":" + maybeAddPx(key, property[key]) + ";" } return this.each(function() { this.style.cssText += ";" + css })
這里定義了個變量 css 來接收需要新值的樣式字符串。
if (type(property) == "string") { if (!value && value !== 0) this.each(function() { this.style.removeProperty(dasherize(property)) }) else css = dasherize(property) + ":" + maybeAddPx(property, value) }
當參數 property 為字符串時
如果 value 不存在并且值不為 0 時(注意,value 為 undefined 時,已經在上面處理過了,也即是獲取樣式值),遍歷集合,將對應的樣式值從 style 中刪除。
否則,拼接樣式字符串,拼接成如 width:100px 形式的字符串。這里調用了 maybeAddPx 的方法,自動給需要加 px 的屬性值拼接上了 px 單位。this.css("width", 100) 跟 this.css("width", "100px") 會得到一樣的結果。
for (key in property) if (!property[key] && property[key] !== 0) this.each(function() { this.style.removeProperty(dasherize(key)) }) else css += dasherize(key) + ":" + maybeAddPx(key, property[key]) + ";"
當 property 為 key 是樣式名,value 為樣式值的對象時,用 for...in 遍歷對象,接下來的處理邏輯跟 property 為 string 時差不多,在做 css 拼接時,在末尾加了 ;,避免遍歷時,將樣式名和值連接在了一起。
.hide()hide: function() { return this.css("display", "none") },
將集合中所有元素的 display 樣式屬性設置為 node,就達到了隱藏元素的目的。注意,css 方法中已經包含了 each 循環。
.show()show: function() { return this.each(function() { this.style.display == "none" && (this.style.display = "") if (getComputedStyle(this, "").getPropertyValue("display") == "none") this.style.display = defaultDisplay(this.nodeName) }) },
hide 方法是直接將 display 設置為 none 即可,show 可不可以直接將需要顯示的元素的 display 設置為 block 呢?
這樣在大多數情況下是可以的,但是碰到像 table 、li 等顯示時 display 默認值不是 block 的元素,強硬將它們的 display 屬性設置為 block ,可能會更改他們的默認行為。
show 要讓元素真正顯示,要經過兩步檢測:
this.style.display == "none" && (this.style.display = "")
如果 style 中的 display 屬性為 none ,先將 style 中的 display 置為 ``。
if (getComputedStyle(this, "").getPropertyValue("display") == "none") this.style.display = defaultDisplay(this.nodeName) })
這樣還未完,內聯樣式的 display 屬性是置為空了,但是如果嵌入樣式或者外部樣式表中設置了 display 為 none 的樣式,或者本身的 display 默認值就是 none 的元素依然顯示不了。所以還需要用獲取元素的計算樣式,如果為 none ,則將 display 的屬性設置為元素顯示時的默認值。如 table 元素的 style 中的 display 屬性值會被設置為 table。
.toggle()toggle: function(setting) { return this.each(function() { var el = $(this); (setting === undefined ? el.css("display") == "none" : setting) ? el.show(): el.hide() }) },
切換元素的顯示和隱藏狀態,如果元素隱藏,則顯示元素,如果元素顯示,則隱藏元素。可以用參數 setting 指定 toggle 的行為,如果指定為 true ,則顯示,如果為 false ( setting 不一定為 Boolean),則隱藏。
注意,判斷條件是 setting === undefined ,用了全等,只有在不傳參,或者傳參為 undefined 的時候,條件才會成立。
.hasClass()hasClass: function(name) { if (!name) return false return emptyArray.some.call(this, function(el) { return this.test(className(el)) }, classRE(name)) },
判斷集合中的元素是否存在指定 name 的 class 名。
如果沒有指定 name 參數,則直接返回 false。
否則,調用 classRE 方法,生成檢測樣式名的正則,傳入數組方法 some,要注意, some 里面的 this 值并不是遍歷的當前元素,而是傳進去的 classRE(name) 正則,回調函數中的 el 才是當前元素。具體參考文檔 Array.prototype.some()
調用 className 方法,獲取當前元素的 className 值,如果有一個元素匹配了正則,則返回 true。
.addClass()addClass: function(name) { if (!name) return this return this.each(function(idx) { if (!("className" in this)) return classList = [] var cls = className(this), newName = funcArg(this, name, idx, cls) newName.split(/s+/g).forEach(function(klass) { if (!$(this).hasClass(klass)) classList.push(klass) }, this) classList.length && className(this, cls + (cls ? " " : "") + classList.join(" ")) }) },
為集合中的所有元素增加指定類名 name。 name 可以為固定值或者函數。
如果 name 沒有傳遞,則返回當前集合 this ,以進行鏈式操作。
如果 name 存在,遍歷集合,判斷當前元素是否存在 className 屬性,如果不存在,立即退出循環。要注意,在 each 遍歷中,this 指向的是當前元素。
classList = [] var cls = className(this), newName = funcArg(this, name, idx, cls)
classList 用來接收需要增加的樣式類數組。不太明白為什么要用全局變量 classList 來接收,用局部變量不是更好點嗎?
cls 保存當前類的字符串,使用函數 className 獲得。
newName 是需要新增的樣式類字符串,因為 name 可以是函數或固定值,統一交由 funcArg 來處理。
newName.split(/s+/g).forEach(function(klass) { if (!$(this).hasClass(klass)) classList.push(klass) }, this) classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
newName.split(/s+/g) 是將 newName 字符串,用空白分割成數組。
再對數組遍歷,得到單個類名,調用 hasClass 判斷類名是否已經存在于元素的 className 中,如果不存在,將類名 push 進數組 classList 中。
如果 classList 不為空,則調用 className 方法給元素設置值。classList.join(" ") 是將類名轉換成用空格分隔的字符串,如果 cls 即元素原來就存在有其他類名,拼接時也使用空格分隔開。
.removeClass()removeClass: function(name) { return this.each(function(idx) { if (!("className" in this)) return if (name === undefined) return className(this, "") classList = className(this) funcArg(this, name, idx, classList).split(/s+/g).forEach(function(klass) { classList = classList.replace(classRE(klass), " ") }) className(this, classList.trim()) }) },
刪除元素中指定的類 name 。如果不傳遞參數,則將 className 屬性置為空,也即刪除所有樣式類。
classList = className(this) funcArg(this, name, idx, classList).split(/s+/g).forEach(function(klass) { classList = classList.replace(classRE(klass), " ") }) className(this, classList.trim())
這是的 classList 依然是全局變量,但是接收的是當前元素的當前樣式類字符串(為什么不用局部變量呢?)。
參數 name 依然可以為函數或者固定值,因此用 funcArg 來處理,然后用空白分割成數組,再遍歷得到單個樣式類,調用 replace 方法,如果 classList 中能匹配到這個類,則將匹配的字符串替換成空格,這樣就達到了刪除的目的。
最后,用 trim 將 classList 的頭尾空格去掉,調用 className 方法,重新給當前元素的 className 賦值。
.toggleClass()toggleClass: function(name, when) { if (!name) return this return this.each(function(idx) { var $this = $(this), names = funcArg(this, name, idx, className(this)) names.split(/s+/g).forEach(function(klass) { (when === undefined ? !$this.hasClass(klass) : when) ? $this.addClass(klass): $this.removeClass(klass) }) }) },
切換樣式類,如果樣式類不存在,則增加樣式類,如果存在,則刪除樣式類。
toggleClass 接收兩個參數,name 是需要切換的類名, when 是指定切換的方法,如果 when 為 true ,則增加樣式類,為 false ,則刪除樣式類。when 不一定要為 Boolean 類型。
這個方法跟 toggle 方法的邏輯參不多,只不過調用的方法變成 addClass 和 removeClass ,可以參考 toggle 的實現,不用過多分析。
系列文章讀Zepto源碼之代碼結構
讀 Zepto 源碼之內部方法
讀Zepto源碼之工具函數
讀Zepto源碼之神奇的$
讀Zepto源碼之集合操作
讀Zepto源碼之集合元素查找
讀Zepto源碼之操作DOM
參考MDN: display
Default CSS Display Values for Different HTML Elements
SVGAnimatedString.baseVal
獲取元素CSS值之getComputedStyle方法熟悉
Array.prototype.some()
License最后,所有文章都會同步發送到微信公眾號上,歡迎關注,歡迎提意見:
作者:對角另一面
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87042.html
摘要:所以模塊依賴于模塊,在引入前必須引入模塊。原有的方法分析見讀源碼之樣式操作方法首先調用原有的方法,將元素顯示出來,這是實現動畫的基本條件。如果沒有傳遞,或者為值,則表示不需要動畫,調用原有的方法即可。 fx 模塊提供了 animate 動畫方法,fx_methods 利用 animate 方法,提供一些常用的動畫方法。所以 fx_methods 模塊依賴于 fx 模塊,在引入 fx_m...
摘要:如果偽類的參數不可以用轉換,則參數為字符串,用正則將字符串前后的或去掉,再賦值給最后執行回調,將解釋出來的參數傳入回調函數中,將執行結果返回。重寫的方法,改過的調用的是方法,在回調函數中處理大部分邏輯。 Selector 模塊是對 Zepto 選擇器的擴展,使得 Zepto 選擇器也可以支持部分 CSS3 選擇器和 eq 等 Zepto 定義的選擇器。 在閱讀本篇文章之前,最好先閱讀《...
摘要:讀源碼系列文章已經放到了上,歡迎源碼版本本文閱讀的源碼為改寫原有的方法模塊改寫了以上這些方法,這些方法在調用的時候,會為返回的結果添加的屬性,用來保存原來的集合。方法的分析可以看讀源碼之模塊。 Stack 模塊為 Zepto 添加了 addSelf 和 end 方法。 讀 Zepto 源碼系列文章已經放到了github上,歡迎star: reading-zepto 源碼版本 本文閱讀的...
摘要:模塊是為解決移動版加載圖片過大過多時崩潰的問題。因為沒有處理過這樣的場景,所以這部分的代碼解釋不會太多,為了說明這個問題,我翻譯了這篇文章作為附文怎樣處理移動端對圖片資源的限制,更詳細地解釋了這個模塊的應用場景。 assets 模塊是為解決 Safari 移動版加載圖片過大過多時崩潰的問題。因為沒有處理過這樣的場景,所以這部分的代碼解釋不會太多,為了說明這個問題,我翻譯了《How to...
摘要:模塊基于上的事件的封裝,利用屬性,封裝出系列事件。這個判斷需要引入設備偵測模塊。然后是監測事件,根據這三個事件,可以組合出和事件。其中變量對象和模塊中的對象的作用差不多,可以先看看讀源碼之模塊對模塊的分析。 Gesture 模塊基于 IOS 上的 Gesture 事件的封裝,利用 scale 屬性,封裝出 pinch 系列事件。 讀 Zepto 源碼系列文章已經放到了github上,歡...
閱讀 2742·2021-11-24 10:23
閱讀 1153·2021-11-17 09:33
閱讀 2503·2021-09-28 09:41
閱讀 1409·2021-09-22 15:55
閱讀 3641·2019-08-29 16:32
閱讀 1903·2019-08-29 16:25
閱讀 1056·2019-08-29 11:06
閱讀 3421·2019-08-29 10:55