摘要:獲得當前元素相對于的位置。返回一個對象含有和當給定一個含有和屬性對象時,使用這些值來對集合中每一個元素進行相對于的定位。獲取對象集合中第一個元素相對于其的位置。結尾以上就是中與偏移相關的幾個的解析,歡迎指出其中的問題和有錯誤的地方。
前言
這篇文章主要想說一下Zepto中與"偏移"相關的一些事,很久很久以前,我們經常會使用offset、position、scrollTop、scrollLeft等方式去改變元素的位置,他們之間有什么區別,是怎么實現的呢?接下來我們一點點去扒開他們的面紗。
原文鏈接
源碼倉庫
offsetParentoffset、position兩個api內部的實現都依賴offsetParent方法,我們先看一下它是怎么一回事。
找到第一個定位過的祖先元素,意味著它的css中的position 屬性值為“relative”, “absolute” or “fixed” #offsetParent
我們都知道css屬性position用于指定一個元素在文檔中的定位方式,其初始值是static, css3中甚至還增加了sticky等屬性,不過目前貌似瀏覽器幾乎還未支持。
看一下這個例子
html
css
javascript
console.log($(".child3").offsetParent()) // child1 console.log(document.querySelector(".child3").offsetParent) // child1
既然原生已經有了一個offsetParentmdn offsetParent屬性供我們使用,為什么Zepto還要自己實現一個呢?其實他們之間還是有些不同的,比如同樣是上面的例子,如果child3的display屬性設置為了none,原生的offsetParent返回的是null,但是Zepto返回的是包含body元素的Zepto對象。
源碼分析
offsetParent: function () { return this.map(function () { var parent = this.offsetParent || document.body while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static") parent = parent.offsetParent return parent }) }
實現邏輯還是比較簡單,通過map方法遍歷當前選中的元素集合,結果是一個數組,每個項即是元素的最近的定位祖先元素。
首先通過offsetParent原生DOM屬性去獲取定位元素,如果沒有默認是body節點,這里其實就能解釋前面的child3設置為display:none,原生返回null,但是Zepto得到的是body了
var parent = this.offsetParent || document.body
再通過一個while循環如果
parent元素存在
parent元素不是html或者body元素
parent元素的display屬性是static,則再次獲取parent屬性的offsetParent再次循環。
offset獲得當前元素相對于document的位置。返回一個對象含有: top, left, width和height當給定一個含有left和top屬性對象時,使用這些值來對集合中每一個元素進行相對于document的定位。
offset() ? object
offset(coordinates) ? self v1.0+
offset(function(index, oldOffset){ ... }) ?
#offset
源碼
offset: function (coordinates) { if (coordinates) return this.each(function (index) { var $this = $(this), coords = funcArg(this, coordinates, index, $this.offset()), parentOffset = $this.offsetParent().offset(), props = { top: coords.top - parentOffset.top, left: coords.left - parentOffset.left } if ($this.css("position") == "static") props["position"] = "relative" $this.css(props) }) if (!this.length) return null if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0])) return { top: 0, left: 0 } var obj = this[0].getBoundingClientRect() return { left: obj.left + window.pageXOffset, top: obj.top + window.pageYOffset, width: Math.round(obj.width), height: Math.round(obj.height) } }
和Zepto中的其他api類似遵循get one, set all原則,我們先來看看獲取操作是如何實現的。
if (!this.length) return null if (document.documentElement !== this[0] && !$.contains(document.documentElement, this[0])) return { top: 0, left: 0 } var obj = this[0].getBoundingClientRect() return { left: obj.left + window.pageXOffset, top: obj.top + window.pageYOffset, width: Math.round(obj.width), height: Math.round(obj.height) }
!this.length如果當前沒有選中元素,自然就沒有往下走的必要了,直接return掉
當前選中的集合中不是html元素,并且也不是html節點子元素。直接返回{ top: 0, left: 0 }
接下來的邏輯才是重點。首先通過getBoundingClientRect獲取元素的大小及其相對于視口的位置,再通過pageXOffset、pageYOffset獲取文檔在水平和垂直方向已滾動的像素值,相加既得到我們最后想要的值。
再看設置操作如何實現之前,先看下面這張圖,或許會有助于理解
if (coordinates) return this.each(function(index) { var $this = $(this), coords = funcArg(this, coordinates, index, $this.offset()), parentOffset = $this.offsetParent().offset(), props = { top: coords.top - parentOffset.top, left: coords.left - parentOffset.left } if ($this.css("position") == "static") props["position"] = "relative" $this.css(props) })
還是那個熟悉的模式,熟悉的套路,循環遍歷當前元素集合,方便挨個設置,通過funcArg函數包裝一下,使得入參既可以是函數,也可以是其他形式。
通過上面那張圖,我們應該可以很清晰的看出,如果要將子元素設置到傳入的coords.left的位置,那其實
父元素(假設父元素是定位元素)相對文檔的左邊距(parentOffset.left)
子元素相對父元素的左邊距(left)
相加得到的就是入參coords.left
那再做個減法,就得到我們最終通過css方法需要設置的left和top值啦。
需要注意的是如果元素的定位屬性是static,則會將其改為relative定位,相對于其正常文檔流來計算。
position獲取對象集合中第一個元素相對于其offsetParent的位置。
position: function() { if (!this.length) return var elem = this[0], offsetParent = this.offsetParent(), offset = this.offset(), parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset() offset.top -= parseFloat($(elem).css("margin-top")) || 0 offset.left -= parseFloat($(elem).css("margin-left")) || 0 parentOffset.top += parseFloat($(offsetParent[0]).css("border-top-width")) || 0 parentOffset.left += parseFloat($(offsetParent[0]).css("border-left-width")) || 0 return { top: offset.top - parentOffset.top, left: offset.left - parentOffset.left } }
先看一個例子
html
css
.parent{ width: 400px; height: 400px; border: solid 1px red; padding: 10px; margin: 10px; position: relative; } .child{ width: 200px; height: 200px; border: solid 1px green; padding: 20px; margin: 20px; }
console.log($(".child").position()) // {top: 10, left: 10}
下面分別是父子元素的盒模型以及標注了需要獲取的top的值
接下來我們來看它怎么實現的吧,come on!!!
第一步
var offsetParent = this.offsetParent(), // Get correct offsets // 獲取當前元素相對于document的位置 offset = this.offset(), // 獲取第一個定位祖先元素相對于document的位置,如果是根元素(html或者body)則為0, 0 parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset()
第二步
// 相對于第一個定位祖先元素的位置關系不應該包括margin的舉例,所以減去 offset.top -= parseFloat($(elem).css("margin-top")) || 0 offset.left -= parseFloat($(elem).css("margin-left")) || 0
第三步
// 祖先定位元素加上border的寬度 parentOffset.top += parseFloat($(offsetParent[0]).css("border-top-width")) || 0 parentOffset.left += parseFloat($(offsetParent[0]).css("border-left-width")) || 0
第四步
// 相減即結果 return { top: offset.top - parentOffset.top, left: offset.left - parentOffset.left }
整體思路還是用當前元素相對于文檔的位置減去第一個定位祖先元素相對于文檔的位置,但有兩點需要注意的是position這個api要計算出來的值,不應該包括父元素的border長度以及子元素的margin空間長度。所以才會有第二和第三步。
scrollLeft獲取或設置頁面上的滾動元素或者整個窗口向右滾動的滾動距離。
scrollLeft: function (value) { if (!this.length) return var hasScrollLeft = "scrollLeft" in this[0] if (value === undefined) return hasScrollLeft ? this[0].scrollLeft : this[0].pageXOffset return this.each(hasScrollLeft ? function () { this.scrollLeft = value } : function () { this.scrollTo(value, this.scrollY) }) }
首先判斷當前選中的元素是否支持scrollLeft特性。
如果value沒有傳進來,又支持hasScrollLeft特性,就返回第一個元素的hasScrollLeft值,不支持的話返回第一個元素的pageXOffset值。
pageXOffset是scrollX的別名,而其代表的含義是返回文檔/頁面水平方向滾動的像素值
傳進來了value就是設置操作了,支持scrollLeft屬性,就直接設置其值即可,反之需要用到scrollTo,當然設置水平方向的時候,垂直方向還是要和之前的保持一致,所以傳入了scrollY作為
scrollTop獲取或設置頁面上的滾動元素或者整個窗口向下滾動的距離。
scrollTop: function(value) { if (!this.length) return var hasScrollTop = "scrollTop" in this[0] if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset return this.each(hasScrollTop ? function() { this.scrollTop = value } : function() { this.scrollTo(this.scrollX, value) }) },
可以看出基本原理和模式與scrollLeft一致,就不再一一解析。
結尾以上就是Zepto中與"偏移"相關的幾個api的解析,歡迎指出其中的問題和有錯誤的地方。參考
讀Zepto源碼之屬性操作
scrollTo
scrollLeft
pageXOffset
...
文章記錄ie模塊
Zepto源碼分析之ie模塊(2017-11-03)
data模塊
Zepto中數據緩存原理與實現(2017-10-03)
form模塊
zepto源碼分析之form模塊(2017-10-01)
zepto模塊
這些Zepto中實用的方法集(2017-08-26)
Zepto核心模塊之工具方法拾遺 (2017-08-30)
看zepto如何實現增刪改查DOM (2017-10-2)
Zepto這樣操作元素屬性(2017-11-13)
向Zepto學習關于"偏移"的那些事(2017-12-10)
event模塊
mouseenter與mouseover為何這般糾纏不清?(2017-06-05)
向zepto.js學習如何手動觸發DOM事件(2017-06-07)
誰說你只是"會用"jQuery?(2017-06-08)
ajax模塊
原來你是這樣的jsonp(原理與具體實現細節)(2017-06-11)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/92204.html
摘要:作用范圍這樣都會應用上模式。如果你僅想在一個函數中使用的特性檢查對象中的重復鍵這段代碼會拋出一個錯誤因為出現了兩次。未聲明變量在模式下,給未聲明的變量賦值會拋出的警告。重復的參數注意出現了兩次,因此會拋出一個錯誤。 use strict作用范圍 // file.js use strict function doStuff(){ // use strict is enabled ...
小編寫這篇文章的主要目的,是給大家進行一個解答,解答關于標準庫times的使用方式一些,具體的操作,下面就給大家進行一個解答。 1、time庫 時間戳(timestamp)的方式:通常來說,時間戳表示的是從1970年1月1日00:00:00開始按秒計算的偏移量 結構化時間(struct_time)方式:struct_time元組共有9個元素 格式化的時間字符串(format_strin...
閱讀 1642·2021-09-02 09:55
閱讀 1116·2019-08-30 13:19
閱讀 1404·2019-08-26 13:51
閱讀 1454·2019-08-26 13:49
閱讀 2385·2019-08-26 12:13
閱讀 464·2019-08-26 11:52
閱讀 1911·2019-08-26 10:58
閱讀 3091·2019-08-26 10:19