一、背景:
在最近的可视化项目中,视觉设计采用钢铁的写实风格,整体页面布局为左中右,动效需要实现在刚进入页面时,左右板块撞向中间板块,产生撞击光效和火花飞散的动画效果。
二、实现思路:
需要考虑以下几个方面:
三、实现过程:
1. 撞击效果的实现
主要使用到CSS3 的animation和translate3d
(1)animation的属性解析
name :要绑定到选择器的关键帧的名称
duration :动画指定需要多少秒或毫秒完成
timing-function :设置动画将如何完成一个周期
delay :设置动画在启动前的延迟间隔
iteration-count :定义动画的播放次数
direction :指定是否应该轮流反向播放动画
fill-mode :规定当动画不播放时,要应用到元素的样式
play-state: 指定动画是否正在运行或已暂停
(2)translate3d() css函数在三维空间中重新定位一个元素。
语法:translate3d(tx,ty,tz)
tx——表示平移矢量的横坐标的
ty——表示平移向量的纵坐标的
tz——表示平移矢量的z分量的
.animated {
animation: 0.8s cubic-bezier(0.94,0,1,1);
animation-fill-mode: both;
animation-delay:0.3s;
}
@keyframes bounceInLeft {
0% {
opacity: 0;
transform: translate3d(-100%, 0, 0);
}
to {
opacity: 1;
transform: none;
}
}
.bounceInLeft {
animation-name: bounceInLeft;
}
@keyframes bounceInRight {
0% {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
to {
opacity: 1;
transform: none;
}
}
.bounceInRight {
animation-name: bounceInRight;
}
2. 撞击火光和火花的实现
(1)画光,光效是两个对称的圆,然后做径向渐变
Grad.addColorStop定义了两个渐变的色阶
function drawLight(x,y,direction) {
var grad=cxt.createRadialGradient(x,y,0,x,y,1000);
grad.addColorStop(0,"rgba(255,180,0,0.4)");
grad.addColorStop(1,"rgba(255,180,0,0)");
cxt.fillStyle=grad;
cxt.beginPath();
cxt.arc(x,y,1000,0,Math.PI*2,direction);
cxt.closePath();
cxt.fill();
}
(2)画粒子,声明粒子类
class Spark {
constructor() {
this.startX = 600; //初始位置
this.startY = 100;
this.radius = 5;
this.radiusY = 1;
this.radiusOffset = 0;
this.radiusA = 0.1;
this.v0x = 2; //初始速度
this.v0y = 10;
this.speedAY = 4; //加速度
this.speedAX = -0.5;
this.speedX = 2; //实时速度
this.speedY = 10;
this.offsetX = 0;
this.offsetY = 0;
this.t = 0;
this.color = "#0000FF";
this.tY = 0;
this.time = 0;
this.dirDown = 0;
this.dirUp = 1;
this.dir = 0; //上下方向状态
this.cycleYTimes = 0;
this.waiteTime = 0;
this.startTime = 0;
this.trail = [];
this.dirLeft = 0;
this.dirRight = 1;
this.dirX = 1; //水平方向状态
}
calc(){
if (this.dirX == this.dirLeft) {
this.trail.push({
x: (this.startX - this.offsetX),
y: (this.startY + this.offsetY)});
} else {
this.trail.push({
x: (this.startX + this.offsetX),
y: (this.startY + this.offsetY)});
}
if(this.trail.length >2){
this.trail.splice(0,1);
}
}
}
3)批量生产多个粒子
for (var i = 0; i < 200;i++) {
var spark = new Spark();
spark.startY = 100 + Math.random() * 1800;
spark.v0x = 10 + Math.random() * 8;
spark.speedX = spark.v0x;
spark.radius = 2 + Math.random() * 15;
spark.radiusY = 1 + Math.random() * 2;
let randomDirX = Math.random() * 2;
if (randomDirX < 1) {
//向左面飞的
spark.startX = this.startLeft;
spark.dirX = 0;
spark.dir = 0;
} else {
//向右面飞的
spark.startX = this.startRight;
spark.dirX = 1;
spark.dir = 1;
spark.speedY = spark.v0x - 1;
}
spark.startTime = Math.random() * 10;
spark.color = "#fff";
sparks.push(spark);
}
(4)画粒子,粒子是根据随机数计算出的半径绘制的小椭圆,然后加上外发光的阴影
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
参数:(起点x, 起点y, 半径x, 半径y, 旋转的角度,起始角,结果角,方向)
function drawBall(spark) {
cxt.shadowBlur = 20;
cxt.shadowColor="#fff";
cxt.fillStyle=spark.color;
cxt.beginPath();
if (spark.dirX == spark.dirLeft) {
cxt.ellipse(
spark.startX - spark.offsetX,
spark.startY + spark.offsetY,
spark.radius + spark.radiusOffset,
spark.radiusY + spark.radiusOffset,
0,
0,
Math.PI*2,
true
);
} else {
cxt.ellipse(
spark.startX + spark.offsetX,
spark.startY + spark.offsetY,
spark.radius + spark.radiusOffset,
spark.radiusY + spark.radiusOffset,
0,
0,
Math.PI*2,
true);
}
cxt.closePath();
cxt.fill();
}
(5)画轨迹,
核心公式:v0t+1/2at*t
水平方向,向左和向右飞的粒子作匀减速运动
垂直方向,向左和向右飞的粒子在固定幅度内上下运动
function update(spark){
if (spark.waiteTime >spark.startTime) {
if (spark.speedX >0) {
if (spark.cycleYTimes <3){
if (spark.dir == spark.dirDown) {
if (spark.speedY >-spark.v0y) {
spark.offsetY = spark.v0y * spark.tY - 0.5 * spark.tY * spark.tY *
spark.speedAY;
spark.speedY -= spark.speedAY;
} else {
spark.offsetY = spark.v0y * spark.tY - 0.5 * spark.tY * spark.tY *
spark.speedAY;
spark.dir = spark.dirUp;
spark.tY = 0;
spark.speedY += spark.speedAY;
spark.cycleYTimes++;
}
} else if (spark.dir == spark.dirUp) {
if (spark.speedY
(6)执行动画,每隔30毫秒清理一次画布
function play(){
cxt.clearRect(0,0,5760,1890);
for (let i = 0;i < sparks.length;i++) {
update(sparks[i]);
}
}
drawLight(this.startLeft,950,true)
drawLight(this.startRight,950,false)
setInterval(play,30);
四、完成效果
五、注意事项
五、注意事项
1.为了保证动画时间点的准确性和连续性,不要设置setTimeOut定时器去卡动画时间,这样可能不准,应当监听css动画的结束状态,然后开始绘制canvas动画;
2.canvas画布的层级需要提到最高,但是注意,动画结束后应及时清理掉canvas画布,否则画布会一直停留在页面上,阻挡页面元素的点击等事件;
3.每次切换浏览器的标签页选中状态,应将动画状态重置,重新加载动画,但是不要重新刷新页面,这样会出现等待白屏,影响用户体验,用到的方法是监听浏览器标签页的可视状态(visibilitychange)。
document.addEventListener('visibilitychange', function() {})