We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
这篇文章主要想说一下Zepto中与"偏移"相关的一些事,很久很久以前,我们经常会使用offset、position、scrollTop、scrollLeft等方式去改变元素的位置,他们之间有什么区别,是怎么实现的呢?接下来我们一点点去扒开他们的面纱。
offset
position
scrollTop
scrollLeft
原文链接
源码仓库
offset、position两个api内部的实现都依赖offsetParent方法,我们先看一下它是怎么一回事。
offsetParent
找到第一个定位过的祖先元素,意味着它的css中的position 属性值为“relative”, “absolute” or “fixed” #offsetParent
我们都知道css属性position用于指定一个元素在文档中的定位方式,其初始值是static, css3中甚至还增加了sticky等属性,不过目前貌似浏览器几乎还未支持。
看一下这个例子
html
<div class="wrap"> <div class="child1"> <div class="child2"> <div class="child3"></div> </div> </div> </div>
css
<style> .wrap{ width: 400px; height: 400px; border: solid 1px red; } .child1{ width: 300px; height: 300px; border: solid 1px green; position: relative; padding: 10px; } .child2{ width: 200px; height: 200px; border: solid 1px bisque; } .child3{ width: 100px; height: 100px; border: solid 1px goldenrod; position: absolute; left: 0; top: 0; } </style>
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方法遍历当前选中的元素集合,结果是一个数组,每个项即是元素的最近的定位祖先元素。
map
首先通过offsetParent原生DOM属性去获取定位元素,如果没有默认是body节点,这里其实就能解释前面的child3设置为display:none,原生返回null,但是Zepto得到的是body了
display:none
var parent = this.offsetParent || document.body
再通过一个while循环如果
while
body
static
获得当前元素相对于document的位置。返回一个对象含有: top, left, width和height
当给定一个含有left和top属性对象时,使用这些值来对集合中每一个元素进行相对于document的定位。
#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原则,我们先来看看获取操作是如何实现的。
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掉
!this.length
当前选中的集合中不是html元素,并且也不是html节点子元素。直接返回{ top: 0, left: 0 }
{ 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的位置,那其实
coords.left
那再做个减法,就得到我们最终通过css方法需要设置的left和top值啦。
需要注意的是如果元素的定位属性是static,则会将其改为relative定位,相对于其正常文档流来计算。
relative
获取对象集合中第一个元素相对于其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 } }
先看一个例子
<div class="parent"> <div class="child"></div> </div>
.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空间长度。所以才会有第二和第三步。
border
margin
获取或设置页面上的滚动元素或者整个窗口向右滚动的滚动距离。
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值。
hasScrollLeft
pageXOffset
pageXOffset是scrollX的别名,而其代表的含义是返回文档/页面水平方向滚动的像素值
传进来了value就是设置操作了,支持scrollLeft属性,就直接设置其值即可,反之需要用到scrollTo,当然设置水平方向的时候,垂直方向还是要和之前的保持一致,所以传入了scrollY作为
value
scrollY
获取或设置页面上的滚动元素或者整个窗口向下滚动的距离。
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
...
ie模块
data模块
form模块
zepto模块
event模块
ajax模块
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
原文链接
源码仓库
offsetParent
offset
、position
两个api内部的实现都依赖offsetParent
方法,我们先看一下它是怎么一回事。我们都知道css属性position用于指定一个元素在文档中的定位方式,其初始值是static, css3中甚至还增加了sticky等属性,不过目前貌似浏览器几乎还未支持。
看一下这个例子
html
css
javascript
既然原生已经有了一个offsetParentmdn offsetParent属性供我们使用,为什么Zepto还要自己实现一个呢?其实他们之间还是有些不同的,比如同样是上面的例子,如果child3的display属性设置为了none,原生的offsetParent返回的是null,但是Zepto返回的是包含body元素的Zepto对象。
源码分析
实现逻辑还是比较简单,通过
map
方法遍历当前选中的元素集合,结果是一个数组,每个项即是元素的最近的定位祖先元素。首先通过
offsetParent
原生DOM属性去获取定位元素,如果没有默认是body节点,这里其实就能解释前面的child3设置为display:none
,原生返回null,但是Zepto得到的是body了再通过一个
while
循环如果html
或者body
元素static
,则再次获取parent属性的offsetParent
再次循环。offset
#offset
源码
和Zepto中的其他api类似遵循
get one, set all
原则,我们先来看看获取操作是如何实现的。!this.length
如果当前没有选中元素,自然就没有往下走的必要了,直接return掉当前选中的集合中不是
html
元素,并且也不是html
节点子元素。直接返回{ top: 0, left: 0 }
接下来的逻辑才是重点。首先通过getBoundingClientRect获取元素的大小及其相对于视口的位置,再通过pageXOffset、pageYOffset获取文档在水平和垂直方向已滚动的像素值,相加既得到我们最后想要的值。
再看设置操作如何实现之前,先看下面这张图,或许会有助于理解
还是那个熟悉的模式,熟悉的套路,循环遍历当前元素集合,方便挨个设置,通过funcArg函数包装一下,使得入参既可以是函数,也可以是其他形式。
通过上面那张图,我们应该可以很清晰的看出,如果要将子元素设置到传入的
coords.left
的位置,那其实coords.left
那再做个减法,就得到我们最终通过css方法需要设置的left和top值啦。
需要注意的是如果元素的定位属性是
static
,则会将其改为relative
定位,相对于其正常文档流来计算。position
先看一个例子
html
css
下面分别是父子元素的盒模型以及标注了需要获取的top的值
接下来我们来看它怎么实现的吧,come on!!!
第四步
整体思路还是用当前元素相对于文档的位置减去第一个定位祖先元素相对于文档的位置,但有两点需要注意的是position这个api要计算出来的值,不应该包括父元素的
border
长度以及子元素的margin
空间长度。所以才会有第二和第三步。scrollLeft
首先判断当前选中的元素是否支持scrollLeft特性。
如果value没有传进来,又支持
hasScrollLeft
特性,就返回第一个元素的hasScrollLeft
值,不支持的话返回第一个元素的pageXOffset
值。pageXOffset是scrollX的别名,而其代表的含义是返回文档/页面水平方向滚动的像素值
传进来了
value
就是设置操作了,支持scrollLeft
属性,就直接设置其值即可,反之需要用到scrollTo,当然设置水平方向的时候,垂直方向还是要和之前的保持一致,所以传入了scrollY
作为scrollTop
可以看出基本原理和模式与
scrollLeft
一致,就不再一一解析。结尾
参考
读Zepto源码之属性操作
scrollTo
scrollLeft
pageXOffset
...
文章记录
ie模块
data模块
form模块
zepto模块
event模块
ajax模块
The text was updated successfully, but these errors were encountered: