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 相关文章推荐
关于在IE下的一个安全BUG --可用于跟踪用户的系统鼠标位置
Apr 17 Javascript
chrome浏览器不支持onmouseleave事件的解决技巧
May 31 Javascript
JS+CSS实现大气的黑色首页导航菜单效果代码
Sep 10 Javascript
微信公众号 客服接口的开发实例详解
Sep 28 Javascript
JS验证 只能输入小数点,数字,负数的实现方法
Oct 07 Javascript
Bootstrap导航中表单简单实现代码
Mar 06 Javascript
jQuery实现页面倒计时并刷新效果
Mar 13 Javascript
详解JS数组Reduce()方法详解及高级技巧
Aug 18 Javascript
更改BootStrap popover的默认样式及popover简单用法
Sep 13 Javascript
配置一个vue3.0项目的完整步骤
Apr 26 Javascript
浅谈对于“不用setInterval,用setTimeout”的理解
Aug 28 Javascript
vue3.0 项目搭建和使用流程
Mar 04 Vue.js
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图片库imagemagick安装方法
2014/09/23 PHP
php调用新浪短链接API的方法
2014/11/08 PHP
以实例全面讲解PHP中多进程编程的相关函数的使用
2015/08/18 PHP
总结PHP删除字符串最后一个字符的三种方法
2016/08/30 PHP
直接生成打开窗口代码,不必下载
2008/05/14 Javascript
firefo xml 读写实现js代码
2009/06/11 Javascript
js实现图片旋转的三种方法
2014/04/10 Javascript
table行随鼠标移动变色示例
2014/05/07 Javascript
jQuery的css()方法用法实例
2014/12/24 Javascript
JavaScript获取页面上被选中文字的方法技巧
2015/03/13 Javascript
javascript判断网页是关闭还是刷新
2015/09/12 Javascript
jquery.uploadView 实现图片预览上传功能
2017/08/10 jQuery
AngularJs用户登录问题处理(交互及验证、阻止FQ处理)
2017/10/26 Javascript
vue.js 实现a标签href里添加参数
2019/11/12 Javascript
js构造函数constructor和原型prototype原理与用法实例分析
2020/03/02 Javascript
JavaScript实现弹出窗口效果
2020/12/09 Javascript
python读取Excel实例详解
2018/08/17 Python
在python中只选取列表中某一纵列的方法
2018/11/28 Python
Python-copy()与deepcopy()区别详解
2019/07/12 Python
对django views中 request, response的常用操作详解
2019/07/17 Python
用Python实现定时备份Mongodb数据并上传到FTP服务器
2021/01/27 Python
使用html5 canvas创建太空游戏的示例
2014/05/08 HTML / CSS
html5用video标签流式加载的实现
2020/05/20 HTML / CSS
init进程的作用
2015/08/20 面试题
信息部岗位职责
2013/11/12 职场文书
英文自荐信格式
2013/11/28 职场文书
好习惯伴我成长演讲稿
2014/05/21 职场文书
孩子教育的心得体会
2014/09/01 职场文书
居安思危观后感
2015/06/11 职场文书
给领导敬酒词
2015/08/12 职场文书
保险公司岗前培训工作总结
2015/10/24 职场文书
创业计划书之零食店(进口)
2019/09/24 职场文书
基于Redis过期事件实现订单超时取消
2021/05/08 Redis
MySQL修炼之联结与集合浅析
2021/10/05 MySQL
SpringBoot2零基础到精通之数据库专项精讲
2022/03/22 Java/Android
html中两种获取标签内的值的方法
2022/06/10 HTML / CSS