🚀 纯 CSS Keyframes 实现 Vue 无缝无限轮播组件(跑马灯效果)
实现一个基于 Vue 和纯 CSS 实现的双向无限轮播(跑马灯)组件的使用,支持多行,并且鼠标悬停自动暂停!
🌟 组件核心
- 性能: 使用 CSS
transform和animation。 - 无缝: 通过巧妙的数据复制 和
-50%滚动技巧实现完美无缝衔接。 - 交互: 鼠标悬停(
hover)时,自动暂停滚动。
💻 完整代码:MarqueeLoop.vue
1. Vue Template 和 Script
代码段
ini
<template>
<div class="marquee-container">
<div class="marquee-row">
<div class="marquee-track">
<div class="logo-card" v-for="(item, index) in row1" :key="'r1-'+index" @click="openLink(item.url)">
<img :src="item.imgUrl" fit="fill" />
<span v-if="!item.logo">{{ item.name }}</span>
</div>
<div class="logo-card" v-for="(item, index) in row1" :key="'r1-dup-'+index"
@click="openLink(item.url)">
<img :src="item.imgUrl" fit="fill" />
<span v-if="!item.logo">{{ item.name }}</span>
</div>
</div>
</div>
<div class="marquee-row">
<div class="marquee-track-right">
<div class="logo-card" v-for="(item, index) in row2" :key="'r2-'+index" @click="openLink(item.url)">
<img :src="item.imgUrl" fit="fill" />
<span v-if="!item.logo">{{ item.name }}</span>
</div>
<div class="logo-card" v-for="(item, index) in row2" :key="'r2-dup-'+index"
@click="openLink(item.url)">
<img :src="item.imgUrl" fit="fill" />
<span v-if="!item.logo">{{ item.name }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const row1 = ref([
{ name: 'Vue', imgUrl: 'https://cdn.example.com/vue.png', url: 'https://vuejs.org/' },
{ name: 'React', imgUrl: 'https://cdn.example.com/react.png', url: 'https://react.dev/' },
{ name: 'Node', imgUrl: '', logo: false, url: 'https://nodejs.org/' },
]);
const row2 = ref([
{ name: 'Google', imgUrl: 'https://cdn.example.com/google.png', url: 'https://www.google.com/' },
{ name: 'Apple', imgUrl: 'https://cdn.example.com/apple.png', url: 'https://www.apple.com/' },
]);
const openLink = (url) => {
if (url) {
window.open(url, '_blank');
}
};
</script>
2. CSS 样式和 Keyframes
CSS
xml
<style scoped>
.marquee-container {
width: 100%;
background-color: #f5f7fa;
padding: 20px 0;
overflow: hidden;
}
.marquee-row {
display: flex;
margin-bottom: 20px;
overflow: hidden;
width: 100%;
}
/* --- 滚动轨道定义 --- */
.marquee-track,
.marquee-track-right {
display: flex;
width: max-content;
flex-shrink: 0;
}
.marquee-track {
animation: scroll-left 30s linear infinite; /* 向左滚动 */
}
.marquee-track-right {
animation: scroll-right 30s linear infinite; /* 向右滚动 */
}
/* 交互:鼠标悬停暂停动画 */
.marquee-track:hover,
.marquee-track-right:hover {
animation-play-state: paused;
}
/* --- 卡片样式 (可自定义) --- */
.logo-card {
width: 350px; /* 调整卡片宽度 */
height: 200px; /* 调整卡片高度 */
background: #ffffff;
margin: 0 10px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
flex-shrink: 0;
cursor: pointer;
transition: transform 0.2s;
}
.logo-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.logo-card img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
/* --- 动画关键帧 --- */
/* 1. 向左滚动:0% -> -50% */
@keyframes scroll-left {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
/* 2. 向右滚动:-50% -> 0% */
@keyframes scroll-right {
0% {
transform: translateX(-50%);
}
100% {
transform: translateX(0);
}
}
</style>
💡 无缝滚动原理揭秘
-
数据翻倍: 在 模板中,使用
v-for将数据 (row1或row2) 渲染了两遍。 -
width: max-content: 确保两个列表被强制排在一行,且容器总宽度是单个列表宽度的两倍。 -
关键的
-50%:- 向左: 动画从
translateX(0)移动到translateX(-50%)。当第一组数据完全移出屏幕时(即移动了 50% 的总宽度),动画瞬间重置回0,此时屏幕上显示的是第二组数据的起点,完美对接,实现视觉上的无限循环。 - 向右: 动画从
translateX(-50%)(即第二组数据的起点)移动到translateX(0)(即第一组数据的起点),实现反向循环。
- 向左: 动画从
通过这种方式,我们避免了使用 JavaScript 计算或操作 DOM,将动画交给 CSS 处理,极大地提高了性能和流畅度。