【前端词典】4 种滚动吸顶实现方式的比较

前言

我入职第二家公司接到的第一个需求就是修复之前外包做的滚动吸顶效果。我当时很纳闷为何一个滚动吸顶会有 bug,后来我查看代码才发现直接用的是 offsetTop 这个属性,而且并没有做兼容性处理。

offsetTop

用于获得当前元素到定位父级( element.offsetParent )顶部的距离(偏移值)。

定位父级 offsetParent 的定义是:与当前元素最近的 position != static 的父级元素。

或许写这个代码的人没有注意到“定位父级”这个这个附属条件。

后来在项目中总会遇到滚动吸顶的效果需要实现,现在我将我知道的 4 种滚动吸顶实现方式做详细介绍。

目录

href="https://zhuanlan.zhihu.com/p/62125575/edit#a01">使用 position:sticky 实现

使用 JQuery 的 offset().top 实现

f="https://zhuanlan.zhihu.com/p/62125575/edit#a03">使用原生的 offsetTop 实现

href="https://zhuanlan.zhihu.com/p/62125575/edit#a04">使用 obj.getBoundingClientRect().top 实现

以上这四种方式你都了解吗?相关代码已上传到 GitHub ,感兴趣的可以 clone 代码到本地运行。望给个 star 支持一下。

四种实现方式

我们先看一下效果图:


一、使用 position:sticky 实现

1、粘性定位是什么?

粘性定位 sticky 相当于相对定位 relative 和固定定位 fixed 的结合;在页面元素滚动过程中,某个元素距离其父元素的距离达到 sticky 粘性定位的要求时;元素的相对定位 relative效果变成固定定位 fixed 的效果。

MDN 传送门

2、如何使用?

使用条件:

父元素不能 overflow:hidden 或者 overflow:auto 属性

必须指定 top、bottom、left、right 4 个值之一,否则只会处于相对定位

父元素的高度不能低于 sticky 元素的高度

sticky 元素仅在其父元素内生效

在需要滚动吸顶的元素加上以下样式便可以实现这个效果:

.sticky { position: -webkit-sticky; position: sticky; top: 0; }

3、这个属性好用吗?

我们先看下在 Can I use 中看看这个属性的兼容性:


可以看出这个属性的兼容性并不是很好,因为这个 API 还只是实验性的属性。不过这个 API 在 IOS 系统的兼容性还是比较好的。

所以我们在生产环境如果使用这个 API 的时候一般会和下面的几种方式结合使用。

二、使用 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,于是乎也就有了第三种方案的兼容性写法。

三、使用原生的 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的值来实现滚动吸顶的效果吗?答案是否定的。

我们一同看看第四种方案。

四、使用 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

}

}

}

offsetTop 和 getBoundingClientRect() 区别

1. getBoundingClientRect():

用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。不包含文档卷起来的部分。

该函数返回一个 object 对象,有6个属性: top, right, buttom, left, width, height。 (在 IE 中,默认坐标从(2,2)开始计算,只返回 top,lef,right,bottom 四个值)

2. offsetTop:

用于获得当前元素到定位父级( 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:

元素在水平方向上占用的空间大小:

offsetWidth = border-left + padding-left + width + padding-right + border-right

offsetHeight:

元素在垂直方向上占用的空间大小:

offsetHeight = border-top + padding-top + height + padding-bottom + border-bottom

注:如果存在垂直滚动条,offsetWidth 也包括垂直滚动条的宽度;如果存在水平滚动条,offsetHeight 也包括水平滚动条的高度;

offsetTop:

元素的上外边框至 offsetParent 元素的上内边框之间的像素距离;

offsetLeft:

元素的左外边框至 offsetParent 元素的左内边框之间的像素距离;

注意事项

所有偏移量属性都是只读的;

如果给元素设置了 display:none,则它的偏移量属性都为 0;

每次访问偏移量属性都需要重新计算(保存变量);

在使用的时候可能出现 DOM 没有初始化,就读取了该属性,这个时候会返回 0;对于这个问题我们需要等到 DOM 元素初始化完成后再执行。

遇到的两个问题

一、吸顶的那一刻伴随抖动

出现抖动的原因是因为:在吸顶元素 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()。

前端词典系列

《前端词典》这个系列会持续更新,每一期我都会讲一个出现频率较高的知识点。希望大家在阅读的过程当中可以斧正文中出现不严谨或是错误的地方,本人将不胜感激;若通过本系列而有所得,本人亦将不胜欣喜。

内容: 前端以及网络相关知识点的介绍并加以实际应用作为辅助。

目的: 这个系列的文章可以对读者起到一点帮助,解开一些迷惑。

希望各位多指点一二,不吝赐教。

如果你觉得我的文章写的还不错,可以关注我或者加我的专栏讨论。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容