Vuepress | 爆炸粒子烟花
AI 正在分析并生成摘要... |
此内容根据文章生成,仅用于文章内容的解释与总结
前言
一直想给自己的博客加个点击鼠标特效的(没遇到心仪的—_—),昨天遇到一个特别好看的爆炸粒子烟花特效(虽然看久了有点花里胡哨的,但是目前站点先保留叭)。昨天捣鼓了半天,本地测试效果可以,部署到阿里云就不行了。今天查出原因:自定义 js 脚本不能放在 head 标签里,得放在 body 标签,自定义 js 脚本需要等待网页资源加载完毕才可以执行!
实现鼠标点击爆炸粒子烟花特效的方式总共有两种:纯 js 脚本、外部 anime 脚本+自定义绘制脚本

纯 JS
将下面代码添加到 custom.js 自定义脚本文件中,并将该脚本插入到 body 标签中
export function clickEffect() {
let balls = [];
let longPressed = false;
let longPress;
let multiplier = 0;
let width, height;
let origin;
let normal;
let ctx;
const colours = ["#F73859", "#14FFEC", "#00E0FF", "#FF99FE", "#FAF15D"];
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.setAttribute(
"style",
"width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;"
);
const pointer = document.createElement("span");
pointer.classList.add("pointer");
document.body.appendChild(pointer);
if (canvas.getContext && window.addEventListener) {
ctx = canvas.getContext("2d");
updateSize();
window.addEventListener("resize", updateSize, false);
loop();
window.addEventListener(
"mousedown",
function (e) {
pushBalls(randBetween(10, 20), e.clientX, e.clientY);
document.body.classList.add("is-pressed");
longPress = setTimeout(function () {
document.body.classList.add("is-longpress");
longPressed = true;
}, 500);
},
false
);
window.addEventListener(
"mouseup",
function (e) {
clearInterval(longPress);
if (longPressed == true) {
document.body.classList.remove("is-longpress");
pushBalls(
randBetween(
50 + Math.ceil(multiplier),
100 + Math.ceil(multiplier)
),
e.clientX,
e.clientY
);
longPressed = false;
}
document.body.classList.remove("is-pressed");
},
false
);
window.addEventListener(
"mousemove",
function (e) {
let x = e.clientX;
let y = e.clientY;
pointer.style.top = y + "px";
pointer.style.left = x + "px";
},
false
);
} else {
console.log("canvas or addEventListener is unsupported!");
}
function updateSize() {
canvas.width = window.innerWidth * 2;
canvas.height = window.innerHeight * 2;
canvas.style.width = window.innerWidth + "px";
canvas.style.height = window.innerHeight + "px";
ctx.scale(2, 2);
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
origin = {
x: width / 2,
y: height / 2,
};
normal = {
x: width / 2,
y: height / 2,
};
}
class Ball {
constructor(x = origin.x, y = origin.y) {
this.x = x;
this.y = y;
this.angle = Math.PI * 2 * Math.random();
if (longPressed == true) {
this.multiplier = randBetween(14 + multiplier, 15 + multiplier);
} else {
this.multiplier = randBetween(6, 12);
}
this.vx = (this.multiplier + Math.random() * 0.5) * Math.cos(this.angle);
this.vy = (this.multiplier + Math.random() * 0.5) * Math.sin(this.angle);
this.r = randBetween(8, 12) + 3 * Math.random();
this.color = colours[Math.floor(Math.random() * colours.length)];
}
update() {
this.x += this.vx - normal.x;
this.y += this.vy - normal.y;
normal.x = (-2 / window.innerWidth) * Math.sin(this.angle);
normal.y = (-2 / window.innerHeight) * Math.cos(this.angle);
this.r -= 0.3;
this.vx *= 0.9;
this.vy *= 0.9;
}
}
function pushBalls(count = 1, x = origin.x, y = origin.y) {
for (let i = 0; i < count; i++) {
balls.push(new Ball(x, y));
}
}
function randBetween(min, max) {
return Math.floor(Math.random() * max) + min;
}
function loop() {
ctx.fillStyle = "rgba(255, 255, 255, 0)";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < balls.length; i++) {
let b = balls[i];
if (b.r < 0) continue;
ctx.fillStyle = b.color;
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2, false);
ctx.fill();
b.update();
}
if (longPressed == true) {
multiplier += 0.2;
} else if (!longPressed && multiplier >= 0) {
multiplier -= 0.4;
}
removeBall();
requestAnimationFrame(loop);
}
function removeBall() {
for (let i = 0; i < balls.length; i++) {
let b = balls[i];
if (
b.x + b.r < 0 ||
b.x - b.r > width ||
b.y + b.r < 0 ||
b.y - b.r > height ||
b.r < 0
) {
balls.splice(i, 1);
}
}
}
}anime 脚本+绘制脚本
如果自定义 html 页面,则按照下面三个步骤,并将第三步的自定义绘制脚本引入到 body 标签中,如图,需要注意的是,需要保证 canvas 标签在两个脚本前面,否则效果显示不出来!

绘制代码插入元素
<canvas class="fireworks" style="position:fixed;left:0;top:0;z-index:99999999;pointer-events:none;"></canvas>引用 anime 外部脚本
<script
type="text/javascript"
src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/animejs/3.2.1/anime.min.js"
></script>自定义绘制脚本
export function clickEffect3() {
/**
* 用于 Canvas 的动画效果库
* https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/animejs/3.2.1/anime.min.js
* 教程地址: https://www.5186a.com/?p=1623
* 看教程不走弯路 转载请保留!!!
*/
function updateCoords(e) {
(pointerX =
(e.clientX || e.touches[0].clientX) -
canvasEl.getBoundingClientRect().left),
(pointerY =
(e.clientY || e.touches[0].clientY) -
canvasEl.getBoundingClientRect().top);
}
function setParticuleDirection(e) {
var t = (anime.random(0, 360) * Math.PI) / 180,
a = anime.random(50, 180),
n = [-1, 1][anime.random(0, 1)] * a;
return {
x: e.x + n * Math.cos(t),
y: e.y + n * Math.sin(t),
};
}
function createParticule(e, t) {
var a = {};
return (
(a.x = e),
(a.y = t),
(a.color = colors[anime.random(0, colors.length - 1)]),
(a.radius = anime.random(16, 32)),
(a.endPos = setParticuleDirection(a)),
(a.draw = function () {
ctx.beginPath(),
ctx.arc(a.x, a.y, a.radius, 0, 2 * Math.PI, !0),
(ctx.fillStyle = a.color),
ctx.fill();
}),
a
);
}
function createCircle(e, t) {
var a = {};
return (
(a.x = e),
(a.y = t),
(a.color = "#F00"),
(a.radius = 0.1),
(a.alpha = 0.5),
(a.lineWidth = 6),
(a.draw = function () {
(ctx.globalAlpha = a.alpha),
ctx.beginPath(),
ctx.arc(a.x, a.y, a.radius, 0, 2 * Math.PI, !0),
(ctx.lineWidth = a.lineWidth),
(ctx.strokeStyle = a.color),
ctx.stroke(),
(ctx.globalAlpha = 1);
}),
a
);
}
function renderParticule(e) {
for (var t = 0; t < e.animatables.length; t++)
e.animatables[t].target.draw();
}
function animateParticules(e, t) {
for (var a = createCircle(e, t), n = [], i = 0; i < numberOfParticules; i++)
n.push(createParticule(e, t));
anime
.timeline()
.add({
targets: n,
x: function (e) {
return e.endPos.x;
},
y: function (e) {
return e.endPos.y;
},
radius: 0.1,
duration: anime.random(1200, 1800),
easing: "easeOutExpo",
update: renderParticule,
})
.add({
targets: a,
radius: anime.random(80, 160),
lineWidth: 0,
alpha: {
value: 0,
easing: "linear",
duration: anime.random(600, 800),
},
duration: anime.random(1200, 1800),
easing: "easeOutExpo",
update: renderParticule,
offset: 0,
});
}
function debounce(fn, delay) {
var timer;
return function () {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}
var canvasEl = document.querySelector(".fireworks");
if (canvasEl) {
var ctx = canvasEl.getContext("2d"),
numberOfParticules = 30,
pointerX = 0,
pointerY = 0,
tap = "mousedown",
colors = ["#FF1461", "#18FF92", "#5A87FF", "#FBF38C"],
setCanvasSize = debounce(function () {
(canvasEl.width = 2 * window.innerWidth),
(canvasEl.height = 2 * window.innerHeight),
(canvasEl.style.width = window.innerWidth + "px"),
(canvasEl.style.height = window.innerHeight + "px"),
canvasEl.getContext("2d").scale(2, 2);
}, 500),
render = anime({
duration: 1 / 0,
update: function () {
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
},
});
document.addEventListener(
tap,
function (e) {
"sidebar" !== e.target.id &&
"toggle-sidebar" !== e.target.id &&
"A" !== e.target.nodeName &&
"IMG" !== e.target.nodeName &&
(render.play(),
updateCoords(e),
animateParticules(pointerX, pointerY));
},
!1
),
setCanvasSize(),
window.addEventListener("resize", setCanvasSize, !1);
}
}vuepress 内嵌自定义 js 脚本
vuepress 内嵌自定义 js 脚本的两种方式:head、body
head 标签
docs > .vuepress > config.js 文件
支持第三方脚本,不需要等待页面内容渲染完毕再执行
不支持自定义脚本!
export default defineUserConfig({
lang: "zh-CN",
title: "沐夏",
head: [
[
"script",
{
type: "text/javascript",
src: "https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js",
},
],
],
});body 标签
参考:本站使用的 vuepress 主题为 plume ,在 docs > .vuepress > client.js 文件实现引入自定义脚本
步骤:
1、首先使用 import 导入自定义脚本的方法和 onMounted 方法
2、再在 setup() 方法加载 anime.js、创建 canvas
3、最后执行 clickEffect3 方法
import { defineClientConfig } from "vuepress/client";
import { onMounted } from "vue"; // 引入 onMounted 钩子
import { clickEffect3 } from "./custom.js";
export default defineClientConfig({
enhance({ app, router, siteData }) {
// 其他逻辑
},
setup() {
onMounted(() => {
// 动态加载 anime.js
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/animejs@2.2.0/anime.min.js";
script.onload = () => {
// anime.js 加载完成后,插入 canvas 元素
const canvas = document.createElement("canvas");
canvas.className = "fireworks";
canvas.style.position = "fixed";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = "99999999";
canvas.style.pointerEvents = "none";
canvas.style.width = "848px";
canvas.style.height = "799px";
canvas.width = 1696;
canvas.height = 1598;
document.body.appendChild(canvas);
// 调用 clickEffect3 函数
clickEffect3();
};
document.head.appendChild(script);
});
},
});参考链接
版权所有
版权归属:haipeng-lin