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 相关文章推荐
javascript vvorld 在线加密破解方法
Nov 13 Javascript
jQuery获取当前对象标签名称的方法
Feb 07 Javascript
JavaScript验证电子邮箱的函数
Aug 22 Javascript
跟我学习javascript的函数和函数表达式
Nov 16 Javascript
BootStrap点击下拉菜单项后显示一个新的输入框实现代码
May 16 Javascript
JavaScript 事件流、事件处理程序及事件对象总结
Apr 01 Javascript
在ABP框架中使用BootstrapTable组件的方法
Jul 31 Javascript
微信小程序实现tab切换效果
Nov 21 Javascript
JavaScript面向对象程序设计创建对象的方法分析
Aug 13 Javascript
Phaser.js实现简单的跑酷游戏附源码下载
Oct 26 Javascript
qrcode生成二维码微信长按无法识别问题的解决
Apr 04 Javascript
jquery css实现流程进度条
Mar 26 jQuery
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中的integer类型使用分析
2010/07/27 PHP
Wordpress 相册插件 NextGEN-Gallery 添加目录将中文转为拼音的解决办法
2010/12/29 PHP
PHP判断文件是否存在、是否可读、目录是否存在的代码
2012/10/03 PHP
关于js和php对url编码的处理方法
2014/03/04 PHP
PHP正则替换函数preg_replace和preg_replace_callback使用总结
2014/09/22 PHP
浅谈PHP值mysql操作类
2016/06/29 PHP
PHP命令空间namespace及use的用法小结
2017/11/27 PHP
laravel 框架结合关联查询 when()用法分析
2019/11/22 PHP
LazyLoad 延迟加载(按需加载)
2010/05/31 Javascript
javaScript 利用闭包模拟对象的私有属性
2011/12/29 Javascript
页面右下角弹出提示框示例代码js版
2013/08/02 Javascript
jQuery实现列表内容的动态载入特效
2015/08/08 Javascript
详解JavaScript编程中正则表达式的使用
2015/10/25 Javascript
window.location.hash知识汇总
2015/11/09 Javascript
json对象与数组以及转换成js对象的简单实现方法
2016/06/24 Javascript
使用jQuery加载html页面到指定的div实现方法
2016/07/13 Javascript
微信小程序 侧滑删除(左滑删除)
2017/05/23 Javascript
基于vue实现web端超大数据量表格的卡顿解决
2019/04/02 Javascript
Node.js文本文件BOM头的去除方法
2020/11/22 Javascript
老生常谈Python序列化和反序列化
2017/06/28 Python
最小二乘法及其python实现详解
2020/02/24 Python
英国复古服装和球衣购买网站:3Retro Football
2018/07/09 全球购物
华为智利官方商店:Huawei Chile
2020/05/09 全球购物
神路信息Java面试题目
2013/03/31 面试题
几个Shell Script面试题
2014/04/18 面试题
教导处工作制度
2014/01/18 职场文书
十八大报告观后感
2014/01/28 职场文书
保险专业大学生职业规划书
2014/03/03 职场文书
法人授权委托书格式
2014/04/08 职场文书
解除合同协议书
2014/04/17 职场文书
学校领导班子群众路线整改措施
2014/09/16 职场文书
2014院党委领导班子对照检查材料思想汇报
2014/09/24 职场文书
小学生家长意见
2015/06/03 职场文书
银行客户经理培训心得体会
2016/01/09 职场文书
redis不能访问本机真实ip地址的解决方案
2021/07/07 Redis
Redis集群节点通信过程/原理流程分析
2022/03/18 Redis