前提概要: 由于element中没有类似于noticeBar的组件,根据公司业务需要,自己实现一个noticeBar,满足需求。
重点分析
- 主要使用样式表中
inertRule()和deleteRule()两个方法,对样式表中的控制动画的@keyframes规则进行新增和删除。 一: 设置@keyframes规则
- 第一个动画是控制最开始的状态,
contentLength是内容长度,从0开始向左移动,整体移动到消失则需要切换第二个动画。移动总长度为contentLength。
- 第二个动画是从最右侧(
parentWidth)的位置开始出现,到最左边消失。总长度是parentWidth + contentLength
注意: 需要先删除注册在样式表中的规则,然后再添加新规则。不然在
data变化后,contentLength更改,导致样式表中有相同移动长度不同的同名规则,导致速度的变更。速度 = 总长度 / 时间
- 删除样式表
deleteRule(i)i是insertRule()时设置的索引。如果没有设置,默认应该是0.则不好定位,最好在插入样式时设置一个唯一的索引,用来标记删除。
二: 添加动画,需要根据传进来的速度计算时间,然后设置动画。
- 计算速度设置动画移动时间。时间 = 总长度 / 速度
注意: 第一个动画只执行一次,第二个动画无限循环。需要监听动画消失事件,切换动画。第一次加载mounted和数据切换都需要。
整体代码展示
<template>
<!--主要内容-->
<div class="noticebar" :style="{backgroundColor: options.background}">
<div style="margin-left:5px"></div>
<div ref="parent" class="parent">
<p ref="content" :style="{fontSize:options.size?options.size:'14px',color:options.color?options.color:'#f60'}" class="content">
<template v-if="type == 'text'">
{{ dataText }}
</template>
<template v-if="type == 'list'">
<span class="content-inside" v-for="(n,index) in data" :key="index">
{{ n.text }}
</span>
</template>
</p>
</div>
</div>
</template>
<script>
export default {
props: {
options: {
type: Object,
default() {
return {
text: '默认'
};
}
},
type: {
type: String,
default: 'text'
},
dataText: {
type: String,
default: ''
},
data: {
type: Array,
default() {
return []
}
},
},
watch: {
data: {
handler: function(data) {
if (data.length > 0) {
this.start();
}
},
deep: true
},
dataText: {
handler: function(data) {
this.start();
}
}
},
data() {
return {
speed: this.options.speed, // 速度(单位px/s)
parentWidth: '', // 父级宽度
parentHeight: '', // 父级高度
contentLength: '', // 文本长度
state: 1,
firstAnimationTime: '', // 状态一动画效果
secondAnimationTime: '', // 状态二动画效果
};
},
methods: {
start() {
this.state = 1;
let content = this.$refs.content;
// 清空上次的动画
content.style.animation = '';
this.Listener();
this.$nextTick(() => {
this.setAnimation();
})
},
// 获取数据
setAnimation() {
let style = document.styleSheets[0];
// 切换数据需删除之前的注册事件
_.filter(style.cssRules, (e,index) => {
return style.cssRules[index].name === 'firstAnimation' || style.cssRules[index].name == 'secondAnimation'
}).map(i => {
style.deleteRule(i);
})
let content = this.$refs.content;
let parent = this.$refs.parent;
this.parentWidth = parent.offsetWidth;
this.parentHeight = parent.offsetHeight;
content.style.lineHeight = this.parentHeight + 'px';
this.contentLength = content.offsetWidth;
this.ComputationTime(); // 计算时间
var s1 = style.insertRule(
`@keyframes firstAnimation {0% {left:0px;}100% {left:-${this.contentLength}px;}}`,
style.cssRules.length
);
var s2 = style.insertRule(
`@keyframes secondAnimation {0% {left:${this.parentWidth}px;}100% {left:-${this.contentLength}px;}}`,
style.cssRules.length
);
setTimeout(res => {
this.changeState();
}, this.options.delay);
},
// 用速度计算时间(想要保持速度一样,2种状态时间不同需算出)
ComputationTime() {
this.firstAnimationTime = this.contentLength / this.speed;
this.secondAnimationTime = (this.contentLength + this.parentWidth) / this.speed;
},
// 根据状态切换动画
changeState() {
let content = this.$refs.content;
if (this.state == 1) {
content.style.animation = `firstAnimation ${this.firstAnimationTime}s linear`;
this.state = 2;
} else {
content.style.animation = `secondAnimation ${this.secondAnimationTime}s linear infinite`;
}
},
// 监听动画结束事件
Listener() {
let content = this.$refs.content;
content.addEventListener(
'animationend',
res => {
this.changeState();
},
false
);
}
},
mounted() {
this.start();
}
};
</script>
<style lang="scss" scoped>
.noticebar {
display: flex;
align-items: center;
height: 100%;
width: 100%;
background-color: #fff7cc;
.parent {
overflow: hidden;
white-space: nowrap;
margin: 0 auto;
height: 100%;
width: 100%;
position: relative;
.content {
position: absolute;
display: inline-block;
padding: 2px 0;
.content-inside {
display: inline-block;
margin-right: 50px;
}
}
}
}
</style>