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从头学起第三讲
Jul 06 Javascript
jQuery中对节点进行操作的相关介绍
Apr 16 Javascript
关于extjs4如何获取grid修改后的数据的问题
Aug 07 Javascript
JS获取select的value和text值的简单实例
Feb 26 Javascript
Javascript前端UI框架Kit使用指南之kitjs的对话框组件
Nov 28 Javascript
jQuery插件echarts实现的单折线图效果示例【附demo源码下载】
Mar 04 Javascript
js对象实例详解(JavaScript对象深度剖析,深度理解js对象)
Sep 21 Javascript
Angular2监听页面大小变化的解决方法
Oct 09 Javascript
解决ng-repeat产生的ng-model中取不到值的问题
Oct 02 Javascript
React中阻止事件冒泡的问题详析
Apr 12 Javascript
JavaScript定时器设置、使用与倒计时案例详解
Jul 08 Javascript
在layui.use 中自定义 function 的正确方法
Sep 16 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目录操作函数之获取目录与文件的类型
2010/12/29 PHP
Yii框架结合sphinx,Ajax实现搜索分页功能示例
2016/10/18 PHP
PHP设计模式之模板方法模式实例浅析
2018/12/20 PHP
动态添加js事件实现代码
2009/03/12 Javascript
javascript Object与Function使用
2010/01/11 Javascript
一个JavaScript变量声明的知识点
2013/10/28 Javascript
VS2008中使用JavaScript调用WebServices
2014/12/18 Javascript
Nodejs学习笔记之入门篇
2015/04/16 NodeJs
javascript实现动态表头及表列的展现方法
2015/07/14 Javascript
jQuery CSS3相结合实现时钟插件
2016/01/08 Javascript
javascript每日必学之继承
2016/02/23 Javascript
vueJS简单的点击显示与隐藏的效果【实现代码】
2016/05/03 Javascript
EasyUI布局 高度自适应
2016/06/04 Javascript
javascript ES6中箭头函数注意细节小结
2017/02/17 Javascript
详解Vue2中组件间通信的解决全方案
2017/07/28 Javascript
详解jquery插件jquery.viewport.js学习使用方法
2017/09/08 jQuery
three.js搭建室内场景教程
2018/12/30 Javascript
Vue打包后访问静态资源路径问题
2019/11/08 Javascript
Python读取文件内容的三种常用方式及效率比较
2017/10/07 Python
Python 对输入的数字进行排序的方法
2018/06/23 Python
Python3.4学习笔记之列表、数组操作示例
2019/03/01 Python
深入解析python中的实例方法、类方法和静态方法
2019/03/11 Python
Python何时应该使用Lambda函数
2019/07/02 Python
python实现对图片进行旋转,放缩,裁剪的功能
2019/08/07 Python
Python获取统计自己的qq群成员信息的方法
2019/11/15 Python
pycharm修改file type方式
2019/11/19 Python
Python关键字及可变参数*args,**kw原理解析
2020/04/04 Python
Python基于smtplib模块发送邮件代码实例
2020/05/29 Python
Clarria化妆品官方网站:购买天然和有机化妆品系列
2018/04/08 全球购物
大学生志愿者感言
2014/01/15 职场文书
小学开学寄语
2014/01/19 职场文书
2015年五一劳动节活动总结
2015/02/09 职场文书
教师岗位说明书
2015/09/30 职场文书
python开发实时可视化仪表盘的示例
2021/05/07 Python
java基础——多线程
2021/07/03 Java/Android
Python 如何利用ffmpeg 处理视频素材
2021/11/27 Python