A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 没名字i 于 2019-4-11 14:33 编辑

目录
使用 position:sticky 实现
使用 JQuery 的 offset().top 实现
使用原生的 offsetTop 实现
使用 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 元素仅在其父元素内生效

在需要滚动吸顶的元素加上以下样式便可以实现这个效果:
[CSS] 纯文本查看 复制代码
.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() 的结合,我们也可以实现滚动吸顶效果。
[JavaScript] 纯文本查看 复制代码
...
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 做以下处理:
[JavaScript] 纯文本查看 复制代码
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;
    }
}

使用:
[JavaScript] 纯文本查看 复制代码
..
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
[HTML] 纯文本查看 复制代码
<div class="pride_tab_fixed" ref="pride_tab_fixed">
    <div class="pride_tab" :class="titleFixed == true ? 'isFixed' :''">
        // some code
    </div>
</div>


// vue
[JavaScript] 纯文本查看 复制代码
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 上边距的距离。代码如下:
[JavaScript] 纯文本查看 复制代码
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 值来实现吸顶效果,即:
[HTML] 纯文本查看 复制代码
<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()。

链接:https://juejin.im/post/5caa0c2d51882543fa41e478

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马