我入职第二家公司接到的第一个需求就是修复以前外包作的滚动吸顶效果。我当时很纳闷为什么一个滚动吸顶会有 bug,后来我查看代码才发现直接用的是 offsetTop 这个属性,并且并无作兼容性处理。html
用于得到当前元素到定位父级( element.offsetParent )顶部的距离(偏移值)。前端
定位父级 offsetParent 的定义是:与当前元素最近的 position != static 的父级元素。vue
或许写这个代码的人没有注意到“定位父级”这个这个附属条件。ios
后来在项目中总会遇到滚动吸顶的效果须要实现,如今我将我知道的 4 种滚动吸顶实现方式作详细介绍。git
使用 position:sticky 实现github
使用 JQuery 的 offset().top 实现web
使用原生的 offsetTop 实现浏览器
以上这四种方式你都了解吗?相关代码已上传到 GitHub (https://github.com/wanqihua/The-dictionary-of-front-end-test),感兴趣的能够 clone 代码到本地运行。望给个 star 支持一下。微信
1、使用 position:sticky 实现网络
粘性定位 sticky 至关于相对定位 relative 和固定定位 fixed 的结合;在页面元素滚动过程当中,某个元素距离其父元素的距离达到 sticky 粘性定位的要求时;元素的相对定位 relative 效果变成固定定位 fixed 的效果。
使用条件:
父元素不能 overflow:hidden 或者 overflow:auto 属性
必须指定 top、bottom、left、right 4 个值之一,不然只会处于相对定位
父元素的高度不能低于 sticky 元素的高度
在须要滚动吸顶的元素加上如下样式即可以实现这个效果:
.sticky { position: -webkit-sticky; position: sticky; top: 0; }
咱们先看下在 Can I use 中看看这个属性的兼容性:
能够看出这个属性的兼容性并非很好,由于这个 API 还只是实验性的属性。不过这个 API 在 IOS 系统的兼容性仍是比较好的。
因此咱们在生产环境若是使用这个 API 的时候通常会和下面的几种方式结合使用。
2、使用 JQuery 的 offset().top 实现
咱们知道 JQuery 中封装了操做 DOM 和读取 DOM 计算属性的 API,基于 offset().top 这个 API 和 scrollTop() 的结合,咱们也能够实现滚动吸顶效果。
... window.addEventListener('scroll', self.handleScrollOne); ... handleScrollOne: function() { let self = this; let scrollTop = $('html').scrollTop(); let offsetTop = $('.title_box').offset().top; self.titleFixed = scrollTop > offsetTop; } ...
这样实现当然能够,不过因为 JQuery 慢慢的退出历史的舞台,咱们在代码中尽可能不使用 JQuery 的 API。咱们能够基于 offset().top 的源码本身处理原生 offsetTop。因而乎就有了第三种方式。
scrolloTop() 有兼容性问题,在微信浏览器、IE、某些 firefox 版本中 $('html').scrollTop() 的值会为 0,因而乎也就有了第三种方案的兼容性写法。
3、使用原生的 offsetTop 实现
咱们知道 offsetTop 是相对定位父级的偏移量,假若须要滚动吸顶的元素出现定位父级元素,那么 offsetTop 获取的就不是元素距离页面顶部的距离。
咱们能够本身对 offsetTop 作如下处理:
getOffset: function(obj,direction){ let offsetL = 0; let offsetT = 0; while( obj!== window.document.body && obj !== null ){ offsetL += obj.offsetLeft; offsetT += obj.offsetTop; obj = obj.offsetParent; } if(direction === 'left'){ return offsetL; }else { return offsetT; } }
使用:
... window.addEventListener('scroll', self.handleScrollTwo); ... handleScrollTwo: function() { let self = this; let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; let offsetTop = self.getOffset(self.$refs.pride_tab_fixed); self.titleFixed = scrollTop > offsetTop; } ...
你是否是看出了以上两种方式的一些问题?
咱们必定须要使用 scrollTop-offsetTop 的值来实现滚动吸顶的效果吗?答案是否认的。
咱们一同看看第四种方案。
4、使用 obj.getBoundingClientRect().top 实现
这个 API 能够告诉你页面中某个元素相对浏览器视窗上下左右的距离。
tab 吸顶可使用 obj.getBoundingClientRect().top 代替 scrollTop-offsetTop,代码以下:
// html <div class="pride_tab_fixed" ref="pride_tab_fixed"> <div class="pride_tab" :class="titleFixed == true ? 'isFixed' :''"> // some code </div> </div> // vue export default { data(){ return{ titleFixed: false } }, activated(){ this.titleFixed = false; window.addEventListener('scroll', this.handleScroll); }, methods: { //滚动监听,头部固定 handleScroll: function () { let offsetTop = this.$refs.pride_tab_fixed.getBoundingClientRect().top; this.titleFixed = offsetTop < 0; // some code } } }
用于得到页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。不包含文档卷起来的部分。
该函数返回一个 object 对象,有6个属性: top,right,buttom,left,width,height。(在 IE 中,默认坐标从(2,2)开始计算,只返回 top,lef,right,bottom 四个值)
用于得到当前元素到定位父级( element.offsetParent )顶部的距离(偏移值)。
定位父级 offsetParent 的定义是:与当前元素最近的 position != static 的父级元素。
offsetTop 和 offsetParent 方法相结合能够得到该元素到 body 上边距的距离。代码以下:
getOffset: function(obj,direction){ let offsetL = 0; let offsetT = 0; while( obj!== window.document.body && obj !== null ){ offsetL += obj.offsetLeft; offsetT += obj.offsetTop; obj = obj.offsetParent; } if(direction === 'left'){ return offsetL; }else { return offsetT; } }
元素在水平方向上占用的空间大小:
offsetWidth = border-left + padding-left + width + padding-right + border-right
元素在垂直方向上占用的空间大小:
offsetHeight = border-top + padding-top + height + padding-bottom + border-bottom
注:若是存在垂直滚动条,offsetWidth 也包括垂直滚动条的宽度;若是存在水平滚动条,offsetHeight 也包括水平滚动条的高度;
元素的上外边框至 offsetParent 元素的上内边框之间的像素距离;
元素的左外边框至 offsetParent 元素的左内边框之间的像素距离;
全部偏移量属性都是只读的;
若是给元素设置了 display:none,则它的偏移量属性都为 0;
每次访问偏移量属性都须要从新计算(保存变量);
出现抖动的缘由是由于:在吸顶元素 position 变为 fixed 的时候,该元素就脱离了文档流,下一个元素就进行了补位。就是这个补位操做形成了抖动。
为这个吸顶元素添加一个等高的父元素,咱们监听这个父元素的 getBoundingClientRect().top值来实现吸顶效果,即:
<div class="title_box" ref="pride_tab_fixed"> <div class="title" :class="titleFixed == true ? 'isFixed' :''"> 使用 `obj.getBoundingClientRect().top` 实现 </div> </div>
这个方案就能够解决抖动的 Bug 了。
这个问题是我比较头痛,以前我没有在乎过这个问题。直到有一天我用美团点外卖的时候,我才开始注意这个问题。
当页面往下滚动时,吸顶元素须要等页面滚动中止以后才会出现吸顶效果
缘由:在 ios 系统上不能实时监听 scroll 滚动监听事件,在滚动中止时才触发其相关的事件。
还记得第一种方案中的 position:sticky 吗?这个属性在 IOS6 以上的系统中有良好的兼容性,因此咱们能够区分 IOS 和 Android 设备作两种处理。
IOS 使用 position:sticky,Android 使用滚动监听 getBoundingClientRect().top 的值。
若是 IOS 版本太低呢?这里提供一种思路: window.requestAnimationFrame()。
《前端词典》这个系列会持续更新,每一期我都会讲一个出现频率较高的知识点。但愿你们在阅读的过程中能够斧正文中出现不严谨或是错误的地方,本人将不胜感激;若经过本系列而有所得,本人亦将不胜欣喜。
内容: 前端以及网络相关知识点的介绍并加以实际应用做为辅助。
目的: 这个系列的文章能够对读者起到一点帮助,解开一些迷惑。
但愿各位多指点一二,不吝赐教。
若是你以为个人文章写的还不错,那就关注我呗!