Vue仿iphone通讯录

798 阅读2分钟

仿iPhone手机通讯录的滚动效果,代码参照了网络上各位大神。自己整理以做记录,也顺便巩固一下知识。使用到的组件如下: BetterSCROLL

一、基础滚动组件:scroll.vue

基于BetterScroll先封装一个基础的滚动组件,便于后续使用。

相关代码如下:

<template>
    <div ref="rootRefDom">
        <solt></solt>
    </div>
</template>
<script>
import { ref } from 'vue'
import useScroll from './userScroll'

export default {
    name: 'scroll',
    props: {
        probeType: {
            type: Number,
            default: 0
        }
    },
    emits: ['scroll'],
    setup(props, { emit }) {
        const rootRefDom = ref(null)
        useScroll(rootRefDom, props, emit)
        
        return {
            rootRefDom
        }
    }
}
</script>

部分代码注释:

ref="rootRefDom" 获取DOM结构;

probeType:设置滚动的灵明度,分别可以设置为1、2、3。数字越大,灵明度越高,当然性能也就越低。

emits: 在vue3需要通过这种方式来对外传递事件。

setup(),可以传入两个参数,一个是:props,一个是:context,context是一个对象,包含:{attrs, slots, emit, expose},此处只使用emit。后续通过emit将滚动的x、y坐标传出。

useScroll(): 分别将获取的DOM结构,props,emit作为参数传入。

二、 useScroll.js

页面挂载的时候实例化BScroll组件,通过ObserveDOM,监听页面数据填充后,实现滚动。页面离开的时候销毁之前的挂载。 传入的probeType大于设置的值时,监听滚动,通过传入的emit,调用外部的scroll事件,并将滚动的坐标值传出。

相关代码如下:

import { ref, onMonted, onUnMonted } from 'vue'
import BScroll from '@better-scroll/core'
import ObserveDOM from '@better-scroll/observe-dom'

BScroll.use(ObserveDOM)

export defautl function useScroll(rootRef, options, emit) {
    const scroll = ref(null)
    
    onMounted(() => {
        const scrollValue = scroll.value = new BScroll(rootRef,{
            observeDOM: true,
            ...options
        })
        
        if(options.probeType > 1) {
            scrollValue.on('scroll', (pos) => {
                emit('scroll', pos)
            })
        }
    })
    
    onUnMounted(() => {
        scroll.value.destroy()
    })
}

三、列表组件list.vue

页面按照iphone通讯录的样子,一个分类,对应一列分类数据,对应分类标题滚动到顶部后,自动吸在顶部。

此处数据直接在页面写死的,正常情况下都是从后端获取,这里写出来是表明数据结构。

相关代码如下:

<template>
    <scroll class="list" :probeType="3" @scroll="onScroll">
        <ul ref="groupRefDOM">
            <li v-for="group in list" :key="group.title">
                <h2>{{ group.title }}</h2>
                <ul>
                    <li v-for="item in group.child" :key="item.name">
                        {{ item.name }}
                    </li>
                </ul>
            </li>
        </ul>
        <div class="fixedTop" v-if="fixTitle" :style="fixTitleStyle">{{fixTitle}}</div>
    </scroll>
</template>

<script>
import scroll from './scroll'
import useFixTitle from './useFixTitle'

export default {
    name: 'list',
    components: {
        scroll
    },
    setup() {
        const list = [{title:'a',child:[{name: 'a1'}]},{title:'b',child:[{name: 'b1'}]}]
        const fixTitleHeight = 30
        const {groupRefDOM, onScroll, fixTitle, fixTitleStyle } = useFixTitle(list, fixTitleHeight)
        
        return {
            list,
            groupRefDOM,
            onScroll,
            fixTitle,
            fixTitleStyle
        }
    }
}
</script>

部分代码注释:

引入scroll组件,设置灵敏度最高3。监听组件的scroll事件。

ref="groupRefDOM" 获取滚动区域的DOM结构。

v-if="fixTitle" useFixTitle没有返回fixTitle时,不显示。

:style="fixTitleStyle" 标题滚动到顶部时,进行切换效果。

四、useFixTitle.js

第一部分

获取到页面结构,计算出各个分组的高度,并存入数组内,以便后续使用。

import { ref, reactive, watch, nextTick } from 'vue'

export default useFixTitle(list, fixTitleHeight) => {
    const groupRefDOM = ref(null)
    const groupHeightList = reactive([])
    
    watch(list, async () => {
        await nextTick()
        clacGroupHeight()
    })
    
    function clacGroupHeight() {
        const list = groupRefDOM.value.children
        let height = 0
        
        groupHeightList = []
        groupHeightList.push(0)
        
        for (let i = 0; i < list.length; i ++) {
            height += list[i].clientHeight
            groupHeightList.push(height)
        }
    }
    
    return {
        groupRefDOM,
        onScroll
    }
}

第二部分

响应页面滚动事件,实时计算滚动到哪个分类,并对页面上展示的分类内容进行替换。

import { ref, reactive, watch, nextTick, computed } from 'vue'

export default useFixTitle(list, fixTitleHeight) => {
    ...
    const currentIndex = ref(0)
    const ScrollY = ref(0)
    
    const fixTitle = computed(() => {
        
        if (ScrollY.value <= 0) {
            return ''
        }
        
        const _current = currentIndex.value
        return (groupHeightList.length != 0) ? list[_current].title : ''
    })
    
    function onScroll(pos) {
        const ScrollYValue = Scroll.value = pos.y
        
        for (let i = 0; i < groupHeightList.length - 1; i++) {
            if (ScrollYValue > groupHeightList[i] && ScrollYValue < groupHeightList[i+1]) {
                currentIndex.value = i
            }
        }
    }
    
    return {
        ...
        onScroll,
        fixTitle
    }
}

第三部分

顶部两个分类交替时,出现动画效果


const distance = ref(0)

const fixTitleStyle = computed(() => {
    const disValue = distance.value
    const diff = (disValue > 0 && disValue < fixTitleHeight) ? disValue - fixTitleHeight
    
    return {
        transform: `translate3d(0, ${diff}px, 0)`
    }
})

function onScroll(pos) {
    const ScrollYValue = Scroll.value = - pos.y
        
    for (let i = 0; i < groupHeightList.length - 1; i++) {
        if (ScrollYValue > groupHeightList[i] && ScrollYValue < groupHeightList[i+1]) {
            currentIndex.value = i
            distance.value = groupHeightList[i+1] - ScrollYValue
        }
    }
}