共计 10142 个字符,预计需要花费 26 分钟才能阅读完成。
Vue
实现列表自动滚动 (纯与原生方式)
源码放在最后!
1. 效果展示:
2. 功能说明:
该滚动可能存在的 Bug:
- 1. 如果你写的大屏不是使用的接口轮询的方式可能会存在也页面空白的情况 (需要手动刷新才能触发列表滚动),因为我使用的是监听数据的变化然后做出响应,一般来说写大屏都会有个数据更新的机制。这一点需要你对源码进行改动。
3. 原理分析:
1. 核心数据和变量:
CityData
:包含了所有的表格数据,是一个二维数组,每一行数据代表一个城市的信息。CURRENTDATA
:存储当前显示在屏幕上的行数据,动态更新以实现滚动效果。COLUMNHEIGHT
:存储每一行的高度,在滚动时通过调整前几行的高度来实现滑动效果。CURRENTINDEX
:表示当前显示的首行索引,在滚动过程中不断递增,最终通过循环回到起始行。
2. 数据初始化:
当组件挂载时,会通过 onMounted
钩子初始化表格数据。关键步骤包括:
- 计算每列的宽度
HandelHeaderAverageWidth
和HandelColumnAverageWidth
。 - 计算行的高度
HandelAverageHeight
。 - 格式化数据并初始化
CityData
,为每一行数据添加索引列,并根据Alluserdata
动态填充内容。
3. 高度变化实现 (核心!):
滚动动画的关键在于每行的高度如何变化。为了模拟滚动,我们让行逐渐“消失”并再“出现”。具体实现步骤如下:
-
初始行高度 :表格中的每一行都有固定的高度,在初始化时通过
HandelAverageHeight
计算每行的平均高度,并将结果存储在COLUMNHEIGHT
中。const HandelAverageHeight = () => { const AverageHeight = height.value / columnNumber.value; AverageHeighT.value = AverageHeight; const TotalCount = 23; COLUMNHEIGHT.value = new Array(TotalCount).fill(AverageHeight); };
例如,假设每行的高度为
50px
,这意味着表格中的所有行在初始时都是统一高度的。 -
高度变化模拟滚动 :在开始动画时,
StartAnimation
函数负责将某些行的高度设置为0
,模拟这些行滚动出可视区域。COLUMNHEIGHT.value.splice(0, MoveNumber, ...new Array(MoveNumber).fill(0));
这个
splice
操作将COLUMNHEIGHT
数组中前MoveNumber
行的高度设置为0
,因此这些行会逐渐缩小到不可见,产生滚动的效果。当行的高度变为
0
时,浏览器会根据 CSS 的transition
动画特性让该行逐渐收缩,这就是为什么行从视野中“滑动”出去,而不是瞬间消失。
4. 数据无限循环的过程 :
为了让滚动效果无限循环,需要对 CityData
(存储所有表格数据)和 CURRENTDATA
(存储当前显示的行数据)进行操作,使得数据能够循环显示。
-
当前索引控制 :通过
CURRENTINDEX
控制当前显示的起始行。在每次动画开始时,会计算新的起始行索引,并根据这个索引来更新当前显示的数据。const alldataLenght = CityData.value.length; const MoveNumber = 1; const index = CURRENTINDEX.value;
CURRENTINDEX
会随着每次滚动递增,当它达到数据集的长度时,循环回到开头,确保表格数据可以无限滚动。 -
数据循环拼接 :为了避免滚动到最后几行时的“断层”,
StartAnimation
函数会将CityData
从当前索引开始的数据提取出来,并将前面的数据拼接到后面,形成一个无缝连接的效果。const rowdata = cloneDeep(CityData.value); const rows = rowdata.slice(index); rows.push(...rowdata.slice(0, index));
比如当表格已经滚动到倒数第二行时,
slice(index)
提取出剩下的最后几行,而slice(0, index)
则从头部提取最开始的几行,并将它们拼接在一起。这使得滚动能够无缝连接。这样,在
CURRENTDATA
中,数据始终是连续的,模拟了数据无限循环滚动的效果。
5. 递归定时器控制滚动:
滚动效果通过递归调用 StartAnimation
来实现,每次滚动后等待一段时间,然后再执行下一次滚动。这就像一个“无尽的循环”,使表格持续滚动。
-
延迟机制 :在每次滚动之前,会通过
await
等待一段时间,控制滚动的速度。await new Promise(resolve => setTimeout(resolve, awaitTime));
这里的
awaitTime
控制了滚动的速度,500ms 表示每次滚动间隔 0.5 秒。 -
高度动画延迟 :滚动分为两个阶段,先将行的高度逐渐设置为
0
,然后让下一行进入视野。这个过程通过两次setTimeout
来控制。await new Promise(resolve => setTimeout(resolve, 800 - awaitTime));
这个延迟的机制让滚动变得平滑,防止表格中的数据瞬间跳跃。
4. 完整源码
首先你要有 pinia、loadsh、uuid 这些库
这是我的文件目录,有点不规范,你可以自己修改 hook 可以抽取一下!
PlanList.vue
{{item}}
`;
} else {
columnIndex.value[i] = `
`;
}
// 生成数据行
for (let i = 0; i
`;
// 生成数据行
for (let j = 0; j ${text}`);
} else {
CityData.value[i].push(`
`);
}
}
}
}
Handeldata();
});
onMounted(() => {
StartAnimation()
Handeldata()
HandelHeaderAverageWidth()
HandelColumnAverageWidth()
HandelAverageHeight()
})
init.ts
import { ref, onMounted } from 'vue'
const init = (id: string) => {
const width = refnumber>(0)
const height = refnumber>(0)
onMounted(() => {
const container = document.getElementById(id) as HTMLElement | null
if (container) {
const domwidth = container.clientWidth
const domheight = container.clientHeight
width.value = domwidth
height.value = domheight
}
})
return {
width,
height
}
}
export default init
PanList 文件夹下的 Index.vue
区域销售大盘环比分析
Store 文件夹下面的 index.ts
import { defineStore } from 'pinia'
interface UserData {
order: string;
shop: string;
rider: string;
newShop: string;
avgOrder: string;
Rowindex?: number;
}
export const useAllUserDataStore = defineStore('AllUserData', {
state: (): { Alluserdata: UserData[] } => ({
Alluserdata: [
{ "order": "北京 -17%", "shop": "北京 -3%", "rider": "北京 +20%", "newShop": "北京 +14%", "avgOrder": "北京 -16%" },
{ "order": "上海 -19%", "shop": "上海 +12%", "rider": "上海 -15%", "newShop": "上海 +15%", "avgOrder": "上海 -21%" },
{ "order": "广州 +20%", "shop": "广州 -5%", "rider": "广州 +17%", "newShop": "广州 -15%", "avgOrder": "广州 +10%" },
{ "order": "深圳 -4%", "shop": "深圳 -23%", "rider": "深圳 -5%", "newShop": "深圳 +15%", "avgOrder": "深圳 -4%" },
{ "order": "南京 +10%", "shop": "南京 -6%", "rider": "南京 -7%", "newShop": "南京 -5%", "avgOrder": "南京 -22%" },
{ "order": "杭州 -3%", "shop": "杭州 +20%", "rider": "杭州 +16%", "newShop": "杭州 -11%", "avgOrder": "杭州 -17%" },
{ "order": "合肥 +8%", "shop": "合肥 +17%", "rider": "合肥 -23%", "newShop": "合肥 +6%", "avgOrder": "合肥 -11%" },
{ "order": "济南 -13%", "shop": "济南 -11%", "rider": "济南 +6%", "newShop": "济南 -7%", "avgOrder": "济南 +18%" },
{ "order": "太原 -11%", "shop": "太原 -10%", "rider": "太原 +18%", "newShop": "太原 -14%", "avgOrder": "太原 -2%" },
{ "order": "成都 +19%", "shop": "成都 -6%", "rider": "成都 +14%", "newShop": "成都 -19%", "avgOrder": "成都 +10%" },
{ "order": "重庆 -12%", "shop": "重庆 +12%", "rider": "重庆 +12%", "newShop": "重庆 +7%", "avgOrder": "重庆 -3%" },
{ "order": "苏州 +15%", "shop": "苏州 +18%", "rider": "苏州 -8%", "newShop": "苏州 -3%", "avgOrder": "苏州 +4%" },
{ "order": "无锡 -15%", "shop": "无锡 -19%", "rider": "无锡 -14%", "newShop": "无锡 +21%", "avgOrder": "无锡 -21%" },
{ "order": "常州 +12%", "shop": "常州 +10%", "rider": "常州 +5%", "newShop": "常州 -22%", "avgOrder": "常州 -13%" },
{ "order": "温州 -14%", "shop": "温州 -16%", "rider": "温州 -15%", "newShop": "温州 +14%", "avgOrder": "温州 +17%" },
{ "order": "哈尔滨 +5%", "shop": "哈尔滨 -18%", "rider": "哈尔滨 -18%", "newShop": "哈尔滨 -18%", "avgOrder": "哈尔滨 -20%" },
{ "order": "长春 +14%", "shop": "长春 -5%", "rider": "长春 -17%", "newShop": "长春 +18%", "avgOrder": "长春 +4%" },
{ "order": "大连 -4%", "shop": "大连 -15%", "rider": "大连 -22%", "newShop": "大连 +14%", "avgOrder": "大连 +14%" },
{ "order": "沈阳 -1%", "shop": "沈阳 -21%", "rider": "沈阳 -15%", "newShop": "沈阳 +24%", "avgOrder": "沈阳 +8%" },
{ "order": "拉萨 +18%", "shop": "拉萨 +5%", "rider": "拉萨 -6%", "newShop": "拉萨 -24%", "avgOrder": "拉萨 -10%" },
{ "order": "呼和浩特 +14%", "shop": "呼和浩特 -12%", "rider": "呼和浩特 +8%", "newShop": "呼和浩特 +9%", "avgOrder": "呼和浩特 -21%" },
{ "order": "武汉 -21%", "shop": "武汉 +13%", "rider": "武汉 -10%", "newShop": "武汉 -14%", "avgOrder": "武汉 +10%" },
{ "order": "南宁 -22%", "shop": "南宁 +23%", "rider": "南宁 -9%", "newShop": "南宁 +6%", "avgOrder": "南宁 -12%" }
]
}),
actions: {
setAllUserData(newData: UserData[]) {
this.Alluserdata = newData.map((item, index) => ({
...item,
Rowindex: index + 1
}));
console.log("Store updated");
},
addData(data: UserData) {
const newRowIndex = this.Alluserdata.length ? Math.max(...this.Alluserdata.map(item => item.Rowindex!)) + 1 : 1;
this.Alluserdata.push({ ...data, Rowindex: newRowIndex });
},
getAllUserData() {
return this.Alluserdata;
}
}
});
App.vue
原文地址: vue 实现列表自动滚动 (纯与原生方式)