基于better-scroll简单封装一个滚动的组件

632 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

Html

很简单,我们的核心主体内容全部交给slot,其他的逻辑行为全部交给此组件

<template>
  <div ref="wrapper">
    <slot />
  </div>
</template>

js

由于此组件基于better-scroll(以下全部简称为BS),所以先引入BS的核心包,并按需引入其对应的插件。我们给BS添加鼠标滚动的行为

import BScroll, { BScrollInstance, Options } from '@better-scroll/core'
import MouseWheel from '@better-scroll/mouse-wheel'
BScroll.use(MouseWheel)

function _init(elm: HTMLElement | string, option: Options): BScrollInstance {
  const scroll = new BScroll(elm, { ...option })
  return scroll
}

我们创建了一个_init函数进行BS的实例化。

props

props: {
    /**
     * 1 滚动的时候会派发scroll事件,会截流。
     * 2 滚动的时候实时派发scroll事件,不会截流。
     * 3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件
     */
    probeType: {
      type: Number,
      default: 3
    },
    /**
     * 点击列表是否派发click事件
     */
    click: {
      type: Boolean,
      default: true
    },
    /**
     * 是否开启纵向滚动
     */
    scrollY: {
      type: Boolean,
      default: true
    },
    /**
     * 是否开启横向滚动
     */
    scrollX: {
      type: Boolean,
      default: false
    },
    /**
     * 是否派发滚动事件
     */
    listenScroll: {
      type: Boolean,
      default: false
    },
    /**
     * 列表的数据
     */
    data: {
      type: Array,
      default: null
    },
    /**
     * 当数据更新后,刷新scroll的延时。
     */
    refreshDelay: {
      type: Number,
      default: 20
    }
  },

setup

 setup(props, ctx) {
    const wrapper = ref<HTMLElement | null>(null) // dom
    const scroll = ref<null | BScrollInstance>(null) // BScroll
    nextTick(() => {
      scroll.value = _init(wrapper.value as HTMLElement, {
        probeType: props.probeType,
        click: props.click,
        scrollX: props.scrollX,
        scrollY: props.scrollY,
        mouseWheel: true
      })
      if (props.listenScroll) {
        scroll.value.on('scroll', (pos: any) => {
          ctx.emit('scroll', pos)
        })
      }
    })
    return { wrapper }
}   

我们在nextTick中,进行BS实例化,确保dom被挂载。

代理BS方法

为确保组件整洁,父组件使用时不去调用底层方法,我们对BS方法进行代理

const disable = () => {
  // 代理better-scroll的disable方法
  scroll.value && scroll.value.disable()
}
const enable = () => {
  // 代理better-scroll的enable方法
  scroll.value && scroll.value.enable()
}
const refresh = () => {
  // 代理better-scroll的refresh方法
  scroll.value && scroll.value.refresh()
}
function scrollTo() {
  // 代理better-scroll的scrollTo方法
  scroll.value &&
    scroll.value.scrollTo.apply(scroll.value, arguments as any)
}
function scrollToElement() {
  // 代理better-scroll的scrollToElement方法
  scroll.value &&
    scroll.value.scrollToElement.apply(scroll.value, arguments as any)
}
watch(props.data, () => {
  setTimeout(() => {
    refresh()
  }, props.refreshDelay)
})


return { wrapper, disable, enable, refresh, scrollTo, scrollToElement }

source code

<template>
  <div ref="wrapper">
    <slot />
  </div>
</template>

<script lang='ts'>
import { defineComponent, ref, nextTick, watch } from 'vue'
import BScroll, { BScrollInstance, Options } from '@better-scroll/core'
import MouseWheel from '@better-scroll/mouse-wheel'
BScroll.use(MouseWheel)

function _init(elm: HTMLElement | string, option: Options): BScrollInstance {
  const scroll = new BScroll(elm, { ...option })
  return scroll
}

export default defineComponent({
  name: 'Scroller',
  props: {
    /**
     * 1 滚动的时候会派发scroll事件,会截流。
     * 2 滚动的时候实时派发scroll事件,不会截流。
     * 3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件
     */
    probeType: {
      type: Number,
      default: 3
    },
    /**
     * 点击列表是否派发click事件
     */
    click: {
      type: Boolean,
      default: true
    },
    /**
     * 是否开启纵向滚动
     */
    scrollY: {
      type: Boolean,
      default: true
    },
    /**
     * 是否开启横向滚动
     */
    scrollX: {
      type: Boolean,
      default: false
    },
    /**
     * 是否派发滚动事件
     */
    listenScroll: {
      type: Boolean,
      default: false
    },
    /**
     * 列表的数据
     */
    data: {
      type: Array,
      default: null
    },
    /**
     * 当数据更新后,刷新scroll的延时。
     */
    refreshDelay: {
      type: Number,
      default: 20
    }
  },
  setup(props, ctx) {
    const wrapper = ref<HTMLElement | null>(null) // dom
    const scroll = ref<null | BScrollInstance>(null) // BScroll
    nextTick(() => {
      scroll.value = _init(wrapper.value as HTMLElement, {
        probeType: props.probeType,
        click: props.click,
        scrollX: props.scrollX,
        scrollY: props.scrollY,
        mouseWheel: true
      })
      if (props.listenScroll) {
        scroll.value.on('scroll', (pos: any) => {
          ctx.emit('scroll', pos)
        })
      }
    })
    const disable = () => {
      // 代理better-scroll的disable方法
      scroll.value && scroll.value.disable()
    }
    const enable = () => {
      // 代理better-scroll的enable方法
      scroll.value && scroll.value.enable()
    }
    const refresh = () => {
      // 代理better-scroll的refresh方法
      scroll.value && scroll.value.refresh()
    }
    function scrollTo() {
      // 代理better-scroll的scrollTo方法
      scroll.value &&
        scroll.value.scrollTo.apply(scroll.value, arguments as any)
    }
    function scrollToElement() {
      // 代理better-scroll的scrollToElement方法
      scroll.value &&
        scroll.value.scrollToElement.apply(scroll.value, arguments as any)
    }
    watch(props.data, () => {
      setTimeout(() => {
        refresh()
      }, props.refreshDelay)
    })
    return { wrapper, disable, enable, refresh, scrollTo, scrollToElement }
  }
})
</script>

总结

借鉴黄轶老师的better-scroll的vue2封装原理进行的vue3版本的二次封装。

这个组件的源码对于<script setup>已经有点老了。希望大家可以对源码进行魔改,因为vue3的好多api,例如defineProps, withDefault, defineExpose ···都可以应用上,确保组件的安全和规范