Bootstrap affix 源码解读
许多情况下我们都需要去处理那种导航在页面滑动了一定的距离然后固定导航的需求,然后有可能会要求在滚动到距询问多少距离的时候又不固定导航。Bootstrap affix就是为此设计的。
功能需求:
- 当页面滚动到一定距离即固定住元素
- 当页面滚动到距离底部一定距离的时候不再固定元素而是绝对定位元素
思路分三个阶段:
- 当用户没有滚动到阀值的时候是一个类affix-top
- 当用户滚动到阀值的时候类改为affix,然后样式化这个类比如写position:fixed
- 当用户到达底部阀值的时候元素类改为affix-bottom,然后样式化这个类比如position:absolute
当发现固定部分在滚动有抖动现象需要给body
设置position:relative**。
名词解释:
视窗高度:所看到的高度不包括可滚动的距离
内容高度:所看到的高度加上可滚动的距离
首先知道CSS定位position
属性:
引用自MDN css position
所谓的
positioned element
即定位为relative,absolute,fixed或者sticky。
- fixed: 元素相对于视窗的定位。不随滚动条滚动。
- absolue: 元素相对于最近有定位父级元素的定位,如果元素有外边距则会增加进
offset
位移属性里面,会随滚动条滚动。- relative: 相对于元素本身本来的位置的定位。
然后需要知道的是关于jQuery
的offset
函数,$(elem).offset()
获得元素在文档中的位移值默认输出{top: top值, left: left值}
。
元素的position属性所导致的值的不同:
- 当元素为
relative
或者不设置的时候, 当页面滚动的时候,$(elem).offset()
值保持不变。 - 当元素为
absolute
的时候, 当页面滚动的时候,$(elem).offset()
值保持不变,因为他是随着最近的有定位的父级元素滚动的。 - 当元素为
fixed
的时候,当页面滚动的时候,$(elem).offset()
值为元素的top
值加上滚动容器滚动的距离。
Bootstrap affix源码解读如下:
构造函数
|
|
首先是获得滚动容器this.$target
,还有作用的元素this.$element
,this.affixed
为目标元素的定位的状态值为top, bottom, false, null,this.unpin
指的是当滚动到底部阀值的时候的位移值为null或者元素的位移值减去滚动容器(默认为window)滚动的距离。this.pinnedOffset
同this.unpin
。
事件代理目标滚动容器(默认为window)的滚动事件和点击事件。
检查元素状态
|
|
当offsetTop'有值并且固定的状态为
top则判断
scrollTop < offsetTop滚动距离是否小于顶部阀值,是则返回
top否则返回
false`。
如果固定状态为bottom
并且offsetTop
有值则判断scrollTop
滚动的距离加上相对位移值this.unpin
和元素的绝对位移值position.top
,若小于或等于则返回false
否则返回bottom
。
若offsetTop
为空则计算滚动距离加上滚动窗口的视窗高度和容器的总高度scrollHeight
(即包括可滚动距离和视窗的高度)的值减去offsetBottom
底部阀值作对比若小则返回false
否则返回bottom
。
接下来判断是否是初始化,设置colliderTop
和colliderHeight
,当是第一次渲染的时候,分别为滚动的距离和目标滚动容器的视窗高度,否则分别为目标元素的绝对位移和目标元素的高度值。
如果传进来的顶部绝对位移值不为空并且滚动距离小于传进来的顶部阀值则返回top
。
如果传进来的底部阀值不为空并且colliderTop
和colliderHeight
的和大于或等于目标容器的总高度减去传进来的底部阀值则返回bottom
。
即当元素一直滚动到大于滚动的阀值的时候返回bottom
, 默认返回false
即处于affix
状态。
|
|
当为affix-bottom
状态这个是难点
当为bottom的时候posiiton.top为scrollHeight - height - offsetBottom,this.unpin是position.top - scrollTop
,当offsetTop
不为空则判断是向下滚动还是向上滚动若向上滚动则有可能会进入affix
状态,若向下滚动则是affix-bottom
状态。
当offsetTop
设置为空的时候,比较目标窗口滚动的距离+目标窗口的视窗高度和目标容器的内容高度(包括滚动距离)减去底部阀值若小于则是在affix
状态否则进入底部状态并设置类affix-bottom
。
获得锁定状态的位移值
|
|
获取锁定状态的值,当即将进入offsetBottom
阀值的时候触发。这个会获取在affix
状态进入affix-bottom
的时候的定值,即元素仍然为affix
状态的时候的阀值。若目标元素在affix
状态定位为fixed
则此值为CSS类affix-fixed
设定的fixed
状态的top
值。
检测位置
|
|
若元素不可见则返回,这里的目标容器的高度是取的document
和document.body
之间的最大值。
获取选项中的offset
变量,若为非对象则offsetBottom
,offsetTop
和offset
相同。
若offset
中的offsetTop
和offsetBottom
为函数则执行函数,这里是相当的有用的地方。
这里若设置的offset
为对象如offset: { bottom: 30 }
则一开始就处在affix
的状态。
获得affix
值,this.getState(scrollHeight, height, offsetTop, offsetBottom)
。
当this.affixed
不等于affix
值的时候,如果this.unpin
不为空则去除top
值。重置的意思。affixType
即为元素所处状态的类型若是在顶部阀值内则是affix-top
类,若触发则是affix
,若
这是自己合成了事件比如affix-top.bs.affix
这样的事件,也就是说你可以自定义这个事件,在里面进行一些操作当滚动的时候,如果在自定义的函数里面return false
即e.isDefaultPrevented
为true, if (e.isDefaultPrevented()) return
将不会继续执行下去。
例如可以这样写:
|
|
this.affixed
赋值为重新获取的状态值, 若是到达底部阀值则赋值this.unpin
即为获取即将进入affix-bottom
状态的位移值。
然后为目标元素增加对应的目标元素粘滞状态的类并触发诸如affix.bs.affix
的事件。
如果是到达底部的阀值即affix
为bottom
则设置目标元素的csstop
值为目标容器的内容高度(包含滚动距离)减去目标元素的高度减去底部阀值并设置元素为相对定位relative
。
if (this.unpin != null) this.$element.css('top', '')
若元素是从affix-bottom
状态进入affix
则去除元素的top
值。
|
|
当点击滚动条的时候触发。
这里从代码可以发现一个问题,当滚动到底部阀值的时候:
|
|
会给目标元素增加position: relative
这样的定位属性,但是当回到affix
状态的时候,还会带着position: relative
这个属性,显然是不对的搜索了下bootstrap issues。但是这个有问题,如果代码改为
|
|
在状态切换的时候会出现页面抖动的现象!!
Todolist:
- 当滚动到底部阀值的时候,元素会被设置成
position: relative
,当回到affix
状态的时候,这个affix
状态的样式类比如写成position: fixed
就便无法起作用。