<!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;
padding: 0;
overflow: hidden;
background: linear-gradient(to bottom, #87CEEB, #E0F7FA);
font-family: Arial, sans-serif;
}
#canvas {
display: block;
background: linear-gradient(to bottom, #87CEEB, #E0F7FA);
}
.info {
position: absolute;
top: 20px;
left: 20px;
background: rgba(255, 255, 255, 0.7);
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.speed-meter {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.7);
padding: 10px 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
font-weight: bold;
}
.article-link {
position: absolute;
bottom: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.7);
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
font-size: 14px;
}
.article-link a {
color: #0066cc;
text-decoration: none;
}
.article-link a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="info">移动鼠标控制自行车速度 - 鼠标越靠右速度越快</div>
<div class="speed-meter">速度: <span id="speed">0</span> km/h</div>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const speedDisplay = document.getElementById('speed');
// 设置画布大小为窗口大小
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 自行车参数
const bike = {
x: 100,
y: canvas.height / 2,
speed: 0,
maxSpeed: 30,
acceleration: 0.1,
deceleration: 0.05,
wheelRadius: 40,
frameLength: 100,
angle: 0,
pedalAngle: 0
};
// 背景参数
const background = {
x1: 0,
x2: canvas.width,
speed: 0
};
// 云朵数组
const clouds = [];
for (let i = 0; i < 5; i++) {
clouds.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height * 0.3,
speed: 0.5 + Math.random() * 1.5,
size: 30 + Math.random() * 50
});
}
// 鼠标位置
let mouseX = canvas.width / 2;
// 监听鼠标移动
canvas.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
});
// 动画循环
function animate() {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 根据鼠标位置计算目标速度
const targetSpeed = (mouseX / canvas.width) * bike.maxSpeed;
// 平滑调整速度
if (bike.speed < targetSpeed) {
bike.speed += bike.acceleration;
if (bike.speed > targetSpeed) bike.speed = targetSpeed;
} else if (bike.speed > targetSpeed) {
bike.speed -= bike.deceleration;
if (bike.speed < targetSpeed) bike.speed = targetSpeed;
}
// 更新速度显示
speedDisplay.textContent = Math.round(bike.speed * 10);
// 更新自行车位置和动画
bike.x += bike.speed;
bike.angle += bike.speed / bike.wheelRadius;
bike.pedalAngle += bike.speed / (bike.wheelRadius / 2);
// 如果自行车超出右边界,回到左侧
if (bike.x > canvas.width + 200) {
bike.x = -200;
}
// 绘制背景
drawBackground();
// 绘制云朵
drawClouds();
// 绘制地面
drawGround();
// 绘制自行车
drawBike();
requestAnimationFrame(animate);
}
function drawBackground() {
// 天空渐变
const skyGradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
skyGradient.addColorStop(0, '#87CEEB');
skyGradient.addColorStop(1, '#E0F7FA');
ctx.fillStyle = skyGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 远山
ctx.fillStyle = '#4CAF50';
ctx.beginPath();
ctx.moveTo(0, canvas.height * 0.7);
for (let x = 0; x < canvas.width; x += 100) {
const height = Math.sin(x / 200) * 50 + 100;
ctx.lineTo(x, canvas.height * 0.7 - height);
}
ctx.lineTo(canvas.width, canvas.height * 0.7);
ctx.lineTo(canvas.width, canvas.height);
ctx.lineTo(0, canvas.height);
ctx.closePath();
ctx.fill();
}
function drawClouds() {
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
for (const cloud of clouds) {
// 更新云朵位置
cloud.x -= cloud.speed * (bike.speed / bike.maxSpeed);
if (cloud.x < -cloud.size * 3) {
cloud.x = canvas.width + cloud.size * 3;
cloud.y = Math.random() * canvas.height * 0.3;
}
// 绘制云朵
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.arc(
cloud.x + i * cloud.size * 0.6,
cloud.y,
cloud.size * 0.5,
0,
Math.PI * 2
);
ctx.fill();
}
}
}
function drawGround() {
// 地面
ctx.fillStyle = '#8BC34A';
ctx.fillRect(0, canvas.height * 0.7, canvas.width, canvas.height * 0.3);
// 道路
ctx.fillStyle = '#795548';
ctx.fillRect(0, canvas.height * 0.75, canvas.width, canvas.height * 0.1);
// 道路中线
ctx.strokeStyle = 'yellow';
ctx.lineWidth = 4;
ctx.setLineDash([20, 30]);
ctx.beginPath();
ctx.moveTo(0, canvas.height * 0.8);
ctx.lineTo(canvas.width, canvas.height * 0.8);
ctx.stroke();
ctx.setLineDash([]);
}
function drawBike() {
// 自行车位置
const bikeY = canvas.height * 0.65;
// 后轮
ctx.save();
ctx.translate(bike.x, bikeY);
ctx.rotate(bike.angle);
ctx.strokeStyle = '#333';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.arc(0, 0, bike.wheelRadius, 0, Math.PI * 2);
ctx.stroke();
// 轮辐
for (let i = 0; i < 8; i++) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(
Math.cos(i * Math.PI / 4) * bike.wheelRadius,
Math.sin(i * Math.PI / 4) * bike.wheelRadius
);
ctx.stroke();
}
ctx.restore();
// 前轮
ctx.save();
ctx.translate(bike.x + bike.frameLength, bikeY);
ctx.rotate(bike.angle);
ctx.strokeStyle = '#333';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.arc(0, 0, bike.wheelRadius, 0, Math.PI * 2);
ctx.stroke();
// 轮辐
for (let i = 0; i < 8; i++) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(
Math.cos(i * Math.PI / 4) * bike.wheelRadius,
Math.sin(i * Math.PI / 4) * bike.wheelRadius
);
ctx.stroke();
}
ctx.restore();
// 车架
ctx.strokeStyle = '#FF5722';
ctx.lineWidth = 8;
ctx.beginPath();
// 后轮中心到踏板
ctx.moveTo(bike.x, bikeY);
ctx.lineTo(bike.x + bike.frameLength * 0.3, bikeY - bike.frameLength * 0.4);
// 踏板到前轮中心
ctx.lineTo(bike.x + bike.frameLength, bikeY);
// 座管
ctx.moveTo(bike.x + bike.frameLength * 0.2, bikeY);
ctx.lineTo(bike.x + bike.frameLength * 0.2, bikeY - bike.frameLength * 0.6);
// 车把
ctx.moveTo(bike.x + bike.frameLength * 0.7, bikeY - bike.frameLength * 0.4);
ctx.lineTo(bike.x + bike.frameLength * 0.7, bikeY - bike.frameLength * 0.6);
ctx.stroke();
// 踏板
ctx.save();
ctx.translate(bike.x + bike.frameLength * 0.3, bikeY - bike.frameLength * 0.4);
ctx.rotate(bike.pedalAngle);
// 曲柄
ctx.strokeStyle = '#333';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, 30);
ctx.stroke();
// 踏板
ctx.fillStyle = '#777';
ctx.beginPath();
ctx.arc(0, 30, 10, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// 骑手
ctx.save();
ctx.translate(bike.x + bike.frameLength * 0.2, bikeY - bike.frameLength * 0.6);
// 头部
ctx.fillStyle = '#FFD54F';
ctx.beginPath();
ctx.arc(0, -20, 15, 0, Math.PI * 2);
ctx.fill();
// 身体
ctx.strokeStyle = '#2196F3';
ctx.lineWidth = 6;
ctx.beginPath();
ctx.moveTo(0, -5);
ctx.lineTo(0, 30);
// 手臂
ctx.moveTo(0, 10);
ctx.lineTo(-30, 0);
ctx.moveTo(0, 10);
ctx.lineTo(30, 0);
// 腿
ctx.moveTo(0, 30);
ctx.lineTo(-20, 60);
ctx.moveTo(0, 30);
ctx.lineTo(20, 60);
ctx.stroke();
ctx.restore();
}
// 窗口大小调整时重设画布大小
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// 开始动画
animate();
</script>
</body>
</html>