tab summary of ceiling problems

Posted by virtualdevl on Tue, 14 Dec 2021 04:48:20 +0100

preface

Recently, I have encountered the problem of tab ceiling several times due to the tasks I handle. In fact, it is not difficult to implement tab ceiling, but tab ceiling often appears together with tab switching. I'm good at cooking and have a bad memory, so record it, hahaha!

1, Implementation of tab ceiling

tab ceiling can be realized in several ways, which can be roughly divided into two types: 1 CSS implementation 2 js monitors the implementation of scroll; However, in the actual development, one is used, that is, the implementation of js. But in any case, what should be said and what is unnecessary (in fact, it is very necessary) should be said

1.position:sticky to achieve ceiling (not recommended)

Sticky is actually a combination of fixed and relative methods. Setting position: before the sticky element meets the setting position requirements, it is equivalent to relative positioning. When the position requirements are met, it is fixed positioning.

Application requirements of sticky:

  • The height of the parent element cannot be less than the height of the set sticky element
  • Parent element cannot set overflow:hidden or auto
  • One of the properties top, bottom, left, right must be specified
  • The sticky element value takes effect within the parent element
<style>
.sticky {
  position: -webkit-sticky;
  position: sticky;
  top: 0;//When top reaches 0, it becomes fixed; When less than 0, it is relative
}
</style>
...
<div id="app">
	<div class="header"></div>
	<div class="body">
		<div class="tab sticky"></div>
		<div class="content"></div>
	</div>
</div>

The biggest problem with sticky is its compatibility

2. Scrolltop - offsettop > 0 to achieve ceiling

The idea of this method is to judge the position between the screen scrolling distance and the distance from the tab element to the top. In fact, this method is the general idea of js implementation. One problem with this implementation idea is that it is difficult to obtain offsetTop. What is offsetTop?

offsetTop: read only attribute, the distance of the current element from the top inner margin of its offsetParent element. What is offsetParent?

offsetParent: a read-only attribute that returns a location element that points to the nearest (the nearest at the inclusion level) containing the element or the nearest table, td, th, body element. (as long as the parent element has no position set, offsetParent will look up until the body)

Get the method encapsulation of offsetTop

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

Ceiling implementation

<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 can be obtained through 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;
        }
    }
})

The problem with this method is the acquisition of offsetTop and scrollTop. The acquisition of offsetTop needs to pay attention to offsetParent and the compatibility of scrollTop.

3.getBoundingClientRect

What is getBoundingClientRect? Method returns the size of the element and its position relative to the viewport. (the width and height of the element, the up, down, left and right distances from the viewport, and xy coordinates). In this way, you can directly judge whether the top value of the distance viewport is 0, so you can ceiling. However, considering the problem of lifting the ceiling when pulling down, you can't directly judge top < = 0, but you should judge it by the top value of the content below the tab and the height of the tab.

<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;
        }
    }
})

getBoundingClientRect has good compatibility.

2, Problems of tab ceiling
1.tab no memory

Because tab ceiling usually appears together with tabs, there will be a problem that the position of the content of each tab affects each other during tab switching. As shown below

Click 1 and move the content under 1 to a certain position, and then click 2. You will find that the initial position of 2 is the end position of 1, which obviously does not meet the expectation

Solution: through an object, the attribute of the object is the name corresponding to each tab, and each value is the position where the tab is switched.

Problem: the above idea only realizes the ceiling option tab. The option tab that has not been switched will not ceiling, because the rolling distance of the tab that has not been switched is 0.

Optimization: the above solution is optimized. When switching tabs, if they are already ceiling mounted, the switched tab moves to the default value (this value is not 0, and this value just makes the tab in ceiling mounted state)

<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(() => {
            // Initialize the content corresponding to the tab to the top of the viewport
            this.initContentTop = this.$refs.contentRef.getBoundingClientRect().top;
        })
    },
    computed: {
        tabHeight() {
            return this.$refs.tabRef.getBoundingClientRect().height;
        },
    },
    methods: {
        handleScroll() {
            const contentTop = this.$refs.contentRef.getBoundingClientRect().top;
            // Record the rolling distance of each tab
            this.scrollDistance[this.tabActive] = this.initContentTop - contentTop;
            this.isTabShow = contentTop <= this.tabHeight;
        },

        handleTabChange(index) {
            this.tabActive = index;
            if (this.isTabShow) {
                //As long as one tab is on the top, the default value will be moved during switching. Otherwise, if the switched tab has not been moved, the y value of scrollTo will be 0, which will cause the tab not to attract the top
                window.scrollTo(0, this.getDistance());
            }
        },

        // Get the corresponding movement distance during tab switching
        getDistance() {
            const d = this.scrollDistance[this.tabActive];
            const initd = this.initContentTop - this.tabHeight;
            return d > initd ? d : initd;
        }
    }
})

design sketch:

Topics: Javascript css3 Vue css