跑马灯是比较常见的页面展现元素。写这篇博客的目的主要是掘金上面关于前端跑马灯的教程基本没有,仅有的一个也是x轴方向的,今天写一个y轴方向的。
Demo地址: github.com/ZSX-DB/Reus…
首先我们需要定义 html 结构和 css,我将 box 的高度和 item 的高度都设置了 40px,这些都是可更改的。
.box{
overflow: hidden;
margin-top: 200px;
width: 500px;
height: 40px;
border: 1px solid #000;
box-sizing: border-box;
color: #fff;
background-color: #000;
}
.item{
display: flex;
align-items: center;
padding-left: 10px;
color: #fff;
cursor: pointer;
}
<div class="box">
<div class="item">y轴跑马灯实现</div>
<div class="item">前端实现</div>
<div class="item">可能使用margin-top</div>
<div class="item">短暂停留</div>
</div>
CSS实现
使用 @keyframes 可以定位帧动画, 通过设置 infinite 来实现不间断轮播的效果, 设置item滚动, 当item为最后一个时, 重置。
以下是定义的@keyframes
.box{
...
animation: run 8.4s infinite;
}
@keyframes run {
0%{
transform: translateY(0px);
}
25%{
transform: translateY(-40px);
}
50%{
transform: translateY(-80px);
}
75%{
transform: translateY(-120px);
}
100%{
transform: translateY(-160px);
}
}
为了保证滚动的顺滑, 还需要在尾部加一个<div class="item">y轴跑马灯实现</div>在尾部。
JavaScript实现
上面的动画高度依赖于item的条数, 如果item有很多条, 并且实际开发中需求大概是滚动到哪一条, 停顿, 过会后再滚动。这些如果用CSS写将会特别麻烦甚至无法实现。因此我们用js来实现。
有以下的思路
- 设置一个定时器setInterval, 定义步进距离, 步进时间, 来模拟帧动画
- 当一个item完全滚动到可视区, 清除计时器, 再添加一个setTimeout, 停顿时间结束后再重启计时器
- 当滚动到最后的item时, 重置高度
使用margin-top来动态改变item的位置, 只需要设置第一个item的规则, 后面的item会自动排布, 为了知道当第一个item的margin-top为多少时重置, 还需要给定item的数量
function runYMarquee(firstNode, nodeNum, step = 1, time = 20) {
let ch = firstNode.clientHeight
const start = marginTop => {
let mt = marginTop
let interval = setInterval(()=>{
// 判断不是第一项和最后一项
if(Number.isInteger(mt/ch) && mt !== marginTop && mt !== - (ch * nodeNum) ){
clearInterval(interval)
setTimeout(()=>{
start(mt)
}, 1000)
}else if(mt === - (ch * nodeNum)){
// 到达最后一项,归零
clearInterval(interval)
// 恢复原来的位置
firstNode.style.marginTop = '0'
setTimeout(()=>{
start(0)
},1000)
}else {
// 如果是start的第一个执行此项
mt -= step
firstNode.style.marginTop = `${mt}px`
}
}, time)
}
// 这里设置setTimeout是为了首条item的延迟滚动
setTimeout(()=>{
start(0)
}, 1000)
}
let i = document.querySelector('.item')
runYMarquee(i, 4)
注: 以上仍然需要在最后添加<div class="item">y轴跑马灯实现</div>来实现顺滑效果
优化
如果使用margin-top来改变位置, 将会频繁触发浏览器的重排重绘, 而且需要手动在末尾添加item, 并且无法设置停顿时间
基于此, 我写了一个优化版本, 优化了下列几项
- 使用transfrom中的translateY属性替代margin-top, 可以生成一个合成层, 由GPU控制,支持硬件加速,并不需要软件方面的渲染
- clone第一个节点, 自动追加于最后, 不需要手动添加
- 支持自定义设置停顿时间
translateY的对高度的控制是独立的, 因此必须在item外部再套一层div, 通过控制该层div来控制高度。
<div class="box">
<div id="marquee-box">
<div class="item" onclick="log(1)">y轴跑马灯实现</div>
<div class="item" onclick="log(2)">前端实现</div>
<div class="item" onclick="log(3)">可能使用margin-top</div>
<div class="item" onclick="log(4)">短暂停留</div>
</div>
</div>
实现的思路与上面一样, 代码如下
function runMarquee(node, step = 1, time = 20, stopTime = 1000) {
// 最后追加子节点
let firstNode = node.children[0]
let newNode = firstNode.cloneNode(true)
node.append(newNode)
// 获取children的数量
let childNodeNum = node.children.length
// 获取此时的node高度
let ch = node.clientHeight
// 子节点的高度
let cnh = ch / childNodeNum
// 最后停止的高度
let lastHeight = ch * ((childNodeNum - 1) / childNodeNum)
const start = initTY => {
// 初始化translateY
let ty = initTY
let interval = setInterval(()=>{
// 判断不是第一项或最后一项
if(Number.isInteger(ty / cnh) && ty !== initTY && ty !== -lastHeight ){
clearInterval(interval)
setTimeout(()=>{
start(ty)
}, stopTime)
}else if(ty === -lastHeight){
clearInterval(interval)
node.style.transform = `translateY(0)`
setTimeout(()=>{
start(0)
},stopTime)
}else {
ty -= step
node.style.transform = `translateY(${ty}px)`
}
}, time)
}
setTimeout(()=>{
start(0)
}, stopTime)
}
runMarquee(document.querySelector('#marquee-box'))
使用需满足两个条件
- 外部box的高度必须与item相同
- step必须能被item的高度整除
如果这篇文章对你有用, 请给我点个赞吧, 有疑问欢迎在下方评论区交流。