移动端H5页面中的跑马灯效果看似简单,却暗藏性能陷阱。许多开发者习惯性使用setInterval方案,结果在低端安卓设备上遭遇严重卡顿。本文将带你用Vue 3的transition特性,打造一个零卡顿的智能跑马灯组件,同时解决动态文本宽度适配、循环播放等核心难题。
在小米6(骁龙835)上的测试数据显示:当使用setInterval实现跑马灯时,JavaScript线程持续占用导致帧率从60FPS暴跌至32FPS。而采用CSS transition的方案,帧率稳定在58-60FPS之间。
关键性能差异对比:
| 指标 | setInterval方案 | transition方案 |
|---|---|---|
| 主线程占用率 | 78% | 12% |
| 动画帧率(FPS) | 32 | 58 |
| 内存占用(MB) | 24.7 | 18.2 |
| 滚动流畅度 | 明显卡顿 | 丝滑流畅 |
造成这种差异的根本原因在于:
setInterval强制浏览器每16ms执行一次JS计算和DOM操作提示:在Chrome DevTools的Performance面板中,红色三角标记表示帧丢失,这是检测卡顿最直观的方式
传统跑马灯的固定时长会导致长文本滚动过慢、短文本滚动过快。我们的智能算法根据文本宽度动态计算持续时间:
javascript复制// 获取文本DOM的实际宽度
const textWidth = textElement.offsetWidth
// 基准速度:每50px需要1秒
const baseSpeed = 50
// 动态计算持续时间(单位:秒)
const duration = textWidth / baseSpeed
实现细节优化:
ResizeObserver监听文本变化,自动重新计算以下是基于Composition API的现代化实现:
vue复制<template>
<div class="marquee-container" ref="container">
<div
class="marquee-content"
:style="{
transform: `translateX(${currentPosition}px)`,
transition: `transform ${duration}s linear`,
willChange: 'transform'
}"
>
{{ content }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
const props = defineProps({
content: String,
speed: { type: Number, default: 50 } // px/s
})
const container = ref(null)
const contentElement = ref(null)
const currentPosition = ref(0)
const duration = ref(0)
const startAnimation = () => {
if (!container.value || !contentElement.value) return
const containerWidth = container.value.offsetWidth
const contentWidth = contentElement.value.offsetWidth
// 只有当内容超出容器时才需要滚动
if (contentWidth > containerWidth) {
duration.value = contentWidth / props.speed
currentPosition.value = containerWidth
requestAnimationFrame(() => {
currentPosition.value = -contentWidth
})
}
}
onMounted(() => {
startAnimation()
})
watch(() => props.content, startAnimation)
</script>
<style>
.marquee-container {
width: 100%;
overflow: hidden;
white-space: nowrap;
}
.marquee-content {
display: inline-block;
will-change: transform;
}
</style>
关键优化点:
transform代替left属性,触发硬件加速will-change提示浏览器提前优化requestAnimationFrame确保动画起始帧同步组件卸载时清除所有定时器:
javascript复制import { onUnmounted } from 'vue'
let animationFrame
const loopAnimation = () => {
// ...动画逻辑...
animationFrame = requestAnimationFrame(loopAnimation)
}
onUnmounted(() => {
cancelAnimationFrame(animationFrame)
})
通过transitionend事件实现完美循环:
javascript复制const onTransitionEnd = () => {
currentPosition.value = containerWidth
requestAnimationFrame(() => {
currentPosition.value = -contentWidth
})
}
添加以下CSS提升移动端表现:
css复制.marquee-content {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
transform: translateZ(0);
}
根据不同业务需求,我们可以扩展组件功能:
配置项示例:
javascript复制defineProps({
direction: { type: String, default: 'left' }, // left|right|up|down
loop: { type: Boolean, default: true },
pauseOnHover: { type: Boolean, default: true },
speed: { type: Number, default: 50 },
delay: { type: Number, default: 0 }
})
垂直滚动实现:
javascript复制const verticalStyle = computed(() => {
return {
transform: `translateY(${currentPosition}px)`,
whiteSpace: 'normal',
display: 'block'
}
})
在真实项目中使用时,建议通过Vue的provide/inject实现全局配置,避免重复传参。对于超长列表的跑马灯,可以考虑虚拟滚动技术进一步优化性能。