赞
踩
- <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、fontSize,titles代表每列数据的标题,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);
示例图
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。