VUE3实现table表格数据列表无缝滚动,移入暂停,移出滚动

前引
        此数据表格滚动功能是基于VUE3 + ant-design-vue实现,支持标题列数组、数据列表、各项数据列宽度、字体大小、数据为空等多参数动态配置,最大限度满足不同功能需求。

一、建立scroll.vue文件

<script setup lang="ts">
	import {
		CSSProperties,
		getCurrentInstance,
		onMounted,
		ref,
		Ref,
	} from "vue";
	import {
		Props
	} from "ant-design-vue/es/form/useForm";
	import {
		number
	} from "vue-types";

	/*保存帧动画id*/
	const requestId = ref(0);
	/*渲染数据的大容器 -- 限制高度*/
	const listBoxRef = ref();
	/*渲染数据的容器*/
	const listRef = ref();
	/*主容器*/
	const refMain = ref();
	/*移动的位置*/
	const count = ref(0);
	/*组件高度*/
	const hHeight = ref(0);
	/*引用上下文*/
	const {
		ctx: that,
		proxy: any
	} = getCurrentInstance();

	/*定义属性*/
	const props: Props = defineProps({
		titles: Array < string > ,
		sizes: Array < number > ,
		datas: Array < Array < string >> ,
        title: String ,
		fontSize: Number,
	})

	/*标题数据*/
	const titles: Ref < Array < string >> = ref([]);
	/*尺寸数据*/
	const sizes: Ref < Array < number >> = ref([]);
	/*内容数据*/
	const datas: Ref < Array < Array < string >>> = ref([]);
	/*拷贝数据列表*/
	const copyList: Ref < Array < Array < string >>> = ref([]);
	/*样式集合*/
	const titleStyles: Ref < Array < CSSProperties >> = ref([]);
	const itemStyles: Ref < Array < CSSProperties >> = ref([]);
	const subItemStyles: Ref < Array < CSSProperties >> = ref([]);
	/* 判断数据是否为空 */
	const empty = ref(true)
	/* 传入空数据描述*/
	const destitle = ref('')

	import {
		Empty
	} from 'ant-design-vue';
	const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;

	/**
	 * 开始滚动
	 */
	const start = () => {
		if (requestId.value) {
			/*有动画 先移除动画*/
			window.cancelAnimationFrame(requestId.value);
		}
		/*计算当前容器高度 能够展示多少条数据*/
		let listNum = listBoxRef.value.offsetHeight / parseInt(30)
		/*判断是否需要动画*/
		if (datas.value.length < listNum) {
			listRef.value.style.transform = 'translateY(0)';
			// that.$forceUpdate();
		} else {
			/*复制一份首次展示出来的那些数据 并 添加到数组末尾 --- 为实现滚动位置重置做铺垫*/
			const newCopyList: Ref < Array < Array < string >>> = ref([])
			copyList.value = datas.value.slice(0, listNum)
			if (copyList.value.length < listNum) {
				/*当渲染的数据较少的时候 添加空字符串补充*/
				let length = listNum - copyList.value.length
				for (let i = 0; i < length; i++) {
					newCopyList.value.unshift();
				}
			}
			datas.value.push(...[...newCopyList.value, ...copyList.value])
			// that.$forceUpdate();
			requestId.value = requestAnimationFrame(onScroll)
		}
	}

	/**
	 * 滚动方法
	 */
	const onScroll = () => {
		count.value++
		if (count.value >= parseInt(30) * (datas.value.length - copyList.value.length)) {
			/*当移动的位置大于等于当前列表的高度的时候 重置移动位置*/
			count.value = 0
		}
		if (listRef.value) {
			listRef.value.style.transform = `translateY(-${count.value}px)`;
			requestId.value = requestAnimationFrame(onScroll)
		}
	}

	/**
	 * 停止滚动
	 */
	const stop = () => {
		if (requestId.value) {
			window.cancelAnimationFrame(requestId.value);
		}
	}

	/**
	 * 鼠标移入
	 */
	const onMouseOver = () => {
		stop()
	}

	/**
	 * 鼠标移出
	 */
	const onMouseOut = () => {
		if (datas.value.length > 0) {
			start()
		}
	}

	/**
	 * 初始化
	 */
	const init = () => {
		hHeight.value = refMain.value?.clientHeight - 40;
	}

	/**
	 * 更新数据展示
	 * @param inputTitles 标题集合[]
	 * @param inputSizes 列宽度集合[]
	 * @param inputDatas 数据集合[][]
	 */
	const updateDom = (inputTitles: Array < string > , inputSizes: Array < number > , inputDatas: Array < Array < string >>
		, title) => {
		if (title) {
			empty.value = false
			destitle.value = title
		} else {
			empty.value = true
		}

		if (inputTitles.length != inputSizes.length || inputTitles.length != inputSizes.length) return;
		titles.value = [];
		sizes.value = [];
		datas.value = [];
		titleStyles.value = [];
		itemStyles.value = [];
		for (let idx = 0; idx < inputTitles.length; idx++) {
			titles.value.push(inputTitles[idx]);
			sizes.value.push(inputSizes[idx]);
			let titleStyle: CSSProperties = {
				width: '0px',
				fontSize: '12px',
				transformOrigin: '0 0',
				// whiteSpace: 'nowrap',
				transform: `scale(${props.fontSize/12})`
			}
			let itemStyle: CSSProperties = {
				width: '0px',
				// transform: 'scale(2)'
			}
			subItemStyles.value = {
				fontSize: '12px',
				transform: `scale(${props.fontSize/12})`,
				transformOrigin: '0 0',
				whiteSpace: 'nowrap',
				width: '100%',
				paddingLeft:'5px',
				textAlign:'center'
			}
			if (inputSizes[idx] != 0) {
				titleStyle.width = itemStyle.width = sizes.value[idx] + 'px';
			} else {
				titleStyle.width = itemStyle.width = '0px';
				titleStyle.flex = itemStyle.flex = 1;
			}
			titleStyles.value.push(titleStyle);
			if (idx % 2 == 1) {
				itemStyle.background = 'rgba(20, 121, 59, 0.2)';
			}
			itemStyles.value.push(itemStyle);
		}
		for (let idx = 0; idx < inputDatas.length; idx++) {
			datas.value.push(inputDatas[idx]);
		}

		start();
	}

	/**
	 * DOM元素创建完成后执行的函数
	 */
	onMounted(() => {
		init();
		// updateDom(props.titles, props.sizes, props.datas);
	})

	/**
	 * 更新数据展示
	 * @param inputTitles 标题集合[]
	 * @param inputSizes 列宽度集合[]
	 * @param inputDatas 数据集合[][]
	 */
	const update = (inputTitles: Array < string > , inputSizes: Array < number > , inputDatas: Array < Array < string >> ,
		title) => {
		updateDom(inputTitles, inputSizes, inputDatas, title);
	}

	/**
	 * 对外公开方法
	 */
	defineExpose({
		update,
	})
</script>

<template>
	<div class="cr-scroll-auto-panel">
		<div class="cr-content" ref="refMain">
			<div class="cr-head">
				<div class="cr-column cr-common-no-color" :style="titleStyles[index]" v-for="(item,index) in titles">
					{{ item }}
				</div>
			</div>
			<div ref="listBoxRef" class="cr-list-box" @mouseenter="onMouseOver" @mouseleave="onMouseOut"
				:style="{height:hHeight+'px'}">
				<ul ref="listRef" class="list">
					<li v-for="(item,index) in datas" :key="index" v-if="empty">
						<div class="cr-column" v-for="(subItem,subIndex) in item" :style="itemStyles[subIndex]">
							<span :style="subItemStyles">
								{{ subItem }}
							</span>
						</div>
					</li>
					<div v-else>
						<a-empty :image="simpleImage" :description="destitle" />
					</div>
				</ul>
			</div>
		</div>
	</div>
</template>

<style scoped lang="less">
	/*引入字体样式*/
	@import url('/@/assets/fonts/index.css');

	:deep(.ant-empty-description) {
		color: #fff !important;
	}

	/*主体容器*/
	.cr-scroll-auto-panel {
		width: 100%;
		height: 100%;
		display: flex;
		flex-direction: column;
		/*内容样式*/

		.cr-content {
			height: 100px;
			flex: 1;
			display: flex;
			flex-direction: column;
			/*标题栏样式*/

			.cr-head {
				width: 100%;
				background: rgba(25, 32, 102, 1.0);
				display: flex;
				flex-direction: row;
				color: yellow;
				font-size: 10px;
				height: 40px;
				justify-items: center;
			}

			/*列样式*/

			.cr-column {
				font-size: 9px;
				display: flex;
				flex-direction: row;
				justify-content: center;
				align-items: center;
				font-family: "AlimamaShuHeiTiBold", serif;
			}

			/*滚动表无背景色列样式*/

			.cr-common-no-color {
				background: rgba(255, 255, 255, 0);
			}

			/*list容器样式*/

			.cr-list-box {
				height: 0;
				overflow: hidden;

				/*list样式*/

				.list {
					li {
						height: 30px;
						line-height: 30px;
						color: white;
						display: flex;
						flex-direction: row;
						justify-items: center;
						align-items: center;
					}
				}
			}
		}
	}
</style>

        组件共接受五个参数,分别是titles、sizes、datas、title、fontSizetitles代表每列数据的标题,sizes是每列数据的宽度,datas是所有滚动展示数据数组,fontSize是展示字体大小,对于小于12号的字体我们做了缩放处理,不需要担心浏览器不支持,title是指数据为空时传入的标注,为空时表示有数据,不为空则会显示无数据图标。注意sizes的数组长度与titles和datas的长度要相同,否则会报错

        组件抛出了update方法,当数据更新时父组件调用update会重新渲染滚动列表数据,在vue2中更新滚动数据需要借助$forceUpdate强制更新视图,在vue3中响应式系统已经进行了重构,响应式数据会自动更新视图。

        数据滚动动画是基于window API requestAnimationFrame实现,可以更改speed速度等参数。

二、父组件调用

        在父组件中

<ScrollAuto ref="refScroll" :datas="datas" :sizes="sizes" :titles="titles" :fontSize="9" />

        数据更新时

refScroll.value.update(titles.value, sizes.value, datas.value);

示例图

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值