基於svelte3自定義虛擬滾動條元件svelte3-scrollbar

xiaoyan2017發表於2022-05-09

前段時間有給大家分享兩個svelte3.x系列元件。今天繼續分享一個最新寫的svelte3-scrollbar虛擬美化系統滾動條元件。

svelte-layer:一款svelte3.x輕量級PC端彈窗元件
svelte-popup:一款svelte3.x移動端彈層元件

svelte-scrollbar 一款輕量級網頁自定義美化系統滾動條元件。支援原生滾動條、自動隱藏、垂直+水平滾動、自定義滾動條大小/顏色/間隙等功能。

在lib目錄下新建一個Scrollbar元件。

引入元件

在需要用到滾動條功能的頁面引入外掛。

import Scrollbar from '$lib/Scrollbar'

快速呼叫

通過如下方式呼叫,即可快速生成一個虛擬化滾動條。

<!-- //原生滾動條 -->
<Scrollbar native>
    <div>自定義內容資訊。</div>
</Scrollbar>

<!-- //自動隱藏 -->
<Scrollbar autohide={true}>
    <div>自定義內容資訊。</div>
</Scrollbar>

<!-- //水平滾動(支援滾輪滑動) -->
<Scrollbar mousewheel>
    <div>自定義內容資訊。</div>
</Scrollbar>

<!-- //自定義高度/最大高度 -->
<Scrollbar height="200" maxHeight="350">
    <div>自定義內容資訊。</div>
</Scrollbar>

<!-- //自定義大小/間隙/顏色 -->
<Scrollbar size="10px" gap="5" color="#09f">
    <div>自定義內容資訊。</div>
</Scrollbar>

引數配置

svelte-scrollbar支援如下引數配置。

<script>
    // 是否開啟原生滾動條
    export let native = false
    // 是否自動隱藏滾動條
    export let autohide = false
    // 滾動條尺寸
    export let size = undefined
    // 滾動條顏色
    export let color = ''
    // 滾動條層疊
    export let zIndex = null
    // 滾動條區域高度
    export let height = undefined
    // 滾動條區域最大高度
    export let maxHeight = undefined
    // 滾動條間隙
    export let gap = 0
    // 是否開啟水平滾輪滾動控制
    export let mousewheel = false
    
    ...
</script>

模板及邏輯部分

<div class="vui__scrollbar" bind:this={el} on:mouseenter={handleMouseEnter} on:mouseleave={handleMouseLeave}>
    <div class="vscroll__wrap" class:hidenative={!bool(native)} bind:this={wrap} on:scroll={handleScroll} on:mousewheel={handleMouseWheel} style="{wrapStyle}">
        <slot />
    </div>
    <div class="vscroll__bar vertical" class:ishide={!data.isShowBar} on:mousedown={e => handleClickTrack(e, 0)} >
        <div class="vscroll__thumb" bind:this={barY} style="background: {color}; height: {data.barHeight}px; width: {addUnit(size)}" on:mousedown={e => handleDragThumb(e, 0)}></div>
    </div>
    <div class="vscroll__bar horizontal" class:ishide={!data.isShowBar} on:mousedown={e => handleClickTrack(e, 1)}>
        <div class="vscroll__thumb" bind:this={barX} style="background: {color}; width: {data.barWidth}px; height: {addUnit(size)}" on:mousedown={e => handleDragThumb(e, 1)}></div>
    </div>
</div>

<script>
    /**
     * @Desc     svelte3虛擬滾動條元件svelte-scrollbar
     * @Time     andy by 2022-05
     * @About    Q:282310962  wx:xy190310
     */
     
    // ...

    import { onMount, afterUpdate, createEventDispatcher, tick } from 'svelte'
    const dispatch = createEventDispatcher()

    import util from './util'

    $: data = {
        // 滾動條寬度
        barWidth: 0,
        // 滾動條高度
        barHeight: 0,
        // 滾動條水平偏移率
        ratioX: 1,
        // 滾動條垂直偏移率
        ratioY: 1,
        // 滑鼠是否按住滾動條
        isTaped: false,
        // 滑鼠是否懸停於滾動區域
        isHover: false,
        // 顯示滾動條
        isShowBar: !bool(autohide)
    }

    const bool = (boolean) => JSON.parse(boolean) ? true : false
    const addUnit = (val) => val ? parseInt(val) + 'px' : null

    let observeTimer = null
    let c = {}
    // 滾動條物件
    let el
    let wrap
    let barX
    let barY

    $: wrapStyle = `height: ${addUnit(height)}; max-height: ${addUnit(maxHeight)}`
    $: GAP = addUnit(gap)

    onMount(() => {
        console.log('監聽滾動條開啟...')

        updated()

        let observer = new MutationObserver(mutation => {
            updated()
        })

        observer.observe(wrap, {
            attributes: true,
            childList: true,
            subtree: true,
            attributeFilter: [
                'style', 'class'
            ]
        })

        window.addEventListener('resize', util.throttle(updated))

        return () => {
            observer.disconnect()
            window.removeEventListener('resize', updated)
            console.log('監聽滾動條關閉...')
        }
    })

    afterUpdate(() => {
        // console.log('監聽dom更新...')
    })

    // 滑鼠滑入
    function handleMouseEnter() {
        data.isHover = true
        data.isShowBar = true
        updated()
    }

    // 滑鼠滑出
    function handleMouseLeave() {
        data.isHover = false
        if(!data.isTaped && bool(autohide)) {
            data.isShowBar = false
        }
    }

    // 拖動滾動條
    function handleDragThumb(e, index) {
        // ...
    }

    // 點選滾動條插槽
    function handleClickTrack(e, index) {
        if(index == 0) {
            wrap.scrollTop = (Math.abs(e.target.getBoundingClientRect().top - e.clientY) - barY.offsetHeight / 2) * data.ratioY
            barY.style.transform = `translateY(${wrap.scrollTop / data.ratioY}px)`
        }else {
            wrap.scrollLeft = (Math.abs(e.target.getBoundingClientRect().left - e.clientX) - barX.offsetWidth / 2) * data.ratioX
            barX.style.transform = `translateX(${wrap.scrollLeft / data.ratioX}px)`
        }
    }

    // 更新滾動區
    async function updated() {
        // ...
    }

    // 滑鼠滾動事件
    function handleScroll(e) {
        let target = e.target
        let status
        if(target.scrollTop == 0) {
            status = 'top' // 滾動至頂部
        }else if(target.scrollTop + target.offsetHeight >= target.scrollHeight) {
            status = 'bottom' // 滾動至底部
        }

        /**
         * 父元件呼叫 const { target, status, scrollTop, scrollLeft } = e.detail
         */
        dispatch('scroll', {
            target, // 滾動物件
            status, // 滾動狀態(記錄滾動位置)
            scrollTop: target.scrollTop,
            scrollLeft: target.scrollLeft
        })
        updated()
    }

    // 控制滾輪水平滾動
    function handleMouseWheel(e) {
        if(!bool(mousewheel)) return
        e.preventDefault()
        if(wrap.scrollWidth > wrap.offsetWidth) {
            wrap.scrollLeft += e.deltaY
        }
    }

    // 滾動到一組特定座標
    export async function scrollTo(arg1, arg2) {
        await tick()

        if(typeof arg1 == 'object') {
            wrap.scrollTo(arg1)
        }else if(!isNaN(arg1) && !isNaN(arg2)) {
            wrap.scrollTo(arg1, arg2)
        }
    }

    // 設定滾動條到頂部的距離
    export async function setScrollTop(value) {
        await tick()

        wrap.scrollTop = value == 'top' ? 0 : value == 'bottom' ? wrap.scrollHeight : parseInt(value)
        barY.style.transform = `translateY(${wrap.scrollTop / data.ratioY}px)`
    }

    // 設定滾動條到左邊的距離
    export async function setScrollLeft(value) {
        await tick()

        wrap.scrollLeft = value == 'left' ? 0 : value == 'right' ? wrap.scrollWidth : parseInt(value)
        barX.style.transform = `translateX(${wrap.scrollLeft / data.ratioX}px)`
    }
</script>

<style>
    @import 'Scrollbar.scss';
</style>

另外還支援動態監聽scroll事件。

<Scrollbar on:scroll={handleScroll}>
    <div>自定義內容資訊。</div>
</Scrollbar>

<script>
    // 監聽滾動事件
    function handleScroll(e) {
        const { target, status, scrollTop, scrollLeft } = e.detail
        scrollTopVal = scrollTop
        scrollLeftVal = scrollLeft

        // 判斷滾動狀態(方法1)
        /*if(scrollTop == 0) {
            scrollStatus = '滾動至頂部'
        }else if(scrollTop + target.offsetHeight >= target.scrollHeight) {
            scrollStatus = '滾動底部'
        }else {
            scrollStatus = '滾動中'
        }*/

        // // 判斷滾動狀態(方法2)
        if(status == 'top') {
            scrollStatus = '滾動至頂部'
        }else if(status == 'bottom') {
            scrollStatus = '滾動底部'
        }else {
            scrollStatus = '滾動中'
        }
    }
</script>

OK,以上就是svelte.js自定義系統美化滾動條元件的分享。後續還會分享一個svelte桌面端例項專案。

最後附上一個svelte3聊天實戰專案

svelte-chat基於svelte3.x仿微信APP聊天專案

目前svelte.js穩定性還可以,生態也逐步完善,期待更多的開發小夥伴一起參與進來。

相關文章