preface

Last year this note started, the result touch fish touch too happy forget this stubble… Back to the point. Initially thought of using onPageScroll this API to achieve, the result is not ideal, a single top effect is ok, but the list top will have obvious lag, poor user experience. Caught the dog head, open SAO operation, to the community, sogou (why is not Baidu and ditch, after all, small program is Tencent home, of course, with sogou) a wild search, as expected, standing on the shoulders of big guys to see further, the following code is uniAPP version.

List (pseudo) suction top implementation

The principle is to monitor the intersection of the target node and the reference node to show and hide the top module, so it is called (pseudo) top.

This paper realizes the effect of list sucking and list folding.

Core API

wx.createSelectorQuery()

Create a SelectorQuery selector to query node information

SelectorQuery.select(string selector)

Gets the first matching selector on the current page

SelectorQuery.selectAll(string selector)

Gets all the selector nodes of the current page

wx.createIntersectionObserver(Object component, Object options)

Create and return an IntersectionObserver object instance that corresponds to the IntersectionObserver API on the Web side

IntersectionObserver.relativeTo(string selector, Object margins)

Specify a selector as the reference node

IntersectionObserver.observe(string targetSelector, function callback)

Used to monitor the change of the intersection status of the specified target node

Get target node

Create a selector using createSelectorQuery(). Use selectAll to get all the target elements of the page. Note that custom components are created using this.createsElectorQuery ().

targetElmEvent(targetElm) {
    const me = this
    return new Promise((resolve, reject) = > {
        // Create a selector to get the listener array
        me.createSelectorQuery().selectAll(targetElm).boundingClientRect(function(res) {
	    // The element cannot be found
            // The id must be the same as the id on the element node. I'm going to do something with id, and it looks like there's a problem with numeric ID
            if(res.length < 1) {
                const tip = JSON.parse(JSON.stringify(me.list))
                tip.map(v= > v.id=`tip${v.id}`)
                me.targetList = tip
            }else {
                me.targetList = res
            }
            resolve(res)
        }).exec()
    })
}
Copy the code

Setting up listeners

Through wx. CreateIntersectionObserver () to create a listener, incoming current custom component instance this, set attribute observeAll for monitoring multiple nodes at the same time, pay attention to the node consumes too much performance. Set a reference with a relativeTo. Use Observe to monitor the intersections between the target node and the reference.

observerEvent(targetElm, relativeElm) {
    const me = this;
    this.targetElmEvent(targetElm).then(res= > {
        // Create observer set observeAll to monitor multiple nodes simultaneously
        const observer = wx.createIntersectionObserver(this, {observeAll: true})
        // Set the reference and listen for the specified target
        observer.relativeTo(relativeElm).observe(targetElm, res => {
            // intersectionRatio is the intersectionRatio. BoundingClientRect is the target boundary
            const {id, intersectionRatio, boundingClientRect} = res
            const {top, bottom, height} = boundingClientRect
            // intersectionRatio is used to judge whether intersecting boundary is exceeded
            if (intersectionRatio > 0) {
                // Assign the top data
                me.fixedData = me.list.filter(v= >id.includes(v.id))[0]
	            me.fixedShow = true
            }else {
                // Perform special processing on the upper boundary of the top and tail items of the target to clear the top data
                if((id === me.targetList[0].id && top >= 0) || (id === me.targetList[me.targetList.length- 1].id && top <= 0)) {
                    me.fixedShow = false
                    me.fixedData = {}
                }
            }
        })
    })
}
Copy the code

Additional small features – folding

Control folding by setting the show property for each piece of data.

stackToggleEvent(item) {
    const me = this
    let count = 0
    me.list.map((v, i) = > {
        if(v.id===item.id) { v.show=! v.show }if(! v.show) { count+=1
        }
        if(count === me.list.length) {
            me.fixedData = {}
            me.fixedShow = false}})}Copy the code

Here is the full code

<template>
    <div>
        <! -- Reference node -->
        <div class="relative"></div>
        <! -- Dummy top module -->
        <div class="stack-top st-fixed" @click="stackToggleEvent(fixedData)" v-if="fixedShow">{{fixedData.title}}</div>
        <! -- Data list -->
        <ul v-if="list.length > 0">
        <! -- Listen on target -->
            <li class="targetTag" :id="`tip${item.id}`" v-for="item of list" :key="item.id"> 
                <div class="stack-top" @click="stackToggleEvent(item)">{{item.title}}</div>
                <div class="stack-ct" v-if="item.show">
                    <ul :class="'foldTag'+item.id">
                        <li class="sc-k" v-for="child in item.children" :key="child.id">
                            <div>{{child.name}}</div>
                        </li>
                    </ul>
                </div>
            </li>
        </ul>
    <! -- Test out of scope -->
        <div class="overflow"></div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                fixedShow: false.// Whether to display the top suction module
                targetList: [], // Listen on the target
                fixedData: {}, // Top module data
                list: [] / / the data source
            }
        },
        onLoad() {
      // Test data
      The show attribute is used for collapsibility
            this.list = [
                {
                    id: 1.title: 'Part ONE'.show: false.children: [{id: 11.name: 1-1 ' '
                        },
                        {
                            id: 12.name: '2'
                        },
                        {
                            id: 13.name: '1-3'
                        },
                        {
                            id: 14.name: 1-4 ' '
                        },
                        {
                            id: 15.name: '1-5']}, {},id: 2.title: 'Part TWO'.show: false.children: [{id: 21.name: '2-1'
                        },
                        {
                            id: 22.name: '2-2'
                        },
                        {
                            id: 23.name: '2-3'
                        },
                        {
                            id: 24.name: '2-4'
                        },
                        {
                            id: 25.name: '2-5'
                        },
                        {
                            id: 26.name: '2-6']}, {},id: 3.title: 'Part THREE'.show: false.children: [{id: 31.name: '3-1'
                        },
                        {
                            id: 32.name: '3-2'
                        },
                        {
                            id: 33.name: '3-3'
                        },
                        {
                            id: 34.name: '3-4'
                        },
                        {
                            id: 35.name: '3 to 5'
                        },
                        {
                            id: 36.name: '3-6'
                        },
                        {
                            id: 37.name: '3 to 7'
                        },
                        {
                            id: 38.name: '3-8'
                        },
                        {
                            id: 39.name: '3-9'
                        },
                        {
                            id: 391.name: '3-9-1'
                        },
                        {
                            id: 392.name: '3-9-2'
                        },
                    ]
                }
            ]
        },
        mounted(){
            const me = this
            // Start the top sucking function, passing in the target node and the reference node
            me.observerEvent('.targetTag'.'.relative')},methods: {
            // Implement the folding function and handle the top change when expanding and hiding
            stackToggleEvent(item) {
                const me = this
                let count = 0
                me.list.map((v, i) = > {
                    if(v.id===item.id) { v.show=! v.show }if(! v.show) { count+=1
                    }
                    if(count === me.list.length) {
                        me.fixedData = {}
                        me.fixedShow = false}})},// Get the listener target and return a promise
            targetElmEvent(targetElm) {
                const me = this
                return new Promise((resolve, reject) = > {
                    // Create a selector to get the listener array
                    me.createSelectorQuery().selectAll(targetElm).boundingClientRect(function(res) {
                        // The element cannot be found
                        // The id must be the same as the id on the element node. I'm going to do something with id, and it looks like there's a problem with numeric ID
                        if(res.length < 1) {
                            const tip = JSON.parse(JSON.stringify(me.list))
                            tip.map(v= > v.id=`tip${v.id}`)
                            me.targetList = tip
                        }else {
                            me.targetList = res
                        }
                        resolve(res)
                    }).exec()
                })
            },
            // Listen on the target to determine the boundary processing core logic
            observerEvent(targetElm, relativeElm) {
                const me = this;
                this.targetElmEvent(targetElm).then(res= > {
                    Set observeAll to monitor multiple nodes at the same time. Note that too many nodes will consume performance
                    const observer = wx.createIntersectionObserver(this, {observeAll: true})
                    // Set the reference and listen for the specified target
                    observer.relativeTo(relativeElm).observe(targetElm, res => {
                        // intersectionRatio is the intersectionRatio. BoundingClientRect is the target boundary
                        const {id, intersectionRatio, boundingClientRect} = res
                        const {top, bottom, height} = boundingClientRect
                        // intersectionRatio is used to judge whether intersecting boundary is exceeded
                        if (intersectionRatio > 0) {
                            // Assign the top data
                            me.fixedData = me.list.filter(v= >id.includes(v.id))[0]
                            me.fixedShow = true
                        }else {
                            // Do a special processing on the top boundary of the target to clear the top data
                            if((id === me.targetList[0].id && top >= 0) || (id === me.targetList[me.targetList.length- 1].id && top <= 0)) {
                                me.fixedShow = false
                                me.fixedData = {}
                            }
                        }
                    })
                })
            }
        }
    }
</script>
<style lang="scss" scoped>.relative { position: fixed; top: -2rpx; left: 0; height: 2rpx; width: 100vw; opacity: 0; } .st-fixed { position: fixed; top: 0; left: 0; width: 100%; z-index: 10; box-sizing: border-box; background-color: #ff7a52 ! important; } .stack-top { @include _flex(space-between); padding: 20rpx 30rpx; background-color: #6f9dff; .deg180c { transform: rotate(180deg); } } .stack-ct { padding: 0 30rpx; text-align: right; .sc-k { position: relative; display: inline-block; width: 100%; padding: 30rpx; margin-bottom: 60rpx; box-sizing: border-box; Box-shadow :0 12rpx 36rpx 0 rgba(31,66,209,0.1); border-radius:8rpx; 2 the RPX border: solid rgba (235238255, 1); &:first-child { margin-top: 10rpx; } &:last-child { .line { opacity: 0; } } } .sk-top { @include _flex(flex-start, flex-start, flex-start); .st-time { font-size:24rpx; Color: rgba (2,0,18,1); } } } .overflow { height: 200vh; width: 100vw; background-color: red; }</style>
Copy the code

conclusion

If there is something wrong, please gently advise.