光斑属性
class Facula {
//初始状态的光斑小球
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r || random(10, 15); // 光斑小球随机半径
this.birth = Date.now(); // 光斑生成时间
this.process = 0; // 光斑运动时长,相对于最大生命值的时长
this.duration = 100000; // 光斑的最大生命时长
this.distance = 5; // 单位位移,光斑的最大生命时长和单位位移动,决定了光斑的运动距离
this.opacity = 1;
this.color = randomColor();
this.angle = degreeToRadian(random(0, 360)); // 光斑与坐标轴形成的夹角,用于三角函数换算x,y坐标
this.animation = Easing.easeOut; // 光斑缓动函数
this.direction = false;
}
// 实现光斑闪烁效果
flash() {}
// 实现光斑的运动轨迹
move() {}
// 在canvas上绘制光斑,ctx为canvas.getContent()
draw(ctx) {}
}
/**
* 角度值转弧度值
* @param {*} deg 角度值
*/
function degreeToRadian(deg) {
return deg * Math.PI / 180;
}
/**
* 生成随机数 [min, max)
* @param {*} min 随机数最小值
* @param {*} max 随机数最大值
* @param {*} times 随机数是times的倍数
*/
function random(min, max, times = 1) {
const num = Math.floor(Math.random() * (max - min) + min);
return (num % times) + num;
}
/**
* 生成随机颜色
*/
function randomColor() {
return `rgba(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)}, 1)`
}
// 贝塞尔曲线函数实现 Easing:https://github.com/gre/bezier-easing
光斑运动
光斑的运动由初始位置向 this.angle 角度随着时间变化而产生位移。
- 匀速运动
class Facula {
//初始状态的光斑小球
constructor(x, y, r) {
// ...其他属性
this.distance = 5; // 单位位移,光斑的最大生命时长和单位位移动,决定了光斑的运动距离
this.angle = degreeToRadian(random(0, 360)); // 光斑与坐标轴形成的夹角,用于三角函数换算x,y坐标
}
// 光斑运动
move() {
// 计算单位时间 requestAnimationFrame 后向角度 this.angle 运动 this.distance 距离所对应的(x,y)值
// ---------- 匀速运动 ----------
this.x += this.distance * Math.cos(this.angle);
this.y += this.distance * Math.sin(this.angle);
// ---------- 匀速运动 ----------
// 光斑闪烁效果的属性值计算
this.flash();
}
// 实现光斑闪烁效果
flash() {}
// 在canvas上绘制光斑,ctx为canvas.getContent()
draw(ctx) {}
}
- 变速运动
实现方案参考Army老师的DEMO
(1)如上图,要求解光斑小球减速运动 dt 时间的距离:s(OB)
(2)我们将减速运动换算到等量的匀速运动上得到:s(OB) = s(OC)
根据匀速运动得知:t(OA) / t(OC) = s(OA) / s(OC)
所以 s(OC) = s(OA) * t(OC) / t(OA)
至此我们知道 s(OB) = s(OC) = s(OA) * t(OC) / t(OA)
将 t(OC) / t(OA) 看做一个时间倍率 percent
上文可知单位时间的匀速运动距离:s(OA) = distance * sin(angle)
我们就可以得到 s(OB) = distance * sin(angle) * percent
(3)贝塞尔曲线函数的作用就是关于时间的倍率换算:
所以 percent = easeOut(process/duration) 其中
process 是光斑生成后已经运动的时间,duration 是光斑整个生命周期的总时间
贝塞尔曲线函数的原理可以参考文章:深入浅出贝塞尔曲线及应用示例
class Facula {
//初始状态的光斑小球
constructor(x, y, r) {
// ...其他属性
this.birth = Date.now(); // 光斑生成时间
this.process = 0; // 光斑运动时长,相对于最大生命值的时长
this.duration = 100000; // 光斑的最大生命时长
this.animation = Easing.easeOut; // 光斑缓动函数
}
// 光斑运动
move() {
// 计算单位时间 requestAnimationFrame 后向角度 this.angle 运动 this.distance 距离的(x,y)值
// ---------- 缓动运动 ----------
let diff = Date.now() - this.birth; // 计算运动时间
let percent = this.process / this.duration; // 计算运动时间占总时间的比例
percent = this.animation(percent); // 利用贝塞尔曲线渐出函数,通过运动时间比例换算出单位长度的变换倍率
this.x += this.distance * Math.cos(this.angle) * percent;
this.y += this.distance * Math.sin(this.angle) * percent;
// ---------- 缓动运动 ----------
this.process += diff; // 记录光斑生命进行时长
}
// 实现光斑闪烁效果
flash() {}
// 在canvas上绘制光斑,ctx为canvas.getContent()
draw(ctx) {}
}
光斑闪烁
flash() {
// 在一定范围内改变透明度和半径,实现闪烁效果
// opacity 的区间为 [0.1, 0.8] 也可以放在光斑属性里动态设置
if (this.opacity <= 0.1) {
this.direction = true; // 光斑变亮 半径变大
}
if (this.opacity >= 0.8) {
this.direction = false; // 光斑变暗 半径变小
}
if (this.direction) {
this.opacity += 0.1;
this.r += 0.5;
} else {
this.opacity -= 0.1;
if (this.r > 0.5) { // 这里要保证半径不能小于0,否则绘制过程会报错
this.r -= 0.5;
}
}
this.color = getColorWithOpacity(this.color, this.opacity);
}
/**
* 给颜色加透明度
* @param {*} color 初始颜色值
* @param {*} opacity 透明度值
* @returns 计算后的颜色值
*/
function getColorWithOpacity(color, opacity) {
if (/rgba/.test(color) || /rgb/.test(color)) {
let cMatch = color.match(/((?<=(rgba\())|(?<=(rgb\())).*(?=(\)))/)[0]
let cArr = cMatch.split(",");
return `rgba(${+cArr[0] || random(0, 255)}, ${+cArr[1] || random(0, 255)}, ${+cArr[2] || random(0, 255)}, ${opacity})`
}
return `rgba(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)}, 1)`
}
绘制光斑
draw(ctx) {
this.move(); // 光斑变速运动 计算位置
this.flash(); // 光斑闪烁效果 计算透明度和半径
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI*2, true);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}