在vue3中想使用滚动列表插件的时候发现,vue-seamless-scroll并不支持vue3版本。

所以这里介绍一下适配vue3版本的滚动列表插件,采用的这位前辈的源代码,技术使用的是tsx。

完整代码

import { computed, CSSProperties, defineComponent, onBeforeMount, onMounted, ref, watch } from 'vue'const props = {// 是否开启自动滚动modelValue: {type: Boolean,default: true,},// 原始数据列表list: {type: Array,required: true,},// 步进速度,step 需是单步大小的约数step: {type: Number,default: 1,},// 开启滚动的数据量limitScrollNum: {type: Number,default: 1,},// 是否开启鼠标悬停hover: {type: Boolean,default: false,},// 控制滚动方向direction: {type: String,default: 'up',},// 单步运动停止的高度singleHeight: {type: Number,default: 0,},// 单步运动停止的宽度singleWidth: {type: Number,default: 0,},// 单步停止等待时间(默认值 1000ms)singleWaitTime: {type: Number,default: 1000,},// 是否开启 rem 度量isRemUnit: {type: Boolean,default: false,},// 开启数据更新监听isWatch: {type: Boolean,default: true,},// 动画时间delay: {type: Number,default: 0,},// 动画方式ease: {type: [String, Object],default: 'ease-in',},// 动画循环次数,-1表示一直动画count: {type: Number,default: -1,},// 拷贝几份滚动列表copyNum: {type: Number,default: 1}
}const Vue3SeamlessScroll = defineComponent({name: 'vue3SeamlessScroll',inheritAttrs: false,props,emits: ['stop', 'count'],setup(props, { slots, emit, attrs }) {const scrollRef = ref(null)const slotListRef = ref<HTMLBodyElement>()const realBoxRef = ref<HTMLBodyElement>()const reqFrame = ref<number>(0)const singleWaitTimeout = ref(0)const realBoxWidth = ref(0)const realBoxHeight = ref(0)const xPos = ref(0)const yPos = ref(0)const isHover = ref(false)const _count = ref(0);// 如果列表长度大于最小滚动长度,就可以发生滚动const isScroll = computed(() => props.list!.length >= props.limitScrollNum)const realBoxStyle = computed<CSSProperties>(() => {return {width: realBoxWidth.value ? `${realBoxWidth.value}px` : 'auto',transform: `translate(${xPos.value}px,${yPos.value}px)`,// @ts-ignoretransition: `all ${typeof props.ease === 'string' ? props.ease : 'cubic-bezier(' + props.ease.x1 + ',' + props.ease.y1 + ',' + props.ease.x2 + ',' + props.ease.y2 + ')'} ${props.delay}ms`,overflow: 'hidden'}})const isHorizontal = computed(() => props.direction == 'left' || props.direction == 'right')const floatStyle = computed<CSSProperties>(() => {return isHorizontal.value? { float: 'left', overflow: 'hidden' }: { overflow: 'hidden' }})const baseFontSize = computed(() => {return props.isRemUnit? parseInt(globalThis.window.getComputedStyle(globalThis.document.documentElement, null).fontSize): 1})const realSingleStopWidth = computed(() => props.singleWidth * baseFontSize.value)const realSingleStopHeight = computed(() => props.singleHeight * baseFontSize.value)const step = computed(() => {let singleStep: numberlet _step = props.stepif (isHorizontal.value) {singleStep = realSingleStopWidth.value} else {singleStep = realSingleStopHeight.value}if (singleStep > 0 && singleStep % _step > 0) {console.error("如果设置了单步滚动,step需是单步大小的约数,否则无法保证单步滚动结束的位置是否准确。~~~~~")}return _step})function cancle() {cancelAnimationFrame(reqFrame.value);reqFrame.value = 0;}function move() {cancle();if (isHover.value || !isScroll.value || _count.value === props.count) {emit('stop', _count.value);_count.value = 0;return;}reqFrame.value = requestAnimationFrame(function () {const h = realBoxHeight.value / 2const w = realBoxWidth.value / 2let { direction, singleWaitTime } = propsif (direction === 'up') {if (Math.abs(yPos.value) >= h) {yPos.value = 0_count.value += 1emit('count', _count.value)}yPos.value -= step.value} else if (direction === 'down') {if (yPos.value >= 0) {yPos.value = h * -1_count.value += 1emit('count', _count.value)}yPos.value += step.value} else if (direction === 'left') {if (Math.abs(xPos.value) >= w) {xPos.value = 0_count.value += 1emit('count', _count.value)}xPos.value -= step.value} else if (direction === 'right') {if (xPos.value >= 0) {xPos.value = w * -1_count.value += 1emit('count', _count.value)}xPos.value += step.value}if (singleWaitTimeout.value) {clearTimeout(singleWaitTimeout.value)}if (!!realSingleStopHeight.value) {if (Math.abs(yPos.value) % realSingleStopHeight.value < step.value) {singleWaitTimeout.value = setTimeout(() => {move()}, singleWaitTime)} else {move()}} else if (!!realSingleStopWidth.value) {if (Math.abs(xPos.value) % realSingleStopWidth.value < step.value) {singleWaitTimeout.value = setTimeout(() => {move()}, singleWaitTime)} else {move()}} else {move()}});}function initMove() {if (isHorizontal.value) {let slotListWidth = slotListRef?.value?.offsetWidth;slotListWidth = slotListWidth! * 2 + 1;realBoxWidth.value = slotListWidth;}if (isScroll.value) {realBoxHeight.value = realBoxRef?.value?.offsetHeight!;if (props.modelValue) {move();}} else {cancle();yPos.value = xPos.value = 0;}}function startMove() {isHover.value = false;move();}function stopMove() {isHover.value = true;if (singleWaitTimeout.value) {clearTimeout(singleWaitTimeout.value);}cancle();}// okconst hoverStop = computed(() => props.hover && props.modelValue && isScroll.value)function reset() {cancle();isHover.value = false;initMove();}watch(() => props.list,() => {if (props.isWatch) {reset();}},{deep: true,});watch(() => props.modelValue,(newValue) => {if (newValue) {startMove();} else {stopMove();}});watch(() => props.count, (newValue) => {if (newValue !== 0) {startMove();}})onBeforeMount(() => {cancle();clearTimeout(singleWaitTimeout.value);});onMounted(() => {initMove()})const { default: $default, html } = slotsconst copyNum = new Array(props.copyNum).fill(null)return () => (<div ref={scrollRef} class={attrs.class}><div ref={realBoxRef} style={realBoxStyle.value} onMouseenter={() => {if (hoverStop.value) {stopMove();}}} onMouseleave={() => {if (hoverStop.value) {startMove();}}}><div ref={slotListRef} style={floatStyle.value}>{$default!()}</div>{isScroll ? copyNum.map(() => {if (html && typeof html === 'function') {return (<div style={floatStyle.value}>{html()}</div>)} else {return (<div style={floatStyle.value}>{$default!()}</div>)}}) : null}</div ></div >)}
})export default Vue3SeamlessScroll

组件结构

首先看看在defineComponent中返回的部分:

// 通过结构赋值拿到slots的 default 和 html 属性
const { default: $default, html } = slots

以前我并没有使用过这个html属性,于是去查了一下slots的类型定义:

export declare type Slots = Readonly<InternalSlots>;declare type InternalSlots = {[name: string]: Slot | undefined;
};
export declare type Slot = (...args: any[]) => VNode[];

可以看到slots是一个只读的对象,key为字符串,value为一个返回VNode数组的函数。这也是为什么我们可以很方便的使用匿名插槽、具名插槽等功能,默认情况下这里的name的值为default,通过slots.default!()便可以拿到匿名插槽中的VNode。

而html部分,是在父组件也为tsx文件时,向子组件传递进来了一个html结构的函数,可以通过判断html是否为function来进行渲染。

const copyNum = new Array(props.copyNum).fill(null)

props.copyNum的作用是声明待滚动列表需要拷贝的次数,fill方法是将数组中的所有元素都用null值代替,假设copyNum = 3,那么通过这个操作可以生成[null, null, null]数组。

平时使用的时候将文件设置为无限循环滚动的话就不必使用这个功能

return () => (<!-- attrs值为是父组件中没有通过bind绑定的数据 --><div ref={scrollRef} class={attrs.class}><!-- 作为滚动列表item的父容器,设置transition等动画样式 --><div ref={realBoxRef} style={realBoxStyle.value} <!-- 增加两个原生事件,负责判断鼠标悬停 -->onMouseenter={() => {if (hoverStop.value) {stopMove();}}} onMouseleave={() => {if (hoverStop.value) {startMove();}}}><!-- 设置具体的子滚动item --><div ref={slotListRef} style={floatStyle.value}>{$default!()}</div><!-- 这里也是设置具体的子滚动item,设置的是拷贝列表的数据 -->{  isScroll ? copyNum.map(() => {if (html && typeof html === 'function') {return (<div style={floatStyle.value}>{html()}</div>)} else {return (<div style={floatStyle.value}>{$default!()}</div>)}}) : null}</div></div>
)

所以这个组件大致的html结构为:

|外部根容器
|
|————滚动item的父容器,设置动画样式
|
|————————滚动item

结构功能

结合组件结构往下介绍,首先从上向下介绍组件结构中出现过的方法

realBoxStyle

通过computed动态返回css对象形式的样式

const realBoxStyle = computed<CSSProperties>(() => {return {width: realBoxWidth.value ? `${realBoxWidth.value}px` : 'auto',transform: `translate(${xPos.value}px,${yPos.value}px)`,// @ts-ignoretransition: `all ${typeof props.ease === 'string' ? props.ease : 'cubic-bezier(' + props.ease.x1 + ',' + props.ease.y1 + ',' + props.ease.x2 + ',' + props.ease.y2 + ')'} ${props.delay}ms`,overflow: 'hidden'}})

hoverStop

如果想要执行鼠标悬停的功能,首先需要确保props传递进来的hover为true。

所以如果开启了悬停功能,hoverStop的值将固定为true,这是为了确保后面的onMouseEnter事件顺利的恒定执行

const hoverStop = computed(() => props.hover && props.modelValue && isScroll.value
)

stopMove

暂停滚动效果的时候,就是鼠标悬停的时候,所以负责记录鼠标isHover的值将会变为true

同时,毕竟这是一个滚动的列表,所以需要清除定时器。之后会在具体的滚动方法中介绍设置定时器

function stopMove() {isHover.value = true;if (singleWaitTimeout.value) {clearTimeout(singleWaitTimeout.value);}cancle();
}

滚动

在进行滚动部分的代码编写之前,再次看看组件的结构。

具体滚动的是滚动item,所以在具体的执行滚动操作之前,我们需要先对他的父元素进行一些初始化。

initMove

function initMove() {// 设置滚动列表的宽度if (isHorizontal.value) {// 获取到每一个item的宽度let slotListWidth = slotListRef?.value?.offsetWidth;slotListWidth = slotListWidth! * 2 + 1;realBoxWidth.value = slotListWidth;}// 判断是否可以滚动 如果可以滚动,就执行内部的move方法if (isScroll.value) {realBoxHeight.value = realBoxRef?.value?.offsetHeight!;if (props.modelValue) {move();}} else {cancle();yPos.value = xPos.value = 0;}
}

由于可以通过props设置滚动的方向,所以在判断是否滚动之前,我们可以先判断一下它的具体滚动方向:

const isHorizontal = computed(() => props.direction == 'left' || props.direction == 'right'
)

如果列表长度大于最小滚动长度,就可以发生滚动:

const isScroll = computed(() => props.list!.length >= props.limitScrollNum)

move

function move() {// 和使用定时器一样,在设置运动操作之前需要清除上一次的运动cancle();// 手动暂停,_count负责记录已经滚动过去了多少个itemif (isHover.value || !isScroll.value || _count.value === props.count) {emit('stop', _count.value);_count.value = 0;return;}reqFrame.value = requestAnimationFrame(function () {const h = realBoxHeight.value / 2const w = realBoxWidth.value / 2let { direction, singleWaitTime } = props// 结合realBoxStop来看,transform(xPos,yPos),所以向上移动的话,yPos是 < 0的if (direction === 'up') {// 如果上移过多,就会回到原点,同时滚动次数+1if (Math.abs(yPos.value) >= h) {yPos.value = 0_count.value += 1emit('count', _count.value)}// 每次滚动单步距离yPos.value -= step.value// 下同} else if (direction === 'down') {if (yPos.value >= 0) {yPos.value = h * -1_count.value += 1emit('count', _count.value)}yPos.value += step.value} else if (direction === 'left') {if (Math.abs(xPos.value) >= w) {xPos.value = 0_count.value += 1emit('count', _count.value)}xPos.value -= step.value} else if (direction === 'right') {if (xPos.value >= 0) {xPos.value = w * -1_count.value += 1emit('count', _count.value)}xPos.value += step.value}if (singleWaitTimeout.value) {clearTimeout(singleWaitTimeout.value)}// !!的作用就是将所有的值转换为boolean类型// !!1 = true  !!0 = false      !!null = false   !!'' = falseif (!!realSingleStopHeight.value) {if (Math.abs(yPos.value) % realSingleStopHeight.value < step.value) {singleWaitTimeout.value = setTimeout(() => {move()}, singleWaitTime)} else {move()}} else if (!!realSingleStopWidth.value) {if (Math.abs(xPos.value) % realSingleStopWidth.value < step.value) {singleWaitTimeout.value = setTimeout(() => {move()}, singleWaitTime)} else {move()}} else {move()}});
}

数据刷新

在做完基本滚动功能后,可以考虑一下数据处理的问题

比如这里,我们可以监测list数值的变化,同时开启deep: true开启深度监测

function reset() {cancle();isHover.value = false;initMove();
}watch(() => props.list,() => {if (props.isWatch) {reset();}},{deep: true,}
);

tsx实现适配vue3的滚动列表插件相关推荐

  1. 用Vue来实现音乐播放器(十六):滚动列表的实现

    滚动列表是一个基础组件  他是基于scroll组件实现的 在base文件夹下面创建一个list-view文件夹 里面有list-view.vue组件 <template><!-- 当 ...

  2. 踩坑记15 动态路由 router.options.routes未更新 | vue升级 element-plus未适配vue3.2.x | vite glob导入动态加载组件,不能使用别名alias

    2021.8.12 坑50(vue-router4.addRoute().router.options.routes未更新):进行动态权限获取菜单的设置,使用了addRoute()来添加路由,但是ro ...

  3. fullPage教程 -- 整屏滚动效果插件 fullpage详解

    为什么80%的码农都做不了架构师?>>>    本文为 H5EDU 机构官方 HTML5培训 教程,主要介绍:fullPage教程 -- 整屏滚动效果插件 fullpage详解 1. ...

  4. js Grid - 列表插件

     js Grid - 列表插件     1)Sponsor Flip Wall With jQuery & CSS一个非常不错的显示数据到网格里的插件. 点击后,缩略图会翻转,然后显示更多信息 ...

  5. 微信小程序实现无限滚动列表

    实现方式是利用小程序原声组件swiper,方向设置为纵向 :vertical='true'设置同时显示的滑块数量:display-multiple-items='4'设置自动轮播:autoplay:' ...

  6. s插件——SlimScroll滚动美化插件

    SlimScroll滚动美化插件 下载:http://www.jq22.com/demo/jQuery-slimScroll-141223223505/jquery.slimscroll.js DEM ...

  7. 功能强大的滚动播放插件JQ-Slide

    查看效果:http://keleyi.com/keleyi/phtml/jqplug/4.htm JQ-Slide插件功能强大,滚动方式自由多样 全部滚动方式 方式一 方式二 方式三 方式四 方式五  ...

  8. android 滚动列表框,建立滚动列表框

    另一个可代替一组单选按钮及复选框的是滚动列表框(见图6.9).使用滚动列表框,你可以建立一个选项列表,用户可以从中选择一个或多个选项.你可以使用建立下拉式列表框的标识符来建立一个滚动列表框,只是使用不 ...

  9. jQuery刻度尺滚动滑块插件

    为什么80%的码农都做不了架构师?>>>    jQuery刻度尺滚动滑块插件 需要用到一个刻度尺插件,网上找来找去都是那几种,所以用jQuery自己写了一个. <!docty ...

  10. vue 实现表格循环滚动 vue-seamless-scroll插件的安装与使用

    vue实现循环滚动 vue-seamless-scroll插件的安装与使用 1. 安装 2. 引入 3. 直接上代码 4. 结果 1. 安装 npm install vue-seamless-scro ...

最新文章

  1. 在应用程序中宿主MEF
  2. 蓝桥杯抽卡游戏c语言,取球游戏——第三届蓝桥杯省赛C语言A组第10题
  3. 小五思科技术学习笔记之SSH
  4. reactjs redux入门完整版示例:store reducer getState dispatch subscribe action
  5. 《中餐厅》弹幕数据分析,我不要你觉得,我只要我觉得!
  6. android ble 实现自动连接,Android:自动重新连接BLE设备
  7. Linux下使用Speedtest测试网速
  8. springcloud灰度发布实现方案
  9. poj 1797 HeavyTransportation——最小边的最大值
  10. android自定义队列,Android 消息机制(一)消息队列的创建与循环的开始 Looper与MessageQueue...
  11. 如何简洁优雅地实现Kubernetes的服务暴露
  12. PCL中把txt文件转换成.pcd文件(很简单)
  13. Android中如何使用Intent在Activity之间传递对象[使用Serializable或者Parcelable]
  14. 怎么查看个人CSDN账号积分-最靠谱!
  15. dscms源码分析笔记
  16. 电商自营藏猫腻 苏宁国美京东的套路谁最深?
  17. 【转载】springboot集成SMS发送短信
  18. 2019数字音乐市场年度回顾,QQ音乐全面领先
  19. html文档绝对引用,excel中的绝对引用怎么用 excel中引用绝对引用是哪个键
  20. 如何区分网线是几类的_5类、6类网线双绞线如何区分又怎么样使用?

热门文章

  1. 【计算机网络】物理层 : 香农定理 ( 噪声 | 信噪比 | 香农定理 | “香农定理“公式 | “香农定理“ 计算示例 | “奈氏准则“ 与 “香农定理“ 对比 与 计算示例)★
  2. PS调出怀旧雨中特写的非主流照片
  3. 管理系统中计算机应用真题及答案文档,2013年4月管理系统中计算机应用真题及答案...
  4. STM32单片机的学习方法(方法大体适用所有开发版入门)
  5. ttl接地是高电平还是低电平_说明图3.12中各门电路的输出是高电平还是低电平。已知它们都是74HC系列的CMOS电路 简单的逻辑门电路 判断各门电路...
  6. js每日一题(11)
  7. tensorflow聊天机器人python实现_用 TensorFlow 做个聊天机器人
  8. 地震点、火点和气候数据收集
  9. 一文理解完美二叉树, 完全二叉树和完满二叉树
  10. Python项目实战:爬取糗事百科最热门的内涵搞笑段子