日常开发过程中,列表是比较常见的展示数据的一种形式,本文主要聚焦于列表的滚动以及循环滚动。
准备条件
由于存在动态数据,本文为了方便,基于 vue3
来实现的,当然原理是相通的,读者可以基于其他框架,或者直接对 dom
操作进行实现,在此不再赘述。
基本骨架
vue
<template>
<div class="list">
<div class="list-item" v-for="(item) in items" :key="item.id">{{ item.name }}</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const datas = ref(
[...Array(100).keys()].map((i) => ({ id: i, name: `item ${i}` }))
);
const items = computed(() => datas.value)
</script>
<style scoped>
.list {
height: 180px;
width: 500px;
border: 1px solid #eee;
overflow: hidden;
}
.list-item {
height: 30px;
line-height: 30px;
box-sizing: border-box;
padding: 0 12px;
border-bottom: 1px solid #eee;
}
.list-item:last-child {
border-bottom-color: transparent;
}
</style>
在此我们定义了一个尺寸为 500 * 100
的列表,每条数据占据 30 的高度,我们不想让它支持鼠标滚动,所以设置了overflow:hidden
,大致效果图如下

实现
准备工作已经完成,是时候实现我们的目标了,怎么做呢?
动起来
如何动起来呢,其实大体有两种方式
- 通过
js
来操作滚动条,这种方式虽然能达到目的,但动画效果都得自己去实现,或者调用浏览器的平滑滚动,其实效果也是一言难尽 - 有没有比较好方式呢,当然有,那就是通过
css
动画实现了,通过让内层元素慢慢向上平移不就达到了滚动的效果么
在之前得改造下结构,因为我们需要一个容器来做平移,改造后的 html
结构如下
html
<div class="list">
<div class="list-wrapper" :style="{transform:`translateY(${ty})`}">
<div class="list-item" v-for="(item) in items" :key="item.id">{{ item.name }}</div>
</div>
</div>
js
const ty = ref(0)
现在就需要改变这个 ty
,从而让列表动起来,但这也需要通过 js
去定时改变它,但我们不想要这么做,所以我们继续改造,使用 css
的 animation
去实现,由于列表长度不固定,我们需要用到css
变量。
首先定义动画,--anim-max-y
为y
方向最大平移距离,--anim-duration
动画持续时间
css
@keyframes scroll-up {
to {
transform: translateY(var(--anim-max-y));
}
}
.list-wrapper {
animation: scroll-up var(--anim-duration) linear infinite;
}
我们的列表可视区域元素为 6
个,每行占 30px
,排除掉可视区域的高度,就是我们最大的滚动距离,持续时间设置为元素个数/2
对应的秒数
html
<div class="list">
<div
class="list-wrapper"
:style="{
'--anim-max-y': `-${(items.length - 6) * 30}px`,
'--anim-duration': `${items.length / 2}s`,
}">
<div
class="list-item"
v-for="item in items"
:key="item.id">
{{ item.name }}
</div>
</div>
</div>
至此我们已经大体实现了我们的需求,看起来效果还不错。为啥说是大体呢,因为我们既然说是循环滚动,那么衔接是不是应该平滑呢,仔细一看,当我们列表滚动到底部的时候,会看到一下子跳到头部,这就是需要我们改进的地方。
其实解决方案比较简单,就是往列表底部附加点可视区域内的数据,这样就能完美的解决问题了
js
const visibleCount = 6
const items = computed(() => {
if (datas.value.length <= visibleCount) {
return datas.value;
}
return datas.value.concat(datas.value.slice(0, visibleCount))
});
至此,大功告成~~~
完整代码
vue
<template>
<div class="list">
<div
class="list-wrapper"
:style="{
'--anim-max-y': `-${(items.length - visibleCount) * 30}px`,
'--anim-duration': `${items.length / 2}s`,
}">
<div
class="list-item"
v-for="item in items"
:key="item.id">
{{ item.name }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
const datas = ref(
[...Array(100).keys()].map((i) => ({ id: i, name: `item ${i}` }))
);
const visibleCount = 6
const items = computed(() => {
if (datas.value.length <= visibleCount) {
return datas.value;
}
return datas.value.concat(datas.value.slice(0, visibleCount))
});
</script>
<style scoped>
.list {
height: 180px;
width: 500px;
border: 1px solid #eee;
overflow: hidden;
}
.list-item {
line-height: 30px;
height: 30px;
box-sizing: border-box;
padding: 0 12px;
border-bottom: 1px solid #eee;
}
.list-item:last-child {
border-bottom-color: transparent;
}
@keyframes scroll-up {
to {
transform: translateY(var(--anim-max-y));
}
}
.list-wrapper {
animation: scroll-up var(--anim-duration) linear infinite;
}
</style>
后记
上面实现的滚动列表只是一种实现方式,当然大家可以进一步封装成组件,每一项的高度也可以通过变量进行设置,这一块就靠读者自由发挥。