其实,这种用在vue项目中的需求跟原生js的实现方法基本一致,且实现的方法有多种,今天就单拎出来一种实现方法吧:
<template>
<div class="marquee-container">
<!-- 横向无缝滚动效果 -->
<div v-if="horizontalZ" class="wrap">
<div id="box">
<div id="marquee">{{ text }}</div>
<div id="copy" />
</div>
<div id="node">{{ text }}</div>
</div>
<!-- 纵向无缝滚动效果 -->
<div v-else class="marquee-wrap" ref="marqueeListRef">
<ul class="marquee-list">
<li v-for="(item, index) in dataList" :key="index">{{ item }}</li>
</ul>
</div>
</div>
</template>
<script>
export default {
name: 'Marquee',
props: {
/** 父组件传入数据, 数组形式 [ "长得帅不是错","错的是一直这么帅下去"] */
lists: {
type: Array,
require: true,
},
/**
* horizontalZ:true :横向
* horizontalZ:false :纵向
*/
horizontalZ: {
type: Boolean,
default: false,
},
},
data() {
return {
text: '',
timer: null,
dataList: [],
speed: 20,
delay: 1500,
liHeight: '',
};
},
mounted() {
this.initData();
this.$nextTick(() => {
this.scrollArea = this.$refs.marqueeListRef;
let li = this.scrollArea.getElementsByTagName("li");
this.liHeight = li[0].offsetHeight;
this.scrollArea.scrollTop = 0;
this.scrollArea.innerHTML += this.scrollArea.innerHTML;
this.dataList.length > 1 && setTimeout(this.startScroll, this.delay);
})
},
updated() {
if (this.horizontalZ) {
this.move();
}
},
destroyed() {
clearInterval(this.timer);
},
methods: {
move() {
// 获取文字text 的计算后宽度 (由于overflow的存在,直接获取不到,需要独立的node计算)
// eslint-disable-next-line prefer-destructuring
const width = document.getElementById('node').getBoundingClientRect().width;
const box = document.getElementById('box');
const copy = document.getElementById('copy');
copy.innerText = this.text; // 文字副本填充
let distance = 0; // 位移距离
// 设置位移
this.timer = setInterval(() => {
// eslint-disable-next-line operator-assignment
distance = distance - 1;
// 如果位移超过文字宽度,则回到起点
if (-distance >= width) distance = 40;
// eslint-disable-next-line prefer-template
box.style.transform = 'translateX(' + distance + 'px)';
}, 20);
},
startScroll() {
this.timer = setInterval(this.scrollUp, this.speed);
this.scrollArea.scrollTop++;
},
scrollUp() {
if (this.scrollArea.scrollTop % this.liHeight == 0) {
clearInterval(this.timer);
setTimeout(this.startScroll, this.delay);
} else {
this.scrollArea.scrollTop++;
if (this.scrollArea.scrollTop >= this.scrollArea.scrollHeight / 2) {
this.scrollArea.scrollTop = 0;
}
}
},
initData() {
if (!this.horizontalZ) {
this.dataList = this.lists;
this.timer = setInterval(this.scrollAnimate, 1500);
} else {
for (let i = 0; i < this.lists.length; i++) {
this.text += ' ' + this.lists[i];
}
}
}
},
};
</script>
<style lang="scss" scoped>
.marquee-container {
color: #fff;
.wrap {
width: 80%;
height: 40px;
border-radius: 20px;
background: rgba($color: #000000, $alpha: 0.6);
margin: 0 auto;
overflow: hidden;
#box {
width: 80000%;
line-height: 40px;
div {
float: left;
}
#marquee {
margin: 0 20px;
}
}
#node {
position: absolute;
z-index: -999;
top: -999999px;
}
}
.marquee-wrap {
width: 80%;
height: 40px;
border-radius: 20px;
background: rgba($color: #000000, $alpha: 0.6);
margin: 0 auto;
overflow: hidden;
.marquee-list {
margin: 0;
padding: 0;
li {
width: 100%;
height: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding: 0 20px;
list-style: none;
line-height: 40px;
text-align: center;
box-sizing: border-box;
}
}
}
}
</style>