4 种滚动吸顶实现方式的比较


Posted in Javascript onApril 09, 2019

前言

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

offsetTop

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

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

或许写这个代码的人没有注意到“定位父级”这个这个附属条件。
后来在项目中总会遇到滚动吸顶的效果需要实现,现在我将我知道的 4 种滚动吸顶实现方式做详细介绍。

目录

  1. 使用 position:sticky 实现
  2. 使用 JQuery 的 offset().top 实现
  3. 使用原生的 offsetTop 实现
  4. 使用 obj.getBoundingClientRect().top 实现

望给个 star 支持一下。

四种实现方式

我们先看一下效果图:

4 种滚动吸顶实现方式的比较

一、使用 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 中看看这个属性的兼容性:

4 种滚动吸顶实现方式的比较

可以看出这个属性的兼容性并不是很好,因为这个 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 元素的左内边框之间的像素距离;

注意事项

  1. 所有偏移量属性都是只读的;
  2. 如果给元素设置了 display:none,则它的偏移量属性都为 0;
  3. 每次访问偏移量属性都需要重新计算(保存变量);
  4. 在使用的时候可能出现 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()。

以上所述是小编给大家介绍的4种滚动吸顶实现方式的比较详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
jQuery focus和blur事件的应用详解
Jan 26 Javascript
JS 日期与时间戮相互转化的简单实例
Jun 22 Javascript
jquery实现网站列表切换效果的2种方法
Aug 12 Javascript
JavaScript数组去重的6个方法
Jan 21 Javascript
vue2 前后端分离项目ajax跨域session问题解决方法
Apr 27 Javascript
详解Angular2学习笔记之Html属性绑定
Jan 03 Javascript
微信小程序实现流程进度的图样式功能
Jan 16 Javascript
Vue登录注册并保持登录状态的方法
Aug 17 Javascript
详解为生产环境编译Angular2应用的方法
Dec 10 Javascript
Vue实现渲染数据后控制滚动条位置(推荐)
Dec 09 Javascript
继承行为在 ES5 与 ES6 中的区别详解
Dec 24 Javascript
vue大型项目之分模块运行/打包的实现
Sep 21 Javascript
vue响应式系统之observe、watcher、dep的源码解析
Apr 09 #Javascript
浅谈发布订阅模式与观察者模式
Apr 09 #Javascript
vue使用keep-alive保持滚动条位置的实现方法
Apr 09 #Javascript
浅谈JavaScript闭包
Apr 09 #Javascript
使用Three.js实现太阳系八大行星的自转公转示例代码
Apr 09 #Javascript
webpack4实现不同的导出类型
Apr 09 #Javascript
Vue中使用create-keyframe-animation与动画钩子完成复杂动画
Apr 09 #Javascript
You might like
php 常用类整理
2009/12/23 PHP
php导出csv数据在浏览器中输出提供下载或保存到文件的示例
2014/04/24 PHP
Ubuntu12下编译安装PHP5.3开发环境
2015/03/27 PHP
php-app开发接口加密详解
2018/04/18 PHP
imagettftext() 失效,不起作用
2021/03/09 PHP
js CSS操作方法集合
2008/10/31 Javascript
JQuery在页面中添加和除移DOM示例代码
2013/06/24 Javascript
前台js对象在后台转化java对象的问题探讨
2013/12/20 Javascript
jquery 显示*天*时*分*秒实现时间计时器
2014/05/07 Javascript
javascript搜索框效果实现方法
2015/05/14 Javascript
c#程序员对TypeScript的认识过程
2015/06/19 Javascript
jQuery实现的分子运动小球碰撞效果
2016/01/27 Javascript
Jquery实现select multiple左右添加和删除功能的简单实例
2016/05/26 Javascript
AngularJS ng-app 指令实例详解
2016/07/30 Javascript
JS用斜率判断鼠标进入DIV四个方向的方法
2016/11/07 Javascript
JS实现间歇滚动的运动效果实例
2016/12/22 Javascript
jQuery中ztree 点击文本框弹出下拉框的实例代码
2017/02/05 Javascript
jQuery 表单序列化实例代码
2017/06/11 jQuery
vue-router beforeEach跳转路由验证用户登录状态
2018/12/26 Javascript
Vue+Koa2+mongoose写一个像素绘板的实现方法
2019/09/10 Javascript
JS控制GIF图片的停止与显示
2019/10/24 Javascript
Python实现动态添加类的属性或成员函数的解决方法
2014/07/16 Python
基于wxpython实现的windows GUI程序实例
2015/05/30 Python
Python整型运算之布尔型、标准整型、长整型操作示例
2017/07/21 Python
Python字符串逆序输出的实例讲解
2019/02/16 Python
在Pytorch中使用样本权重(sample_weight)的正确方法
2019/08/17 Python
python对指定字符串逆序的6种方法(小结)
2020/04/02 Python
Python延迟绑定问题原理及解决方案
2020/08/04 Python
各大浏览器 CSS3 和 HTML5 兼容速查表 图文
2010/04/01 HTML / CSS
html5利用canvas绘画二级树形结构图的示例
2017/09/27 HTML / CSS
美国百货齐全的精品网站,提供美式风格的产品:Overstock.com
2016/07/22 全球购物
英国知名化妆品网站:Revolution Beauty(原TAM Beauty)
2018/02/28 全球购物
ETO男装官方网店:ETO Jeans
2019/02/28 全球购物
揠苗助长教学反思
2014/02/04 职场文书
关于奉献的演讲稿
2014/05/21 职场文书
JavaScript继承的三种方法实例
2021/05/12 Javascript