一、引言
项目有文本滚动展示的需求,一开始使用marquee标签来实现需求,但是看一下MDN对该标签的描述:
这个标签已经不再推荐使用了,那就自己封装一个类似的组件来用。
二、实现思路
1.如何让文本滚动起来?
通过动画我们设置过渡transform:translateX(a) => transfrom:translateX(b)来实现向左或向右的滚动效果。transform:translateY(c) => transfrom:translateY(d)来实现向下或向上的滚动效果。
2.组件需要哪些配置?
(1)滚动的方向:上右下左
(2)滚动的速度:这里我以px/s作为单位
三、实现过程
1.html
<template>
<!-- 文本滚动 -->
<div class="text-scroll" ref="textScroll">
<div class="content" ref="content" :style="scrollAnimation">
<!-- 默认插槽,插入滚动内容 -->
<slot></slot>
</div>
</div>
</template>
2.css
<style scoped lang="scss">
.text-scroll {
width: 100%;
height: 100%;
overflow: hidden;
.content {
height: fit-content;
width: fit-content;
}
}
</style>
最外层的div为滚动的可视区,里面的div为文本滚动区。可视区宽度高度均为100%使用时大小由外部容器决定,overflow设为hidden,防止文本滚动区滚动出可视区外仍可见。文本滚动区设置宽高都为fit-content使大小随内容自适应,内部设置插槽使用组件时插入滚动内容。
3.动画
动画应设置在文本滚动区上
需要上右下左四个方向滚动的动画,我们只需要定义上左两个方向,另外两个方向直接反转即可。先来想想向上滚动的动画怎么写,向上滚动的话文本滚动区的起点应设置在可视区外正下方,所以动画的起点应为 transform: translateY(可视区的高度),终点应设置在可视区外正上方应为transform: translateY(-100%)。
向上向左动画即为:
<style lang="scss">
.text-scroll {
.content {
@keyframes up-scroll {
0% {
transform: translateY(var(--text-scroll-height));
}
100% {
transform: translateY(-100%);
}
}
@keyframes left-scroll {
0% {
transform: translateX(var(--text-scroll-width));
}
100% {
transform: translateX(-100%);
}
}
}
}
</style>
--text-scroll-height、--text-scroll-width两个css变量的值是可视区的高度和宽度,由于可视区的宽高不确定,所以需要通过js获取并设置这两个css变量。
注意: 动画样式不能加上scoped,否则不生效!
4.js
(1)组件配置
props: {
/* 滚动方向
* value: up、down、left、right
*/
direction: {
default: "up",
type: String,
},
//滚动速度 单位px/s
speed: {
default: 60,
type: Number,
},
},
通过props传入组件配置
(2)计算得到滚动动画
methods: {
getScrollAnimation() {
//获取文本滚动实际显示宽度高度,设为css变量,用于设置动画开始起始位置
let height = this.$refs.textScroll.offsetHeight;
let width = this.$refs.textScroll.offsetWidth;
this.$refs.content.style.setProperty(
"--text-scroll-height",
`${height}px`
);
this.$refs.content.style.setProperty("--text-scroll-width", `${width}px`);
//滚动长度、时间
let scrollLength, time;
//根据滚动方向来设置不同的滚动动画
switch (this.direction) {
case "up":
scrollLength =
this.$refs.content.offsetHeight +
height;
time = scrollLength / this.speed;
this.scrollAnimation.animation = `up-scroll linear ${time}s infinite`;
break;
case "down":
scrollLength =
this.$refs.content.offsetHeight +
height;
time = scrollLength / this.speed;
this.scrollAnimation.animation = `up-scroll linear ${time}s infinite reverse`;
break;
case "left":
scrollLength =
this.$refs.content.offsetWidth + width;
time = scrollLength / this.speed;
this.scrollAnimation.animation = `left-scroll linear ${time}s infinite`;
break;
case "right":
scrollLength =
this.$refs.content.offsetWidth + width;
time = scrollLength / this.speed;
this.scrollAnimation.animation = `left-scroll linear ${time}s infinite reverse`;
break;
}
},
},
scrollLength为一次动画的实际滚动的长度,time为一次动画的持续时间。我们可以算出scrollLength应为:可视区的宽或高加上文本滚动区的宽或高(根据滚动的方向来判断是宽还是高)。time应为 scrollLegnth / 组件配置的滚动速度。
(3)生命周期
在mounted里调用上面的方法初始化组件。在updated里同样调用该方法,当组件宽高改变或插槽内容变动重新计算动画样式。
mounted() {
//设置文本滚动动画
this.getScrollAnimation();
},
updated() {
//当插槽内容更新重新计算滚动动画
this.getScrollAnimation();
},
三、完整代码
<template>
<!-- 文本滚动 -->
<div class="text-scroll" ref="textScroll">
<div class="content" ref="content" :style="scrollAnimation">
<!-- 默认插槽,插入滚动内容 -->
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "TextScroll",
props: {
/* 滚动方向
* value: up、down、left、right
*/
direction: {
default: "up",
type: String,
},
//滚动速度 单位px/s
speed: {
default: 60,
type: Number,
},
},
data() {
return {
//滚动动画
scrollAnimation: {},
};
},
methods: {
getScrollAnimation() {
//获取文本滚动实际显示宽度高度,设为css变量,用于设置动画开始起始位置
let height = this.$refs.textScroll.offsetHeight;
let width = this.$refs.textScroll.offsetWidth;
this.$refs.content.style.setProperty(
"--text-scroll-height",
`${height}px`
);
this.$refs.content.style.setProperty("--text-scroll-width", `${width}px`);
//滚动长度、时间
let scrollLength, time;
//根据滚动方向来设置不同的滚动动画
switch (this.direction) {
case "up":
scrollLength =
this.$refs.content.offsetHeight +
height;
time = scrollLength / this.speed;
this.scrollAnimation.animation = `up-scroll linear ${time}s infinite`;
break;
case "down":
scrollLength =
this.$refs.content.offsetHeight +
height;
time = scrollLength / this.speed;
this.scrollAnimation.animation = `up-scroll linear ${time}s infinite reverse`;
break;
case "left":
scrollLength =
this.$refs.content.offsetWidth + width;
time = scrollLength / this.speed;
this.scrollAnimation.animation = `left-scroll linear ${time}s infinite`;
break;
case "right":
scrollLength =
this.$refs.content.offsetWidth + width;
time = scrollLength / this.speed;
this.scrollAnimation.animation = `left-scroll linear ${time}s infinite reverse`;
break;
}
},
},
mounted() {
//设置文本滚动动画
this.getScrollAnimation();
},
updated() {
//当插槽内容更新重新计算滚动动画
this.getScrollAnimation();
},
};
</script>
<style scoped lang="scss">
.text-scroll {
width: 100%;
height: 100%;
overflow: hidden;
.content {
height: fit-content;
width: fit-content;
}
}
</style>
<style lang="scss">
.text-scroll {
.content {
@keyframes up-scroll {
0% {
transform: translateY(var(--text-scroll-height));
}
100% {
transform: translateY(-100%);
}
}
@keyframes left-scroll {
0% {
transform: translateX(var(--text-scroll-width));
}
100% {
transform: translateX(-100%);
}
}
}
}
</style>