<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML5不规则圆点连线动画特效</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #121212;
overflow: hidden;
font-family: 'Arial', sans-serif;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.controls {
position: absolute;
bottom: 20px;
left: 20px;
z-index: 100;
color: white;
}
.credit {
position: absolute;
bottom: 20px;
right: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
z-index: 100;
}
.credit a {
color: #4CAF50;
text-decoration: none;
transition: color 0.3s;
}
.credit a:hover {
color: #FFD700;
}
button {
background: rgba(76, 175, 80, 0.7);
color: white;
border: none;
padding: 8px 15px;
margin: 5px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
button:hover {
background: rgba(76, 175, 80, 0.9);
}
label {
display: block;
margin-top: 10px;
color: white;
}
input[type="range"] {
width: 200px;
margin-top: 5px;
}
</style>
</head>
<body>
<canvas id="dotCanvas"></canvas>
<div class="controls">
<button id="resetBtn">重置动画</button>
<button id="changeColorBtn">改变颜色</button>
<label for="dotCount">圆点数量: <span id="dotCountValue">50</span></label>
<input type="range" id="dotCount" min="10" max="150" value="50">
<label for="lineDistance">连线距离: <span id="lineDistanceValue">150</span></label>
<input type="range" id="lineDistance" min="50" max="300" value="150">
<label for="speedControl">动画速度: <span id="speedValue">50</span></label>
<input type="range" id="speedControl" min="10" max="100" value="50">
</div>
<div class="credit">
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.getElementById('dotCanvas');
const ctx = canvas.getContext('2d');
// 设置画布大小为窗口大小
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 圆点数组
let dots = [];
let dotCount = 50;
let lineDistance = 150;
let speed = 50;
let colorHue = 200;
let animationId;
// 圆点类
class Dot {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = Math.random() * 5 + 2;
this.color = `hsl(${colorHue + Math.random() * 60}, 100%, 50%)`;
this.speedX = Math.random() * 2 - 1;
this.speedY = Math.random() * 2 - 1;
this.connections = [];
}
update() {
// 移动
this.x += this.speedX * (speed / 50);
this.y += this.speedY * (speed / 50);
// 边界检查
if (this.x < 0 || this.x > canvas.width) {
this.speedX *= -1;
}
if (this.y < 0 || this.y > canvas.height) {
this.speedY *= -1;
}
// 随机改变方向
if (Math.random() < 0.02) {
this.speedX += Math.random() * 0.2 - 0.1;
this.speedY += Math.random() * 0.2 - 0.1;
}
// 限制速度
const maxSpeed = 1 + speed / 100;
const speedVal = Math.sqrt(this.speedX * this.speedX + this.speedY * this.speedY);
if (speedVal > maxSpeed) {
this.speedX = (this.speedX / speedVal) * maxSpeed;
this.speedY = (this.speedY / speedVal) * maxSpeed;
}
}
draw() {
// 绘制圆点
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
// 检查并绘制连线
drawConnections(otherDots) {
this.connections = [];
otherDots.forEach(dot => {
if (dot === this) return;
const dx = this.x - dot.x;
const dy = this.y - dot.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < lineDistance) {
this.connections.push(dot);
// 计算连线透明度 (距离越远越透明)
const alpha = 1 - distance / lineDistance;
// 绘制连线
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(dot.x, dot.y);
ctx.strokeStyle = this.color.replace(')', `, ${alpha * 0.5})`).replace('hsl', 'hsla');
ctx.lineWidth = 1;
ctx.stroke();
}
});
}
}
// 初始化圆点
function initDots() {
dots = [];
for (let i = 0; i < dotCount; i++) {
dots.push(new Dot());
}
}
// 动画循环
function animate() {
// 清除画布
ctx.fillStyle = 'rgba(18, 18, 18, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 更新和绘制圆点
dots.forEach(dot => {
dot.update();
dot.draw();
});
// 绘制连线
dots.forEach(dot => {
dot.drawConnections(dots);
});
animationId = requestAnimationFrame(animate);
}
// 初始化并开始动画
initDots();
animate();
// 窗口大小调整时重置画布
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
initDots();
});
// 控制按钮事件
document.getElementById('resetBtn').addEventListener('click', function() {
cancelAnimationFrame(animationId);
initDots();
animate();
});
document.getElementById('changeColorBtn').addEventListener('click', function() {
colorHue = Math.random() * 360;
dots.forEach(dot => {
dot.color = `hsl(${colorHue + Math.random() * 60}, 100%, 50%)`;
});
});
document.getElementById('dotCount').addEventListener('input', function() {
dotCount = parseInt(this.value);
document.getElementById('dotCountValue').textContent = dotCount;
initDots();
});
document.getElementById('lineDistance').addEventListener('input', function() {
lineDistance = parseInt(this.value);
document.getElementById('lineDistanceValue').textContent = lineDistance;
});
document.getElementById('speedControl').addEventListener('input', function() {
speed = parseInt(this.value);
document.getElementById('speedValue').textContent = speed;
});
});
</script>
</body>
</html>