固钉组件的作用是将页面元素固定在可视区域之内。
需求
固钉的基本功能就是将元素固定在页面的特定位置,即使存在滚动条的情况下,固钉元素仍然可以固定在设定的位置。
这里位置固定所指的参考有两种情况:
- 元素相对窗口固定位置。
- 元素相对于父容器固定位置。
常规情况下,我们使用的是元素相对于窗口位置固定,这里直接使用fixed布局就可以了,但是对于第二种需求就不容易实现了,这也是固钉组件主要实现的功能。
预备知识
pageYOffset pageXOffset
pageYOffset pageXOffset是window的属性,表示整个页面滚动过的值,可以使用body元素的scrollTop属性替代。
scrollTop scrollLeft
scrollTop scrollLeft是Element的属性,表示一个具有属性的元素滚动过的值。
offsetTop offsetLeft
要想解释offsetTop 以及 offsetLeft的值,需要理解offsetParent的概念。
offsetParent可以返回距离当前元素最近的采用定位(position属性值为fixed、relative或者absolute)祖先元素。
如果祖先元素中没有采用定位的元素,则返回body对象。
offsetTop/offsetLeft返回值表示当前元素上边缘/左边缘距离offsetParent返回元素的距离的数值,单位是像素。
总结:scroll是与滚动相关的属性,offset是与offsetParent相关的属性值
innerHeight
innerHeight为window的属性,指窗口的大小(不包含工具条与滚动条)。
clientHeight
clientHeight这个属性是只读属性,对于没有定义CSS或者内联布局盒子的元素为0,同时它是元素内部的高度(单位像素),包含内边距,但不包括水平滚动条、边框和外边距。
clientHeight 可以通过 CSS height + CSS padding - 水平滚动条高度.
offsetHeight
在IE6,IE7,IE8以及最新的的FF, Chrome中,在元素上都是offsetHeight = clientHeight + 滚动条 + 边框。
scrollHeight
这个高度与滚动条无关,是内容的实际高度。
计算方式 :scrollHeight = topPadding + bottomPadding + 内容margix box的高度。
总结:clientHeight与scrollHeight与滚动条有关,前者与设置的height有关,后者与元素所占据的实际高度有关
fixed布局
fixed布局是我们通常采用的实现元素固定在窗口的特定位置,如下图所示:
但是如果我们想实现基于任意父容器的固钉呢?即使其固定在父容器的指定位置,而不是单纯的固定在窗口的指定位置,如下图所示:
实现原理
其实实现第二种需求的固钉仍然是基于fixed布局的,只不过我们这里加入的事件监听,动态的设置元素的top/bottom等值,以在视觉效果上实现相对于父元素的位置固定。
事件列表
events = [
'resize',
'scroll',
'touchstart',
'touchmove',
'touchend',
'pageshow',
'load',
];
固钉组件组件要监听如上列表中列出的事件,由以上事件触发位置更新。
注意:这里监听事件的元素应该是固钉的容器元素target,若不指定target,会默认为body。
位置更新
以实现距离容器组件顶部为settingTop px为例,说明何时进行位置更新,以及如何位置更新。
const scrollTop = getScrollTop(targetNode);
const elemOffset = getOffset(affixNode, targetNode);
//elemOffset表示固钉的位置,包括父容器滚动过的值
const targetRect = getTargetRect(targetNode);
//targetRect是基于getBoundingClientRect得到的元素位置信息
//getBoundingClientRect方法返回元素的大小及其相对于视口的位置
if (scrollTop > elemOffset.top - settingTop) {
// Fixed Top
const width = elemOffset.width;
const top = targetRect.top + settingTop;
//即根据父容器的位置和设置的参数确定固钉的实际位置。
this.setAffixStyle(e, {
position: 'fixed',
top,
left: targetRect.left + elemOffset.left,
width,
});
}
这里有一个问题,即该如何去理解判断条件呢?
scrollTop > elemOffset.top - settingTop
其实可以将判断条件改写为:
elemOffset.top - scrollTop < settingTop
即页面向上滚动之后elemOffset.top - scrollTop(即固钉相对于父元素顶部的距离)小于预设值settingTop,发生位置更新。
同理,如果设置的参数是settingBottom,也可以以同样的思路分析。
总结
固钉实现的思路仍然是基于fixed布局,只不过我们为了实现相对于任意父元素的位置固定,引入了事件监听以及位置更新。理解位置更新的机制,关键是理解本文中涉及到的各种距离到底指的是什么。