用戶
 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

掃一掃,登錄網站

小程序社區 首頁 工具/框架 查看內容

小程序列表吸頂組件

Rolan 2020-4-1 00:41

前言 去年這筆記就開了頭,結果摸魚摸的太快樂忘了這茬...言歸正傳。最初想到用onPageScroll這個API來實現,結果效果并不理想,單個吸頂效果尚可,但列表吸頂會有明顯卡頓,用戶體驗較差。抓了抓狗頭,開啟騷操作, ...

前言

去年這筆記就開了頭,結果摸魚摸的太快樂忘了這茬...言歸正傳。最初想到用onPageScroll這個API來實現,結果效果并不理想,單個吸頂效果尚可,但列表吸頂會有明顯卡頓,用戶體驗較差。抓了抓狗頭,開啟騷操作,到社區、搜狗(why不是度娘和股溝,畢竟小程序是tencent家的,當然用搜狗了)一頓狂搜,果然站在大佬們的肩膀上看的更遠,以下代碼是uniapp版本。

列表(偽)吸頂實現

原理是監聽目標節點與參照節點的相交情況對吸頂模塊做顯示隱藏,所以叫(偽)吸頂吧。

文章實現了列表吸頂與列表折疊效果。

核心API

wx.createSelectorQuery()

創建一個SelectorQuery 選擇器,用來查詢節點信息

SelectorQuery.select(string selector)

用于獲取當前頁面第一個匹配selector的節點

SelectorQuery.selectAll(string selector)

用于獲取當前頁面所有selector節點

wx.createIntersectionObserver(Object component, Object options)

創建并返回一個 IntersectionObserver 對象實例,它和web端 IntersectionObserver 這個API相對應

IntersectionObserver.relativeTo(string selector, Object margins)

指定一個選擇器作為參照節點

IntersectionObserver.observe(string targetSelector, function callback)

用于監聽指定目標節點相交狀態變化情況

獲取目標節點

通過createSelectorQuery()創建選擇器,利用selectAll獲取頁面全部目標元素,注意自定義組件用this.createSelectorQuery()來創建,否則獲取不到。

targetElmEvent(targetElm) {
    const me = this
    return new Promise((resolve, reject) => {
        // 創建選擇器 獲取監聽數組
        me.createSelectorQuery().selectAll(targetElm).boundingClientRect(function(res) {
	    // 此處會出現找不到元素的情況,此處做了簡單處理
            // id要與元素節點上的id一致。對id做了處理,貌似數字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()
    })
}
復制代碼

設置監聽器

通過wx.createIntersectionObserver()來創建一個監聽器,傳入當前自定義組件實例this,設置用于同時監測多個節點的屬性observeAll,注意節點過多會消耗性能。通過relativeTo設置參照物,使用observe監聽目標節點與參照物的相交狀態變化。

observerEvent(targetElm, relativeElm) {
    const me = this;
    this.targetElmEvent(targetElm).then(res => {
        // 創建觀察者 設置observeAll用于同時監測多個節點
        const observer = wx.createIntersectionObserver(this, {observeAll: true})
        // 設置參照物并監聽指定目標
        observer.relativeTo(relativeElm).observe(targetElm, res => {
            // intersectionRatio為相交比例 boundingClientRect為目標邊界
            const {id, intersectionRatio, boundingClientRect} = res
            const {top, bottom, height} = boundingClientRect
            // 此處通過 intersectionRatio 來判斷是否超過相交邊界
            if (intersectionRatio > 0) {
                // 賦值吸頂數據
                me.fixedData = me.list.filter(v=>id.includes(v.id))[0]
	            me.fixedShow = true
            }else {
                // 對目標首尾項的上邊界做特殊處理 清空吸頂數據
                if((id === me.targetList[0].id && top >= 0) || (id === me.targetList[me.targetList.length-1].id && top <= 0)) {
                    me.fixedShow = false
                    me.fixedData = {}
                }
            }
        })
    })
}
復制代碼

附加小功能 - 折疊

通過對每條數據設置show屬性來控制折疊效果。

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
        }
    })
}
復制代碼

以下是全部代碼

<template>
    <div>
        <!-- 參照物節點 -->
        <div class="relative"></div>
        <!-- 偽吸頂模塊 -->
        <div class="stack-top st-fixed" @click="stackToggleEvent(fixedData)" v-if="fixedShow">{{fixedData.title}}</div>
        <!-- 數據列表 -->
        <ul v-if="list.length > 0">
        <!-- 監聽目標 -->
            <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>
    <!-- 測試超出范圍 -->
        <div class="overflow"></div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                fixedShow: false, // 是否顯示吸頂模塊
                targetList: [], // 監聽目標
                fixedData: {}, // 吸頂模塊數據
                list: [] // 數據源
            }
        },
        onLoad() {
      // 測試數據
      // show屬性用于折疊功能
            this.list = [
                {
                    id: 1,
                    title: '第一部分',
                    show: false,
                    children: [
                        {
                            id: 11,
                            name: '1 - 1'
                        },
                        {
                            id: 12,
                            name: '1 - 2'
                        },
                        {
                            id: 13,
                            name: '1 - 3'
                        },
                        {
                            id: 14,
                            name: '1 - 4'
                        },
                        {
                            id: 15,
                            name: '1 - 5'
                        },
                    ]
                },
                {
                    id: 2,
                    title: '第二部分',
                    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: '第三部分',
                    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 - 5'
                        },
                        {
                            id: 36,
                            name: '3 - 6'
                        },
                        {
                            id: 37,
                            name: '3 - 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
            // 啟動吸頂功能,傳入目標節點與參照節點
            me.observerEvent('.targetTag', '.relative')
        },
        methods: {
            // 實現折疊功能并處理展開隱藏時吸頂變化
            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
                    }
                })
            },
            // 獲取監聽目標 返回promise
            targetElmEvent(targetElm) {
                const me = this
                return new Promise((resolve, reject) => {
                    // 創建選擇器 獲取監聽數組
                    me.createSelectorQuery().selectAll(targetElm).boundingClientRect(function(res) {
                        // 此處會出現找不到元素的情況,此處做了簡單處理
                        // id要與元素節點上的id一致。對id做了處理,貌似數字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()
                })
            },
            // 監聽目標 判斷邊界 處理核心邏輯
            observerEvent(targetElm, relativeElm) {
                const me = this;
                this.targetElmEvent(targetElm).then(res => {
                    // 創建觀察者 設置observeAll用于同時監測多個節點,注意節點過多會消耗性能
                    const observer = wx.createIntersectionObserver(this, {observeAll: true})
                    // 設置參照物并監聽指定目標
                    observer.relativeTo(relativeElm).observe(targetElm, res => {
                        // intersectionRatio為相交比例 boundingClientRect為目標邊界
                        const {id, intersectionRatio, boundingClientRect} = res
                        const {top, bottom, height} = boundingClientRect
                        // 此處通過 intersectionRatio 來判斷是否超過相交邊界
                        if (intersectionRatio > 0) {
                            // 賦值吸頂數據
                            me.fixedData = me.list.filter(v=>id.includes(v.id))[0]
                            me.fixedShow = true
                        }else {
                            // 對目標首尾的上邊界做特殊處理 清空吸頂數據
                            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;
            border:2rpx solid rgba(235,238,255,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>
復制代碼

結語

若有不對的地方,請輕點指教。


鮮花
鮮花
雞蛋
雞蛋
分享至 : QQ空間
收藏
原作者: Macoo 來自: 掘金
河北20选5大星走势图