tab吸顶问题总结

前言

最近由于经手的任务遇到过几回tab吸顶的问题,其实tab吸顶实现并不难,但是tab吸顶常常是与选项卡切换一起出现的。本人较菜并且记忆力不好,所以记录下,哈哈哈!

一、tab吸顶的实现方式

tab吸顶可以通过几种方式实现,实现大体分为两种:1.css实现 2.js监听scroll实现;但是实际开发中使用的就是一种那就是js的实现方式。但不管怎么说,该说得说,不必要(其实也挺有必要)说的也得说说

1.position:sticky实现吸顶(不推荐)

sticky其实就是fixed与relative这两种方式的结合,设置position:sticky的元素达到设置位置要求之前相当于relative定位,当达到位置要求是就是fixed定位。

sticky的使用要求:

  • 父元素高度不能小于设置sticky元素的高度
  • 父元素不能设置overflow:hidden或auto
  • 必须指定top、bottom、left、right其中一个属性
  • sticky元素值在父元素内生效
<style>
.sticky {
  position: -webkit-sticky;
  position: sticky;
  top: 0;//当top达到0时,变为fixed;小于0时,为relative
}
</style>
...
<div id="app">
	<div class="header"></div>
	<div class="body">
		<div class="tab sticky"></div>
		<div class="content"></div>
	</div>
</div>

sticky存在的最大问题在于sticky的兼容性问题

在这里插入图片描述

2.scrollTop - offsetTop > 0 实现吸顶

这种方式的思路就是屏幕滚动距离与tab元素距离顶部距离的位置之间的判断,其实这种方式就是js实现的大体思路。该种实现思路有一个问题就是offsetTop不好获取。offsetTop是什么?

offsetTop:只读属性,当前元素相对于其 offsetParent 元素的顶部内边距的距离。offsetParent 又是什么?

offsetParent:只读属性,返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的 table, td, th, body 元素。(只要父元素没有设置position,则offsetParent就一直向上找,直到body)

获取offsetTop的方法封装

getOffset(el) {
    let offsetTop = 0;
    while (el !== window.document.body && el !== null) {
        offsetTop += el.offsetTop;
        el = el.offsetParent;
    }
    return offsetTop;
}

吸顶实现

<div id="app">
    <div class="header"></div>
    <div class="body">
        <div ref='tabRef' class="tab" :class="{'tab-fixed': isTabShow}"></div>
        <div class="empty-box" v-show='isTabShow'></div>
        <div ref='contentRef' class="content"></div>
    </div>
</div>
var app = new Vue({
    el: '#app',
    data: {
        isTabShow: false,
        offsetTop: 0,
    },
    mounted() {
        window.addEventListener('scroll', this.handleScroll)
        this.$nextTick(() => {
            this.offsetTop = this.getOffset(this.$refs.tabRef)//offsetTop可以通过jquery去获取
        })
    },
    methods: {
        handleScroll() {
            const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
            this.isTabShow = scrollTop >= this.offsetTop
        },

        getOffset (el) {
            let offsetTop = 0;
            while (el !== window.document.body && el !== null) {
                offsetTop += el.offsetTop;
                el = el.offsetParent;
            }
            return offsetTop;
        }
    }
})

该方式存在的问题就是offsetTop以及scrollTop的获取问题,offsetTop获取需要注意offsetParent,scrollTop要注意兼容性问题。

3.getBoundingClientRect

getBoundingClientRect是什么?方法返回元素的大小及其相对于视口的位置。(元素的宽高,距离视口的上下左右距离,以及xy坐标)。通过这种方式可以直接判断距离视口的top值是否为0,从而进行吸顶,但是考虑到下拉时解除吸顶问题,所以不能直接判断top<=0,而应该通过tab下方的内容的top值与tab的height进行判断。

<div id="app">
    <div class="header"></div>
    <div class="body">
        <div ref='tabRef' class="tab" :class="{'tab-fixed': isTabShow}">
            <span @click="handleTabChange(1)" class="tab-title">1</span>
            <span @click="handleTabChange(2)" class="tab-title">2</span>
            <span @click="handleTabChange(3)" class="tab-title">3</span>
        </div>
        <div class="empty-box" v-show='isTabShow'></div>
        <div ref='contentRef' class="content">
            <div v-show="tabActive === 1" class="tab-content">1</div>
            <div v-show="tabActive === 2" class="tab-content">2</div>
            <div v-show="tabActive === 3" class="tab-content">3</div>
        </div>
    </div>
</div>
var app = new Vue({
    el: '#app',
    data: {
        isTabShow: false,
        tabActive: 1,
    },
    mounted() {
        window.addEventListener('scroll', this.handleScroll)
    },
    computed: {
        tabHeight() {
            return this.$refs.tabRef.getBoundingClientRect().height;
        }
    },
    methods: {
        handleScroll() {
            const contentTop = this.$refs.contentRef.getBoundingClientRect().top;
            this.isTabShow = contentTop <= this.tabHeight;
        },

        handleTabChange(index) {
            this.tabActive = index;
        }
    }
})

存在的问题:tab吸顶的时候,position变为fixed,脱离文档流,造成后面的元素往上挤,出现文档内容突然向上跳动的情况,本人之前的解决方法式在tab元素后面加上一个空盒子即class="empty-box"的div,另一种方式是在吸顶的tab外层包一个div。

getBoundingClientRect有很好的兼容性。

在这里插入图片描述

二、tab吸顶存在的问题
1.tab无记忆

由于tab吸顶一般是与选项卡一起出现的所以,就会出现一个问题,就是选项卡切换的时候,每个选项卡的内容所处的位置是相互影响的。如下图

在这里插入图片描述

点击1并将1下的内容移到某一位置,然后点击2,你会发现2的初始位置就是1的结束位置,这显然是不符合预期的

解决思路:通过一个对象,该对象的属性就是每个tab对应的name,每个值就是tab切换时所处的位置。

问题:上面的思路仅仅只是实现了吸顶的选项tab吸顶了,没有切换过的选项tab是不会吸顶的,因为没有切换的tab的滚动距离为0。

优化:在上面解决思路上优化,切换tab时,如果已经吸顶了,那么切换的tab移动默认值(该值不为0,且该值恰好使得该tab处于吸顶状态)

<div id="app">
    <div class="header"></div>
    <div class="body">
        <div ref='tabRef' class="tab" :class="{'tab-fixed': isTabShow}">
            <span @click="handleTabChange('one')" class="tab-title">1</span>
            <span @click="handleTabChange('two')" class="tab-title">2</span>
            <span @click="handleTabChange('three')" class="tab-title">3</span>
        </div>
        <div class="empty-box" v-show='isTabShow'></div>
        <div ref='contentRef' class="content">
            <div v-show="tabActive === 'one'" class="tab-content">1</div>
            <div v-show="tabActive === 'two'" class="tab-content">2</div>
            <div v-show="tabActive === 'three'" class="tab-content">3</div>
        </div>
    </div>
</div>
var app = new Vue({
    el: '#app',
    data: {
        isTabShow: false,
        tabActive: 'one',
        initContentTop: 0,
        scrollDistance: {
            one: 0,
            two: 0,
            three: 0,
        }
    },
    mounted() {
        window.addEventListener('scroll', this.handleScroll)
        this.$nextTick(() => {
            // 初始化tab对应的content对于视口的top
            this.initContentTop = this.$refs.contentRef.getBoundingClientRect().top;
        })
    },
    computed: {
        tabHeight() {
            return this.$refs.tabRef.getBoundingClientRect().height;
        },
    },
    methods: {
        handleScroll() {
            const contentTop = this.$refs.contentRef.getBoundingClientRect().top;
            // 记录每个tab滚动的距离
            this.scrollDistance[this.tabActive] = this.initContentTop - contentTop;
            this.isTabShow = contentTop <= this.tabHeight;
        },

        handleTabChange(index) {
            this.tabActive = index;
            if (this.isTabShow) {
                //只要一个tab吸顶了,那么切换时,会移动默认值。否则如果切换的tab未移动过,scrollTo的y值会是0,又会造成tab不吸顶
                window.scrollTo(0, this.getDistance());
            }
        },

        // 获取tab切换时,对应的移动距离
        getDistance() {
            const d = this.scrollDistance[this.tabActive];
            const initd = this.initContentTop - this.tabHeight;
            return d > initd ? d : initd;
        }
    }
})

效果图:

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值