先看效果
刚开始的需求是,制作一个类似电视广告字幕的效果,我没什么思路,后来看了字幕的实现方式,就是用css动画的移动实现,于是我开始了对这段动画的驾驭。
技术:万能的vue.js html5 css3
首先我们要提取到一个html文件中,在文件中引用需要的CDN
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>demo</title>
</head>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<style lang='scss' scoped>
</style>
<body>
<div id="app">
</div>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.slim.js"
integrity="sha256-HwWONEZrpuoh951cQD1ov2HUK5zA5DwJ1DNUXaM6FsY=" crossorigin="anonymous"></script>
<script>
new Vue({
el: '#app',
data: function () {
return {
}
},
mounted() {
},
methods: {
}
})
</script>
</html>
我们写一个外面的块,宽度是100%,再写一个里面的块,再里面的块里放置内容。内容部分暂且不管,我主要讲实现思路。
<body>
<div id="app">
<template>
<div class="marquee-outer-wrapper">
<div class="marquee-inner-wrapper">
<div class="span first-marquee">
<div class="item" v-for="item in cionList" :key="item.id">
<img :src="item.icon" alt="" class="icon" />
<div class="title">{{item.name}}</div>
<div class="text">[{{item.crypto}}]</div>
<div class="pre">${{item.a}}</div>
<div :class="item.isRed > 0 ? 'green' : 'red'">
<i
:class="item.isRed > 0 ? 'el-icon-caret-top' : 'el-icon-caret-bottom'">{{item.isRed}}%</i>
</div>
</div>
</div>
</div>
</div>
</template>
</div>
</body>
.marquee-outer-wrapper {
width: 100%;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
}
.marquee-inner-wrapper {
background: #1D2330;
height: 50px;
line-height: 50px;
margin: 0 auto;
white-space: nowrap;
position: relative;
}
.marquee-inner-wrapper .span {
position: absolute;
top: 0;
left: 100%;
/* right: 100%; */
height: 100%;
display: flex;
flex-direction: row;
justify-content: space-evenly;
}
.item {
display: flex;
flex-direction: row;
justify-content: start;
}
.first-marquee {
-webkit-animation: 20s first-marquee linear infinite normal;
animation: 20s first-marquee linear infinite normal;
padding-left: 20%;
}
.icon {
width: 22px;
height: 22px;
margin-top: 14px;
margin-left: 20px;
}
.title {
line-height: 50px;
font-size: 14px;
color: #2D74C4;
margin-left: 6px;
}
.text {
line-height: 50px;
font-size: 12px;
color: #939494;
margin-left: 4px;
}
.pre {
line-height: 50px;
font-size: 14px;
color: white;
font-weight: bold;
margin-left: 6px;
}
.red {
line-height: 50px;
font-size: 10px;
color: #E5541E;
margin-left: 4px;
}
.green {
line-height: 50px;
font-size: 10px;
color: #049E62;
margin-left: 4px;
}
@keyframes first-marquee {
0% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
/* 向左移动 */
100% {
-webkit-transform: translate3d(-200%, 0, 0);
transform: translate3d(-200%, 0, 0);
display: none;
}
}
这里的思路主要是把里面的div放在屏幕的最右侧,然后设置向左迅速运动的动画,达到字幕滚动的效果。
但是问题来了,动画只运行一次,结束后才运行第二次。功能达到了但是效果不够完美。
于是,我想再写一个里面的div,但是是在第一个div先运行一段时间后再跑起来,思路有了,开始实现。
<body>
<div id="app">
<template>
<div class="marquee-outer-wrapper">
<div class="marquee-inner-wrapper">
<div class="span first-marquee">
<div class="item" v-for="item in cionList" :key="item.id">
<img :src="item.icon" alt="" class="icon" />
<div class="title">{{item.name}}</div>
<div class="text">[{{item.crypto}}]</div>
<div class="pre">${{item.a}}</div>
<div :class="item.isRed > 0 ? 'green' : 'red'">
<i
:class="item.isRed > 0 ? 'el-icon-caret-top' : 'el-icon-caret-bottom'">{{item.isRed}}%</i>
</div>
</div>
</div>
<div class="span second-marquee">
<div class="item" v-for="item in cionList" :key="item.crypto">
<img :src="item.icon" alt="" class="icon" />
<div class="title">{{item.name}}</div>
<div class="text">[{{item.crypto}}]</div>
<div class="pre">${{item.a}}</div>
<div :class="item.isRed > 0 ? 'green' : 'red'">
<i
:class="item.isRed > 0 ? 'el-icon-caret-top' : 'el-icon-caret-bottom'">{{item.isRed}}%</i>
</div>
</div>
</div>
</div>
</div>
</template>
</div>
</body>
.second-marquee {
-webkit-animation: 20s first-marquee linear 8s infinite normal;
animation: 20s first-marquee linear 8s infinite normal;
padding-left: 20%;
}
在原有的基础上,又添加了一个类是span的div。不同的是它运行的动画是.second-marquee。这个动画和原有的动画区别就在于,这个动画相对上一个动画延时8s播放,达到一前一后的效果。
现在动画动起来了,但是需求又增加了。
大致内容就是在地址栏输入参数,根据参数的值可以修改一些属性,比如整体的高度,滚动条的颜色,运行的速度,运行的方向,甚至字体的大小。那么先来处理参数。
getParams() {
let str = window.location.href
if (str.indexOf("?") == -1) {
return
} else {
let params = str.split('?')[1]
let paramsList = params.split('&')
let paramsObj = {}
for (let i = 0; i < paramsList.length; i++) {
const item = paramsList[i];
paramsObj[item.split('=')[0]] = item.split('=')[1]
}
// console.log(paramsObj);
this.params = paramsObj
this.size = this.params.size
}
}
这时,我们不知道传来的参数都是什么,除了size,因为是后加的,我就直接提取了出来。
那么接下来考虑的是怎么改这些属性。因为这些属性都是写在样式里面的,我首先想到vue和jquery混写,但是网上都说尽量避免这种写法。那就另辟蹊径,利用document.styleSheets获取当前文件的所有css,再利用deleteRule方法和insertRule方法修改。继续实现。
setStyle() {
if (!this.params) {
return
} else {
console.log(document.styleSheets[1]);
var style = document.styleSheets[1];
// 控制向左向右
if (!this.params.direction) {
return
} else if (this.params.direction == "left") {
style.deleteRule(2)
style.insertRule(".marquee-inner-wrapper .span { position: absolute; top: 0px; left: 100%; height: 100%; display: flex; flex-direction: row; justify-content: space-evenly; }", 2);
style.deleteRule(12)
style.insertRule("@keyframes first-marquee { \n 0% { transform: translate3d(0px, 0px, 0px); }\n 100% { transform: translate3d(-200%, 0px, 0px); display: none; }\n}", 12)
} else {
style.deleteRule(2)
style.insertRule(".marquee-inner-wrapper .span { position: absolute; top: 0px; right: 100%; height: 100%; display: flex; flex-direction: row; justify-content: space-evenly; }", 2);
style.deleteRule(12)
style.insertRule("@keyframes first-marquee { \n 0% { transform: translate3d(0px, 0px, 0px); }\n 100% { transform: translate3d(200%, 0px, 0px); display: none; }\n}", 12)
}
// 控制高度
if (!this.params.height) {
return
} else {
style.deleteRule(11)
style.insertRule(".green { line-height: " + this.params.height + "px; font-size: " + this.size * 0.7 + "px; color: rgb(4, 158, 98); margin-left: 4px; }", 11)
style.deleteRule(10)
style.insertRule(".red { line-height: " + this.params.height + "px; font-size: " + this.size * 0.7 + "px; color: rgb(229, 84, 30); margin-left: 4px; }", 10)
style.deleteRule(9)
style.insertRule(".pre { line-height: " + this.params.height + "px; font-size: " + this.size + "px; color: white; font-weight: bold; margin-left: 6px; }", 9)
style.deleteRule(8)
style.insertRule(".text { line-height: " + this.params.height + "px; font-size: " + this.size * 0.8 + "px; color: rgb(147, 148, 148); margin-left: 4px; }", 8)
style.deleteRule(7)
style.insertRule(".title { line-height: " + this.params.height + "px; font-size: " + this.size + "px; color: rgb(45, 116, 196); margin-left: 6px; }", 7)
style.deleteRule(6)
style.insertRule(".icon { width: " + this.size * 1.5 + "px; height: " + this.size * 1.5 + "px; margin-top: " + (this.params.height - this.size * 1.5) / 2 + "px; margin-left: 20px; }", 6)
style.deleteRule(1)
style.insertRule(".marquee-inner-wrapper { background: rgb(29, 35, 48); height: " + this.params.height + "px; line-height: " + this.params.height + "px; margin: 0px auto; white-space: nowrap; position: relative; }", 1)
}
// 控制背景颜色
if (!this.params.backgroundColor) {
return
} else {
document.getElementsByClassName("marquee-inner-wrapper")[0].style.backgroundColor = "rgb(" + this.params.backgroundColor + ")"
}
// 控制速度
if (!this.params.speed) {
return
} else {
style.deleteRule(4)
style.insertRule(".first-marquee { animation: " + this.params.speed + "s linear 0s infinite normal none running first-marquee; padding-left: 20%; }", 4)
style.deleteRule(5)
style.insertRule(".second-marquee { animation: " + this.params.speed + "s linear " + (this.params.speed) / 2 + "s infinite normal none running first-marquee; padding-left: 20%; }", 5)
}
}
}
这里应该是有优化的空间,我说一下我这种写法的坑。注意一下控制高度的地方,这里顺序是从高到低,为什么这样操作呢,因为当你使用deleteRule方法后,样式的数量会减少,下标也会随之变化,那为什么要用deleteRule方法呢,因为insertRule方法是插入一条,覆盖之前的,这样我们就先发制人,先删除再插入就ok了。
再来说一下insertRule后面跟的字符串就是这个样式的并行字符串,非常容易写错,怎么办呢,就是打印document.styleSheets,这里面每个样式都写的一清二楚,复制过来再把变量替换进去就ok啦。
现在是两列“小火车”交替行进,如何控制他们的距离呢,也就是两列小火车的距离,就在这里
.first-marquee {
-webkit-animation: 0s first-marquee linear infinite normal;
animation: 0s first-marquee linear infinite normal;
padding-left: 20%;
}
.second-marquee {
-webkit-animation: 0s first-marquee linear 0s infinite normal;
animation: 0s first-marquee linear 0s infinite normal;
padding-left: 20%;
}
注意看这个padding-left: 20%;,你可以试试其他的值有什么意想不到的效果。
这里也是经历了我的魔改最后实现一个完美的效果。
最后再来看一遍成品
最后的最后,这个数据接口还是自己找吧,网络上应该不少,或者使用假数据练习。
能读到这的都是大牛,哈哈哈哈我废话贼多
拓展
你觉得这种滚动效果可以做什么有趣的功能呢?
鼠标悬停可以暂停动画,配合点击事件,做一个小小的播放器怎么样。
放一些链接进去,点击之后在小窗里面显示网页内容。
还能怎么玩?