需求
- 实现曲线像小山堆一样慢慢隆起:平时使用的echarts中曲线图的动画都是从左至右沿着x轴方向运动,而本次项目中需要每个数据,从0开始慢慢递增至其数据大小,也就是要沿着y轴进行运动。
灵感来源
- 之前看过同学用canvas绘制的贪食蛇的游戏,贪食蛇的移动效果,原理就是沿着需要移动的方向不断的清空重绘,每次重绘,位置向前移动一格,那么在不断的重复过程中,在用户看来,贪食蛇就在移动了,而移动的速度也就跟重绘的时间间隔相关。
- 同理可得,我需要让曲线动起来,那么我就可以不断地清空重绘,每次都把曲线的顶点升高一点,这样就可以实现曲线慢慢隆起的效果了。
实现步骤
- 使用贝塞尔曲线绘制出需要的曲线形状
- 给曲线面积填充颜色
- 使用定时器,修改贝塞尔曲线中心控制点的高度
主要代码
- 绘制曲线及颜色填充
// 绘制曲线及颜色填充
drawCurve(params) {
const rateW = this.eleW / 768;
// 算沙丘中点和两边的一共四个控制点
const centerPointX =
params.centerPointX || params.startPosX + this.halfCurveWidth;
const centerPointY = params.centerPointY || params.partHeightest;
const control1X = params.control1X || centerPointX - 55 * rateW;
const control1Y = params.control1Y || params.startPosY;
const control2X = params.control2X || centerPointX - 55 * rateW;
const control2Y = params.control2Y || centerPointY;
const control3X = params.control3X || centerPointX + 55 * rateW;
const control3Y = params.control3Y || centerPointY;
const control4X = params.control4X || centerPointX + 55 * rateW;
const control4Y = params.control4Y || params.startPosY;
this.context.beginPath();
this.context.moveTo(params.startPosX, params.startPosY);
this.context.bezierCurveTo(
control1X,
control1Y,
control2X,
control2Y,
centerPointX,
centerPointY
);
this.context.moveTo(centerPointX, centerPointY);
this.context.bezierCurveTo(
control3X,
control3Y,
control4X,
control4Y,
params.endPosX,
params.endPosY
);
// 设置线的粗细和颜色
params.isLast
? this.context.setLineDash([])
: this.context.setLineDash([2]);
this.context.strokeStyle = params.isUp
? this.chartData.lineStyle.curve.color.up
: this.chartData.lineStyle.curve.color.down;
this.context.lineWidth = this.chartData.lineStyle.curve.width;
this.context.stroke();
this.context.closePath();
// 颜色填充
this.context.beginPath();
this.context.moveTo(params.startPosX, params.startPosY);
this.context.bezierCurveTo(
control1X,
control1Y,
control2X,
control2Y,
centerPointX,
centerPointY
);
this.context.lineTo(
centerPointX,
params.isUp ? params.heightY || this.heightY : params.lowY || this.lowY
);
this.context.moveTo(centerPointX, centerPointY);
this.context.bezierCurveTo(
control3X,
control3Y,
control4X,
control4Y,
params.endPosX,
params.endPosY
);
this.context.lineTo(
centerPointX,
params.isUp ? params.heightY || this.heightY : params.lowY || this.lowY
);
if (params.isLast) {
const grad = this.context.createLinearGradient(
centerPointX,
centerPointY,
centerPointX,
params.isUp
? params.heightY || this.heightY
: params.lowY || this.lowY
); // 创建一个渐变色线性对象
// 设置渐变颜色
const color = params.isUp
? this.chartData.fillStyle.gradient.up
: this.chartData.fillStyle.gradient.down;
color.forEach((item, index) => {
const stop = item.stop + 0.2 > 1 ? 1 : item.stop + 0.2;
grad.addColorStop(
params.legend && index !== 0 ? stop : item.stop,
item.color
);
});
this.context.fillStyle = grad; // 设置fillStyle为当前的渐变对象
} else {
this.context.fillStyle = params.isUp
? this.chartData.fillStyle.pureColor.up
: this.chartData.fillStyle.pureColor.down;
}
this.context.fill();
}
- 使用定时器实现动画
// 曲线文字动画
animateAll() {
let countNum = 0;
this.itemList = this.setShort(this.itemList);
this.timer = setInterval(() => {
this.context.clearRect(0, 0, this.eleW, this.eleH);
this.drawAll(this.wordList, this.lineList, this.curveList);
this.itemList.map(item => {
item.curveList.map((item1, index) => {
if (item1.isUp) {
if (
item1.partHeightest &&
item1.partHeightest <= item1.partShortest - countNum
) {
this.drawCurve({
...item1,
partHeightest: item1.partShortest - countNum
});
if (index === item.curveList.length - 1) {
this.drawChangeWord(item1, countNum);
}
if (item1.partHeightest === item1.partShortest - countNum) {
this.curveList.push(item1);
item1.wordList.map(word => {
this.wordList.push(word);
});
}
}
} else {
if (
item1.partHeightest &&
item1.partHeightest >= item1.partShortest + countNum
) {
this.drawCurve({
...item1,
partHeightest: item1.partShortest + countNum
});
if (index === item.curveList.length - 1) {
this.drawChangeWord(item1, countNum);
}
if (item1.partHeightest === item1.partShortest + countNum) {
this.curveList.push(item1);
item1.wordList.map(word => {
this.wordList.push(word);
});
}
}
}
});
});
countNum = countNum + 1;
const flag = this.itemList.every(item => {
const flag = item.curveList.every(item1 => {
return (
(item1.isUp &&
item1.partHeightest > item1.partShortest - countNum) ||
(!item1.isUp &&
item1.partHeightest < item1.partShortest + countNum) ||
!item1.partHeightest
);
});
return flag;
});
if (flag) {
clearInterval(this.timer);
this.context.clearRect(0, 0, this.eleW, this.eleH);
this.drawAll(this.wordList, this.lineList, this.curveList);
}
}, 50);
}
demo展示
好像不能上传视频,所以就截取了两张过程图。
总结
- 这个动画的实现,难点在于三次贝塞尔曲线的运用,可参考相关文章进行学习,如blog.csdn.net/fe_watermel…
- 颜色的填充,重点在于空间闭合,所以使用两段贝塞尔曲线拼成一段完整曲线时,可以在中间画一条竖线路径分割,最后会自动闭合空间,如下图所示: