「H5」三种滚动吸顶效果实现方案以及性能优化

8,277 阅读3分钟

方案


滑动吸顶效果的常见实现方式有以下几种:

1.positon: sticky

2.监听元素的滚动事件,使用offset判断

3.obj.getBoundingClientRect().top

下面依次介绍

positon: sticky


postion: sticky 属性值是css3新加入的属性,近似于relative和fixed的结合体

sticky被称为粘性定位元素,postion属性值为sticky的元素在目标区域内时表现的和position: relative时无异。

当元素满足粘性定位的要求时(如top: 100px),他的表现与position: fixed无异

元素固定的相对偏移是相对于离它最近的具有滚动框的祖先元素,如果祖先元素都不可以滚动,那么是相对于viewport来计算元素的偏移量。

使用条件:

  • 父元素不能设置为 overflow:hidden/auto
  • 如果要实现fixed的效果,必须要指定 top,bottom,right,left之一,不然其表现与relative无异
  • 父元素的高度不能低于skicky元素的高度

坑:

  • sticky元素是容齐相关的,它只在自己的符合条件的祖先容器内生效。
  • sticky元素虽然在满足粘性条件时表现position: fixed 无异,但是并不会触发BFC
  • sticky元素对于写在样式表中的z-index是无效的,如果想用z-index属性可以写在行内样式中

兼容性:

兼容性

兼容性不太友善,ios虽然支持度还行但是刘海屏的表现暂时待定

使用方式:

.sticky{
    positon: sticky;
    top: 10px
}

监听滚动事件——offset


首先来复习以下offset值的含义:

距离拥有相对定位的父级元素的顶部偏移量

那么offset并不一定表示元素距离页面顶部的距离,也可能是与拥有相对定位的父级元素的顶部距离

假设我们需要的吸顶效果是在body的顶部,我们可以如下改造一个方法

getOffset(obj,direction) {
    let offsetL = 0;
    let offsetT = 0;
    // 依次获取父级元素的offsetLeft/offsetTop
    while( obj!== window.document.body && obj !== null ){
        offsetL += obj.offsetLeft;
        offsetT += obj.offsetTop;
        obj = obj.offsetParent;
    }
    if(direction === 'left'){
        return offsetL;
    }else {
        return offsetT;
    }
}

handleScroll(e) {
    let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
    let offsetTop = getOffset(e.target,'top');
    if(scrollTop > offsetTop){
        // fixed
    }
}


优势:兼容性优秀

劣势:存在性能问题,最好搭配节流函数使用

监听滚动事件——obj.getBoundingClientRect().top


定义:返回某个元素相对浏览器视窗上下左右的距离

也就是说,这个api完全可以取代上面那个函数...

兼容性:

兼容性

优势:简 洁 , 兼 容 性 极 佳

劣势:没有解决reflow过多的性能问题

性能优化


众所周知,所有的scroll问题都逃不过高强度reflow带来的性能压力

解决方法也非常明确——牺牲平滑度,减少触发次数

throttle

在通常情况下,我们可以直接使用节流函数直接来限制触发次数,比如这么做

// 假设你封装好了一个throttle(func,time)的节流函数
window.addEventListener('scroll', throttle(handleScroll, 20));

但这么使用有一个问题。。那就是会出现吸顶时有一定卡顿的情况,就不够润。

那咋办呢?

我们不妨换种思路。。。不如精确控制handleScroll()函数的触发时机?

自然我们就想到了 IntersectionObserver API

IntersectionObserver API

IntersectionObserver API 的使用教程请移步这里

这个api的主要目的是用来判断一个元素是否在可视范围内(并可以控制触发时机)

兼容性:

兼容性

不难发现还是有那么一点兼容性问题的。所以我们不如将throttle和IntersectionObserver 方案结合起来使用

结合方案

this.flag == false

scrollFunc(e) {
    if( IntersectionObserver ){
        const observer = new IntersectionObserver(function(){
            const offsetTop = e.target.getBoundingClientRect().top;
            if(offsetTop < 0){
                // 吸顶
                this.flag == true
            }else{
                this.flag == false
            }
        }, {
            // 100%时触发回调函数
            threshold: [1]
        });
        observer.observe(e.target);
    } else {
        window.addEventListener('scroll', throttle(()=>{
            let offsetTop = e.target.getBoundingClientRect().top;
            if(offsetTop < 0){
                // 吸顶
                this.flag == true
            }else{
                this.flag == false
            }
        }, 20));
    }
}