<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>拳头游戏粒子特效</title>
<style>
body {
background-color: #290000;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
window.onload = function () {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let particlesArray = [];
const numberOfParticles = 1000;
const particleSize = 1;
const particleSpacing = 1.4;
const mouseRadius = 70;
const mouse = {
x: null,
y: null,
radius: mouseRadius,
};
window.addEventListener('mousemove', function (event) {
mouse.x = event.x;
mouse.y = event.y;
});
class Particle {
constructor(x, y) {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = particleSize;
this.color = '#EB0029';
this.baseX = x;
this.baseY = y;
this.density = Math.random() * 30 + 1;
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
update() {
let dx = mouse.x - this.x;
let dy = mouse.y - this.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let forceDirectionX = dx / distance;
let forceDirectionY = dy / distance;
let maxDistance = mouse.radius;
let force = (maxDistance - distance) / maxDistance;
let directionX = forceDirectionX * force * this.density;
let directionY = forceDirectionY * force * this.density;
if (distance < mouse.radius) {
this.x -= directionX;
this.y -= directionY;
} else {
if (this.x !== this.baseX) {
let dx = this.x - this.baseX;
this.x -= dx / 10;
}
if (this.y !== this.baseY) {
let dy = this.y - this.baseY;
this.y -= dy / 10;
}
}
}
}
async function loadSVG(url) {
const response = await fetch(url);
const text = await response.text();
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(text, 'image/svg+xml');
const paths = xmlDoc.getElementsByTagName('path');
return Array.from(paths).map((path) => path.getAttribute('d'));
}
function getParticlePositionsFromPathData(pathData) {
const positions = [];
const path = new Path2D(pathData);
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
let minX = canvasWidth,
maxX = 0,
minY = canvasHeight,
maxY = 0;
for (let y = 0; y < canvasHeight; y += particleSpacing) {
for (let x = 0; x < canvasWidth; x += particleSpacing) {
if (ctx.isPointInPath(path, x, y)) {
positions.push({ x, y });
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
}
}
return { positions, minX, maxX, minY, maxY };
}
async function init() {
particlesArray = [];
const svgPaths = await loadSVG(
'https://fecoder-pic-1302080640.cos.ap-nanjing.myqcloud.com/Riot_Games_2022.svg'
);
let allPositions = [];
let minX = canvas.width,
maxX = 0,
minY = canvas.height,
maxY = 0;
svgPaths.forEach((pathData) => {
const {
positions,
minX: pathMinX,
maxX: pathMaxX,
minY: pathMinY,
maxY: pathMaxY,
} = getParticlePositionsFromPathData(pathData);
allPositions = allPositions.concat(positions);
if (pathMinX < minX) minX = pathMinX;
if (pathMaxX > maxX) maxX = pathMaxX;
if (pathMinY < minY) minY = pathMinY;
if (pathMaxY > maxY) maxY = pathMaxY;
});
const offsetX = (canvas.width - (maxX - minX)) / 2 - minX;
const offsetY = (canvas.height - (maxY - minY)) / 2 - minY;
allPositions = allPositions.map((pos) => ({
x: pos.x + offsetX,
y: pos.y + offsetY,
}));
allPositions.forEach((pos) => {
particlesArray.push(new Particle(pos.x, pos.y));
});
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < particlesArray.length; i++) {
particlesArray[i].draw();
particlesArray[i].update();
}
requestAnimationFrame(animate);
}
init();
animate();
window.addEventListener('resize', function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
init();
});
};
</script>
</body>
</html>