因为刚考完试想摸鱼,而且确实对之前的theme不太满意,因此重新装修了一下博客,装修完了发现有点空空的,所以想到了去写一个点击动画。但市面上的点击动画,比如什么点击出爱心、点击炸烟花、点击出文字之类的,都不太符合我的个人喜好,所以我决定自己学自己写
先看效果,就像水波纹或者雨滴一样,我自己非常喜欢

代码部分如下,仿照了这个代码的逻辑框架,注释部分ai-assisted
首先是clickEffect()
函数,整个代码的框架。前面进行了变量的定义,后面添加事件监听,这样可以获取鼠标点击的事件
这块遇到过的问题:
①页面无法滚动:<canvas>
元素覆盖了整个页面,导致鼠标事件被<canvas>
捕获,而无法传递到页面的其他元素上。解决方法:pointer-events: none;
确保鼠标事件可以穿透<canvas>
元素,传递到下面的元素。
②页面切换回来后失效:页面失去焦点(例如切换到其他标签页)时,requestAnimationFrame
会暂停执行,以节省资源。当页面重新获得焦点时,requestAnimationFrame
不会自动恢复执行。解决方法:在页面重新获得焦点时手动触发一次loop()
函数,以恢复动画效果。
function clickEffect() {
// 存储圆形元素的数组
let circles = [];
// 画布的宽度和高度
let width, height;
// 2D 绘图上下文
let ctx;
// 创建一个 canvas 元素
const canvas = document.createElement("canvas");
// 将 canvas 元素添加到文档的 body 中
document.body.appendChild(canvas);
// 设置 canvas 的样式,使其覆盖整个屏幕,且位于最上层,不接收指针事件
canvas.setAttribute("style", "width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;");
// 检查是否支持 canvas 绘图上下文和添加事件监听器
if (canvas.getContext && window.addEventListener) {
// 获取 2D 绘图上下文
ctx = canvas.getContext("2d");
// 初始化 canvas 的大小
updateSize();
// 监听窗口大小改变事件,调整 canvas 大小
window.addEventListener('resize', updateSize, false);
// 开始动画循环
loop();
// 监听点击事件,点击时创建新的圆形
window.addEventListener("click", function(e) {
circles.push(new Circle(e.clientX, e.clientY));
}, false);
// 监听文档可见性变化事件,可见时继续动画循环
document.addEventListener("visibilitychange", function() {
if (document.visibilityState === "visible") {
loop();
}
}, false);
} else {
// 不支持 canvas 或事件监听时输出错误信息
console.log("canvas or addEventListener is unsupported!");
}
// 更新 canvas 大小的函数
function updateSize() {
//...
}
// Circle类
class Circle {
//...
}
// 动画循环函数
function loop() {
//...
}
// 移除不透明度为 0 的圆形
function removeCircles() {
//...
}
}
JavaScript然后是updateSize()
函数,用来更新canvas的尺寸
function updateSize() {
// 设置 canvas 的宽高,扩大两倍以保证高分辨率显示
canvas.width = window.innerWidth * 2;
canvas.height = window.innerHeight * 2;
// 设置 canvas 的显示宽度和高度
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
// 缩放绘图上下文,使绘制的内容在视觉上保持正常大小
ctx.scale(2, 2);
// 存储窗口的实际宽度和高度
width = window.innerWidth;
height = window.innerHeight;
}
JavaScriptCircle
类,设置圆的初始半径、不透明度和颜色,然后在update()
方法里面线性变化,然后用draw()
画出来
class Circle {
// 构造函数,初始化圆形的属性
constructor(x, y) {
this.x = x;
this.y = y;
this.radius = 0;
this.lineWidth = 7;
this.opacity = 1;
this.color = "rgb(114, 134, 212)";
this.startTime = Date.now();
}
// 更新圆形的属性
update() {
// 计算从圆形创建开始经过的时间
const elapsedTime = Date.now() - this.startTime;
if (elapsedTime < 600) {
// 半径随时间增长
this.radius = elapsedTime / 600 * 40; //这个40指的是半径
// 线宽随时间减小
this.lineWidth = 6 - (6 * elapsedTime) / 600;
// 不透明度随时间减小
this.opacity = 1 - (elapsedTime / 600);
} else {
// 超过 600 毫秒后,将圆形的属性设为 0
this.radius = 0;
this.lineWidth = 0;
this.opacity = 0;
}
}
// 绘制圆形
draw() {
// 开始绘制路径
ctx.beginPath();
// 绘制圆形路径
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
// 设置线宽
ctx.lineWidth = this.lineWidth;
// 设置描边颜色及不透明度
ctx.strokeStyle = `rgba(114, 134, 212, ${this.opacity})`;
// 绘制圆形的轮廓
ctx.stroke();
}
}
JavaScript核心的函数loop()
,这里也出过问题:效果会莫名其妙失效,尤其是当页面中有其他css或者是媒体加载的时候。加try catch也是为了调这个,前面的代码我也不觉得有什么问题。然后发现,还是得要一个函数定期地去清理Circle数组才行。
function loop() {
try {
// 清除整个 canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新并绘制每个圆形
for (let i = 0; i < circles.length; i++) {
circles[i].update();
circles[i].draw();
}
// 当文档可见时,继续请求动画帧进行动画循环
if (document.visibilityState === "visible") {
requestAnimationFrame(loop);
}
} catch (error) {
// 捕获并输出错误信息
console.error("Error in loop:", error);
}
// 移除已消失的圆形
removeCircles();
}
JavaScriptremoveCircles()
函数,神奇地解决了我的问题(
function removeCircles() {
for (let i = circles.length - 1; i >= 0; i--) {
let circle = circles[i];
if (circle.opacity <= 0) {
circles.splice(i, 1);
}
}
// 定时调用该函数,确保不断移除已消失的圆形
setTimeout(removeCircles, 100);
}
JavaScript最后贴一个完整代码~!
<script>
function clickEffect() {
// 存储圆形元素的数组
let circles = [];
// 画布的宽度和高度
let width, height;
// 2D 绘图上下文
let ctx;
// 创建一个 canvas 元素
const canvas = document.createElement("canvas");
// 将 canvas 元素添加到文档的 body 中
document.body.appendChild(canvas);
// 设置 canvas 的样式,使其覆盖整个屏幕,且位于最上层,不接收指针事件
canvas.setAttribute("style", "width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;");
// 检查是否支持 canvas 绘图上下文和添加事件监听器
if (canvas.getContext && window.addEventListener) {
// 获取 2D 绘图上下文
ctx = canvas.getContext("2d");
// 初始化 canvas 的大小
updateSize();
// 监听窗口大小改变事件,调整 canvas 大小
window.addEventListener('resize', updateSize, false);
// 开始动画循环
loop();
// 监听点击事件,点击时创建新的圆形
window.addEventListener("click", function(e) {
circles.push(new Circle(e.clientX, e.clientY));
}, false);
// 监听文档可见性变化事件,可见时继续动画循环
document.addEventListener("visibilitychange", function() {
if (document.visibilityState === "visible") {
loop();
}
}, false);
} else {
// 不支持 canvas 或事件监听时输出错误信息
console.log("canvas or addEventListener is unsupported!");
}
// 更新 canvas 大小的函数
function updateSize() {
// 设置 canvas 的宽高,扩大两倍以保证高分辨率显示
canvas.width = window.innerWidth * 2;
canvas.height = window.innerHeight * 2;
// 设置 canvas 的显示宽度和高度
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
// 缩放绘图上下文,使绘制的内容在视觉上保持正常大小
ctx.scale(2, 2);
// 存储窗口的实际宽度和高度
width = window.innerWidth;
height = window.innerHeight;
}
// 圆形类
class Circle {
// 构造函数,初始化圆形的属性
constructor(x, y) {
this.x = x;
this.y = y;
this.radius = 0;
this.lineWidth = 7;
this.opacity = 1;
this.color = "rgb(114, 134, 212)";
this.startTime = Date.now();
}
// 更新圆形的属性
update() {
// 计算从圆形创建开始经过的时间
const elapsedTime = Date.now() - this.startTime;
if (elapsedTime < 600) {
// 半径随时间增长
this.radius = elapsedTime / 600 * 40;
// 线宽随时间减小
this.lineWidth = 6 - (6 * elapsedTime) / 600;
// 不透明度随时间减小
this.opacity = 1 - (elapsedTime / 600);
} else {
// 超过 600 毫秒后,将圆形的属性设为 0
this.radius = 0;
this.lineWidth = 0;
this.opacity = 0;
}
}
// 绘制圆形
draw() {
// 开始绘制路径
ctx.beginPath();
// 绘制圆形路径
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
// 设置线宽
ctx.lineWidth = this.lineWidth;
// 设置描边颜色及不透明度
ctx.strokeStyle = `rgba(114, 134, 212, ${this.opacity})`;
// 绘制圆形的轮廓
ctx.stroke();
}
}
// 动画循环函数
function loop() {
try {
// 清除整个 canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新并绘制每个圆形
for (let i = 0; i < circles.length; i++) {
circles[i].update();
circles[i].draw();
}
// 当文档可见时,继续请求动画帧进行动画循环
if (document.visibilityState === "visible") {
requestAnimationFrame(loop);
}
} catch (error) {
// 捕获并输出错误信息
console.error("Error in loop:", error);
}
// 移除已消失的圆形
removeCircles();
}
// 移除不透明度为 0 的圆形
function removeCircles() {
for (let i = circles.length - 1; i >= 0; i--) {
let circle = circles[i];
if (circle.opacity <= 0) {
circles.splice(i, 1);
}
}
// 定时调用该函数,确保不断移除已消失的圆形
setTimeout(removeCircles, 100);
}
}
// 调用点击效果函数
clickEffect();
</script>
JavaScript