需求人员表示,超过高度的表格内容需要滚动展示,所以效果图如下:
20250327:新增了config配置,可以调整 scrollSpeed、bottomPause、smoothReturn、minDelta。
const config = {
scrollSpeed: 0.5, // 可设置为 0.1~0.9
bottomPause: 2000, // 到底部后的暂停时间
smoothReturn: false, // 启用平滑返回
minDelta: 0.1 // 最小移动单位
};
自定义列表样式,主要是通过flex布局,控制 类th 与 类td 的宽度保持一致,标签结构还是参考了table的结构,由thead与tbody包裹tr再包裹最小单元th,td,但是自定义的列表在样式修改、结构调整上会更加灵活。
.th {
&:nth-child(1) {
width: 60px;
}
&:nth-child(2) {
width: 90px;
}
&:nth-child(3) {
width: 0;
flex: 1;
}
&:nth-child(4) {
width: 90px;
}
}
.td {
&:nth-child(1) {
width: 60px;
}
&:nth-child(2) {
width: 90px;
}
&:nth-child(3) {
width: 0;
flex: 1;
}
&:nth-child(4) {
width: 90px;
}
}
this.stopAutoScroll() 与 this.startAutoScroll() 都是AI生成的JS代码修改而来
在 mounted() 中通过循环创造20条数据的列表,再通过 $nextTick ,当列表数量(length)超过5时,调用startAutoScroll方法进行自动滚动,在该方法之前调用stopAutoScroll是为了防抖节流。如果有主要思路想法,可以借助通义千问等AI工具进行JS代码的快速生成,节约时间。
mounted() {
for (let i = 0; i < 20; i++) {
let obj = {
id: 'tableData00' + i,
td1: i + 1,
td2: 'td2',
td3: i % 3 == 0 ? '测试测试测试' : i % 3 == 1 ? '测试测试' : '测试',
td4: '99'
}
this.tableData.push(obj)
}
this.$nextTick(function () {
// 当列表超过5条,自动滚动,可自行修改
if (this.tableData.length > 5) {
// 自动滚动
this.stopAutoScroll();
this.startAutoScroll();
}
})
},
【完整代码】
<template>
<div class="cus-list-wrap">
<div class="thead">
<div class="tr">
<div class="th">th1</div>
<div class="th">th2</div>
<div class="th">th3</div>
<div class="th">th4</div>
</div>
</div>
<div class="tbody hit1" ref="scrollContainer">
<div class="tr" v-for="item in tableData" :key="item.id">
<div class="td">
{{ item.td1 }}
</div>
<div class="td">
{{ item.td2 }}
</div>
<div class="td">
{{ item.td3 }}
</div>
<div class="td">
{{ item.td4 }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [],
isScrollingDown: true,
scrollAnimationFrameId: null,
isAutoScrollRunning: false,
}
},
mounted() {
for (let i = 0; i < 20; i++) {
let obj = {
id: 'tableData00' + i,
td1: i + 1,
td2: 'td2',
td3: i % 3 == 0 ? '测试测试测试' : i % 3 == 1 ? '测试测试' : '测试',
td4: '99'
}
this.tableData.push(obj)
}
this.$nextTick(function () {
// 当列表超过5条,自动滚动,可自行修改
if (this.tableData.length > 5) {
// 自动滚动
this.stopAutoScroll();
this.startAutoScroll();
}
})
},
methods: {
// 自动滚动
startAutoScroll() {
if (this.isAutoScrollRunning) return;
this.isAutoScrollRunning = true;
const scrollContainer = this.$refs.scrollContainer;
const config = {
scrollSpeed: 0.5, // 可设置为 0.1~0.9
bottomPause: 2000, // 到底部后的暂停时间
smoothReturn: false, // 启用平滑返回
minDelta: 0.1 // 最小移动单位
};
// 平滑滚动方法
const smoothScrollTo = (target) => {
if (!config.smoothReturn) {
scrollContainer.scrollTop = target;
return;
}
const start = scrollContainer.scrollTop;
const duration = 800;
let startTime = null;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const progress = timestamp - startTime;
const percent = Math.min(progress / duration, 1);
// 缓动函数:easeOutQuad
const eased = 1 - Math.pow(1 - percent, 2);
scrollContainer.scrollTop = start + (target - start) * eased;
if (percent < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
};
const autoScroll = () => {
if (!this.isAutoScrollRunning) return;
const { scrollTop, clientHeight, scrollHeight } = scrollContainer;
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
const isAtTop = scrollTop <= 0;
// 累积小数增量
this.accumulatedDelta += config.scrollSpeed;
const actualMove = Math.floor(this.accumulatedDelta);
this.accumulatedDelta -= actualMove;
if (this.isScrollingDown) {
if (isAtBottom) {
this.isScrollingDown = false;
this.accumulatedDelta = 0; // 重置累积值
smoothScrollTo(0);
setTimeout(() => {
if (this.isAutoScrollRunning) {
this.isScrollingDown = true;
autoScroll();
}
}, config.bottomPause);
return;
}
scrollContainer.scrollTop += actualMove;
} else {
if (isAtTop) {
this.isScrollingDown = true;
this.accumulatedDelta = 0;
autoScroll();
return;
}
scrollContainer.scrollTop -= actualMove;
}
// 根据最小移动单位优化性能
if (actualMove >= config.minDelta || Math.abs(this.accumulatedDelta) >= config.minDelta) {
this.scrollAnimationFrameId = requestAnimationFrame(autoScroll);
} else {
this.scrollAnimationFrameId = requestAnimationFrame(autoScroll);
}
};
autoScroll();
},
stopAutoScroll() {
this.isAutoScrollRunning = false;
cancelAnimationFrame(this.scrollAnimationFrameId);
}
},
beforeDestroy() {
// 清除定时器以避免内存泄漏
this.stopAutoScroll();
},
}
</script>
<style lang="less" scoped>
.cus-list-wrap {
margin: 0 16px;
padding-top: 4px;
}
.hit1 {
overflow-x: hidden;
overflow-y: auto;
height: 400px;
}
.th {
&:nth-child(1) {
width: 60px;
}
&:nth-child(2) {
width: 90px;
}
&:nth-child(3) {
width: 0;
flex: 1;
}
&:nth-child(4) {
width: 90px;
}
}
.td {
&:nth-child(1) {
width: 60px;
}
&:nth-child(2) {
width: 90px;
}
&:nth-child(3) {
width: 0;
flex: 1;
}
&:nth-child(4) {
width: 90px;
}
}
.thead {
.tr {
display: flex;
align-items: center;
height: 30px;
}
.th {
text-align: center;
font-size: 14px;
color: #666;
}
}
.tbody {
.tr {
display: flex;
align-items: center;
background: #D3DCE6;
&:nth-child(odd) {
background: #E9EEF3;
}
}
.td {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
font-size: 12px;
color: #666;
}
}
</style>