先上效果图
这篇文章主要分享上图“中间部分传送带”的实现,至于箭头运动的动画请参考可视化大屏-路径-箭头动画。
中间部分效果图
实现原理
整个传送带其实是利用echarts的lines实现,传送带的每个齿都是一个点,设置为symbol: 'rect',首先将所有点测量出来(测量方法参考可视化大屏-路径-箭头动画),简单来说就是以背景图左下角为原点,量出每个点的横纵坐标。然后使用lines将所有点变成一段运动轨迹,一个点的运动轨迹如下:
接着循环所有点,每个点都需要有一条轨迹,注意:所有点要求同时开启动画,如果写在同一个lines的data里就会变成下面这种错误的效果:
正确的写法应该是这样,每一个齿的运动都要放入新的lines里:
对了,外面的传送带其实只是一张静态图。。。这个动不了
实现过程
1.首先测量所有齿的点,并利用散点图画出来,这样一眼就能看出哪个齿的坐标有问题
注意:这里找齿坐标其实是有技巧的,比如我这里一共126个齿,每个齿都去自己手动量那不得累死。。。传送带其实是一个上下左右对称的图形,比如左边环的纵坐标和右边环纵坐标相同,上边和下边的横坐标相同,纵坐标可加减高度计算得出等,再比如根据上边第一个点循环一下就可得出后面一排的点。可参考下面代码:
getData (){
// 上
let centerT = []
let i = 0;
do {
centerT.push([354 + i * 9, 153]) // 354是顶部第一个点起点横坐标,152:顶部点纵坐标
i++
} while (centerT[centerT.length - 1][0] < 866) // 878是顶部最后一个点横坐标
// 下
let centerB = centerT.map(item => {
return [item[0], 123] // 124:底部点纵坐标
}).reverse()
let tpl = [ // 传送带上所有点
// 上
...centerT,
// 右
[876, 152.5],
[884.5, 147],
[887, 138],
[884, 129],
[876, 124],
// 下
...centerB,
// 左
[345, 124],
[337, 129],
[335, 138],
[337, 147],
[345, 152],
// [354,152],
]
return [tpl]
},
结合散点图出现如下效果:
2.然后隐藏散点图(这里设symbolSize: 0),然后使用路径图将一个齿的的运动轨迹配到lines中,即每个齿都需要到剩余其他齿的位置跑一遍,效果如下:
额。。还是加上背景吧:
3.将每个齿都加上路径动画,这里循环了所有齿,将每个点作为线段运动起点,前面的点放后面,这样运动时每个点都独立的同时运动
// 线数据组装
let creatOtherDot = function (arr, index) {
let copyArr = [...arr]
return [...copyArr.slice(index), ...copyArr.slice(0, index)];
}
let finalLinesData = [] // 最终数据
linesData.map((item, index) => {
finalLinesData.push(creatOtherDot(linesData, index)) // 循环所有点,将每个点作为线运动起点,前面的点放后面,这样运动时每个点都独立的同时运动
})
效果如下:
4.最后在图层上面盖上传送带外面的皮带。这是放大后的效果,是不是很完美:
其他的关于测量值与真实值的转换这里就不再重复叙述了,也就是下面这部分代码,比较简单。还是不太理解的话可参考我上篇可视化大屏-路径-箭头动画:
最后vue封装的代码如下
<!--
原理:还是使用路径图,首先有一组点【链条上的齿轮点】,循环这些点,以每个点为起点,
将前面的点放后面进行排列,多个lines同时运动【注意:若不是设置的多个lines,则不是想要的效果】
路径图-传送带“多点线段循环动画”组件,针对一组点循环运动的情况
若图片有变化:
1.修改 imgWH 的宽高为最新图片的宽高
2.重新在原图上量出点合集并赋值给dotsArr
-->
<template>
<div class="chart-box" :id="id" v-show="!this.timer"></div>
</template>
<script>
export default {
name: 'linesChartAnimate',
props: {
id: {
type: String,
default: 'ChartBox'
},
imgWH: {
type: Object,
default(){
return {
width: 1024, // 当前这张图是 1024*240的图
height: 240
}
}
},
dotsArr: { // 需要循环运动的点集合
type: Array,
default(){
return []
}
},
speed: { // 转速
type: Number,
default: 50
}
},
data () {
return {
myChart: '',
// 注意:因为图片在现实的时候可能会拉伸,所以设置actualWH和imgWH两个变量
actualWH: {
width: 0,
height: 0
},
timer: null
}
},
mounted () {
this.actualWH = { // 渲染盒子的大小
width: this.$el.clientWidth,
height: this.$el.clientHeight
}
this.myChart = this.$echarts.init(document.getElementById(this.id))
this.draw()
this.eventListener(true)
},
methods: {
getData (){
// 上
let centerT = []
let i = 0;
do {
centerT.push([354 + i * 9, 153]) // 354是顶部第一个点起点横坐标,152:顶部点纵坐标
i++
} while (centerT[centerT.length - 1][0] < 866) // 878是顶部最后一个点横坐标
// 下
let centerB = centerT.map(item => {
return [item[0], 123] // 124:底部点纵坐标
}).reverse()
let tpl = [ // 传送带上所有点
// 上
...centerT,
// 右
[876, 152.5],
[884.5, 147],
[887, 138],
[884, 129],
[876, 124],
// 下
...centerB,
// 左
[345, 124],
[337, 129],
[335, 138],
[337, 147],
[345, 152],
// [354,152],
]
return [tpl]
},
// 当前宽
getCurrentW(val){
return (this.actualWH.width / this.imgWH.width) * val
},
// 当前高
getCurrentH(val){
return (this.actualWH.height / this.imgWH.height) * val
},
getOption () {
// 点合集-在图片上一个一个量的,注意以渲染盒子左下角为原点,点取值方法:以图片左下角为原点,量几个线段点的(x,y)
let dotsArr = this.dotsArr && this.dotsArr.length ? this.dotsArr : this.getData()
// 点的处理-量图上距离转换为在渲染盒子中的距离 start
let scatterData = []
let linesData = [] // 点的路径
dotsArr.map(item => {
item.map(sub => {
sub[0] = this.getCurrentW(sub[0]) // x值
sub[1] = this.getCurrentH(sub[1]) // y值
})
// 将转换后的点存储
scatterData = scatterData.concat(item)
linesData.push(...item) // 存储将转换后的值
})
// 点的处理-量图上距离转换为在渲染盒子中的距离 end
// 线数据组装
let creatOtherDot = function (arr, index) {
let copyArr = [...arr]
return [...copyArr.slice(index), ...copyArr.slice(0, index)];
}
let finalLinesData = [] // 最终数据
linesData.map((item, index) => {
finalLinesData.push(creatOtherDot(linesData, index)) // 循环所有点,将每个点作为线运动起点,前面的点放后面,这样运动时每个点都独立的同时运动
})
// 。。。。。。 运动点动画线段集合 start 。。。。。。
let seriesLine = []
finalLinesData.map(item => {
seriesLine.push({
type: 'lines',
coordinateSystem: 'cartesian2d',
// symbol:'arrow',
zlevel: 1,
symbol: ['none', 'none'],
polyline: true,
silent: true,
effect: {
symbol: 'rect',
show: true,
period: this.speed, // 箭头指向速度,值越小速度越快
trailLength: 0, // 特效尾迹长度[0,1]值越大,尾迹越长重
// symbolSize: parseInt(Math.min(this.getCurrentW(5),this.getCurrentH(5))), // 图标大小
symbolSize: this.getCurrentW(5), // 图标大小
},
lineStyle: {
width: 1,
normal: {
opacity: 0,
curveness: 0.4, // 曲线的弯曲程度
color: '#536e93'
}
},
data: [{
coords: item
}]
})
})
// 。。。。。。 运动点动画线段集合 end 。。。。。。
let option = {
backgroundColor: 'transparent',
xAxis: {
type: 'value',
show: false,
min: 0,
max: this.actualWH.width,
axisLine: {
lineStyle: {
color: 'red'
}
},
splitLine: {
lineStyle: {
color: 'red'
}
}
},
yAxis: {
type: 'value',
show: false,
min: 0,
max: this.actualWH.height,
axisLine: {
lineStyle: {
color: 'red'
}
},
splitLine: {
lineStyle: {
color: 'red'
}
}
},
grid: {
left: '0%',
right: '0%',
top: '0%',
bottom: '0%',
containLabel: false
},
series: [
// 多段点
{
zlevel: 2,
symbolSize: 0,
// symbolSize: 5,
symbol: 'rect',
data: scatterData,
type: 'scatter',
color: '#536e93'
},
...seriesLine
]
};
return option
},
// 绘制图表
draw () {
this.myChart.clear()
this.resetChartData()
},
// 刷新数据
resetChartData () {
this.myChart.setOption(this.getOption(), true)
},
// 。。。。。 resize 相关优化 start 。。。。。。
clearTimer(){
this.timer && clearTimeout(this.timer)
this.timer = null
},
eventListener(bool){
if (!bool) { // 销毁
window.removeEventListener('resize', this._eventHandle)
this.clearTimer()
} else {
window.addEventListener('resize', this._eventHandle, false)
}
},
// 优化-添加resize
_eventHandle(){
this.clearTimer()
this.timer = setTimeout(() => {
this.clearTimer();
this.$nextTick(() => {
this.myChart && this.myChart.resize()
})
}, 500)
},
// 。。。。。 resize 相关优化 end 。。。。。。
},
beforeDestroy () {
this.myChart && this.myChart.dispose()
this.eventListener() // 销毁
}
}
</script>
<style scoped>
.chart-box {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
</style>
组件使用:其中dotsArr可传入,不传可看到默认效果:
如果本篇文章对你有帮助的话请留个关注,谢谢啦。