在 Web 开发中,Canvas 元素为我们提供了强大的图形渲染能力。本文将通过一个「同心圆星轨动画」的实战案例,带大家深入了解 Canvas 在动画效果开发中的应用,同时掌握如何通过数学逻辑与图形渲染实现浪漫的星空视觉效果。
效果预览
代码实现
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>同心圆星轨</title>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="starCanvas"></canvas>
<canvas id="trailCanvas"></canvas>
<script>
const starCanvas = document.getElementById('starCanvas');
const starCtx = starCanvas.getContext('2d');
const trailCanvas = document.getElementById('trailCanvas');
const trailCtx = trailCanvas.getContext('2d');
let stars = [];
let center = {
x: 0,
y: 0
};
let rotationSpeed = 1;
const STAR_COUNT = 200;
// 设置trailCanvas的样式
trailCanvas.style.position = 'absolute';
trailCanvas.style.top = '0';
trailCanvas.style.left = '0';
// 初始化画布
function resizeCanvas() {
starCanvas.width = window.innerWidth;
starCanvas.height = window.innerHeight;
trailCanvas.width = window.innerWidth;
trailCanvas.height = window.innerHeight;
center.x = starCanvas.width * 0.5;
center.y = starCanvas.height * 0.5;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// 北极星位置
function getPolarisPosition() {
return {
x: center.x,
y: center.y
};
}
// 星星颜色预设
const starColors = [
'#ffffff', // 白色
'#a8c9ff', // 蓝色
'#fffacd', // 黄色
'#ffb6c1', // 粉色
'#d3d3d3' // 灰色
];
// 创建星星
function createStars() {
stars = [];
const polaris = getPolarisPosition();
for (let i = 0; i < STAR_COUNT; i++) {
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * Math.min(starCanvas.width, starCanvas.height) * 0.45 + 20;
stars.push({
x: polaris.x + Math.cos(angle) * distance,
y: polaris.y + Math.sin(angle) * distance,
size: Math.random() * 1.5 + 0.5,
color: starColors[Math.floor(Math.random() * starColors.length)],
angle: angle,
distance: distance,
trailColor: `rgba(255, 255, 255, ${Math.random() * 0.1 + 0.05})`,
blinkPhase: Math.random() * Math.PI * 2,
blinkSpeed: Math.random() * 0.02 + 0.01,
isPolaris: i === 0
});
}
}
// 绘制星空背景
function drawBackground() {
const gradient = starCtx.createRadialGradient(
center.x, center.y, 0,
center.x, center.y, Math.max(starCanvas.width, starCanvas.height)
);
gradient.addColorStop(0, '#050a1a');
gradient.addColorStop(0.5, '#101833');
gradient.addColorStop(1, '#1a244d');
starCtx.fillStyle = gradient;
starCtx.fillRect(0, 0, starCanvas.width, starCanvas.height);
}
// 更新星星位置
function updateStars() {
const polaris = getPolarisPosition();
stars.forEach(star => {
if (!star.isPolaris) {
star.blinkPhase += star.blinkSpeed;
const blinkFactor = Math.sin(star.blinkPhase) * 0.3 + 0.7;
star.angle += 0.001 * rotationSpeed;
star.x = polaris.x + Math.cos(star.angle) * star.distance;
star.y = polaris.y + Math.sin(star.angle) * star.distance;
star.currentBrightness = blinkFactor;
}
});
}
// 绘制星轨
function drawTrails() {
const polaris = getPolarisPosition();
trailCtx.globalCompositeOperation = 'lighter';
stars.forEach(star => {
if (!star.isPolaris) {
const startAngle = star.angle - 0.003 * rotationSpeed;
const endAngle = star.angle;
trailCtx.beginPath();
trailCtx.arc(
polaris.x, polaris.y, star.distance, startAngle, endAngle, false
);
trailCtx.strokeStyle = star.trailColor.replace('0.05', '0.2');
trailCtx.lineWidth = 1.5;
trailCtx.stroke();
trailCtx.closePath();
}
});
trailCtx.globalCompositeOperation = 'source-over';
}
// 绘制星星
function drawStars() {
stars.forEach(star => {
// 绘制星星本体
starCtx.beginPath();
starCtx.arc(
star.x,
star.y,
star.isPolaris ? star.size : star.size * (star.currentBrightness || 1),
0,
Math.PI * 2
);
starCtx.fillStyle = star.color;
starCtx.fill();
starCtx.closePath();
// 添加星星光晕
starCtx.beginPath();
starCtx.arc(
star.x,
star.y,
star.isPolaris ? 15 : star.size * 3 * (star.currentBrightness || 1),
0,
Math.PI * 2
);
starCtx.fillStyle = `${star.color}20`;
starCtx.fill();
starCtx.closePath();
});
}
// 动画循环
function animate() {
updateStars();
// 只在starCanvas上绘制背景和星星
starCtx.clearRect(0, 0, starCanvas.width, starCanvas.height);
drawBackground();
drawStars();
// 在trailCanvas上绘制星轨
drawTrails();
requestAnimationFrame(animate);
}
// 初始化
function init() {
drawBackground();
createStars();
animate();
}
// 启动动画
init();
</script>
</body>
</html>
实现思路
- 使用两个 Canvas 元素,分别负责星星本体和星轨的渲染,避免混合绘制带来的性能问题。
- 北极星逻辑:标记第一个星星为北极星
isPolaris: true
,使其不参与旋转和闪烁,固定在一个位置。 - 通过
Math.cos(angle)
和Math.sin(angle)
计算星星在圆形轨道上的位置。