这是代码
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// 动画变量
let pedalAngle = 0;
let wheelAngle = 0;
let bikeX = 50;
let direction = 1;
// 绘制鹈鹕骑自行车
function drawPelicanBike(x, pedalAng, wheelAng) {
// 地面
ctx.fillStyle = '#7cb342';
ctx.fillRect(0, 340, canvas.width, 60);
ctx.fillStyle = '#558b2f';
ctx.fillRect(0, 345, canvas.width, 2);
// 自行车后轮
const wheelR = 40;
const rearX = x + 100;
const frontX = x + 220;
const wheelY = 300;
// 绘制车轮
function drawWheel(cx, cy, r, angle) {
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.strokeStyle = '#333';
ctx.lineWidth = 4;
ctx.stroke();
// 轮毂
ctx.beginPath();
ctx.arc(cx, cy, 3, 0, Math.PI * 2);
ctx.fillStyle = '#333';
ctx.fill();
// 辐条
for (let i = 0; i < 8; i++) {
const a = angle + (i * Math.PI / 4);
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(cx + Math.cos(a) * r, cy + Math.sin(a) * r);
ctx.strokeStyle = '#333';
ctx.lineWidth = 1.5;
ctx.stroke();
}
// 轮胎纹理
ctx.beginPath();
ctx.arc(cx, cy, r + 2, 0, Math.PI * 2);
ctx.strokeStyle = '#555';
ctx.lineWidth = 1;
ctx.setLineDash([3, 5]);
ctx.stroke();
ctx.setLineDash([]);
}
drawWheel(rearX, wheelY, wheelR, wheelAng);
drawWheel(frontX, wheelY, wheelR, wheelAng + 0.5);
// 自行车架
ctx.strokeStyle = '#e53935';
ctx.lineWidth = 5;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
// 座管
ctx.beginPath();
ctx.moveTo(rearX, wheelY);
ctx.lineTo(rearX + 20, wheelY - 70);
ctx.stroke();
// 上管
ctx.beginPath();
ctx.moveTo(rearX + 20, wheelY - 70);
ctx.lineTo(frontX - 10, wheelY - 70);
ctx.stroke();
// 下管
ctx.beginPath();
ctx.moveTo(rearX, wheelY);
ctx.lineTo(rearX + 60, wheelY - 50);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(rearX + 60, wheelY - 50);
ctx.lineTo(frontX - 10, wheelY - 70);
ctx.stroke();
// 后下叉
ctx.beginPath();
ctx.moveTo(rearX, wheelY);
ctx.lineTo(rearX + 60, wheelY - 50);
ctx.stroke();
// 前叉
ctx.beginPath();
ctx.moveTo(frontX - 10, wheelY - 70);
ctx.lineTo(frontX, wheelY);
ctx.stroke();
// 车座
ctx.fillStyle = '#333';
ctx.beginPath();
ctx.ellipse(rearX + 20, wheelY - 73, 18, 5, 0, 0, Math.PI * 2);
ctx.fill();
// 车把
ctx.strokeStyle = '#333';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(frontX - 10, wheelY - 70);
ctx.lineTo(frontX + 20, wheelY - 90);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(frontX + 15, wheelY - 85);
ctx.lineTo(frontX + 35, wheelY - 85);
ctx.stroke();
ctx.fillStyle = '#333';
ctx.beginPath();
ctx.arc(frontX + 35, wheelY - 85, 3, 0, Math.PI * 2);
ctx.fill();
// 踏板(旋转)
const pedalCx = rearX + 60;
const pedalCy = wheelY - 50;
const pedalLen = 18;
const pAngle = pedalAng;
// 踏板曲柄
ctx.strokeStyle = '#555';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(pedalCx, pedalCy);
ctx.lineTo(pedalCx + Math.cos(pAngle) * pedalLen, pedalCy + Math.sin(pAngle) * pedalLen);
ctx.stroke();
// 第二个曲柄
ctx.beginPath();
ctx.moveTo(pedalCx, pedalCy);
ctx.lineTo(pedalCx + Math.cos(pAngle + Math.PI) * pedalLen, pedalCy + Math.sin(pAngle + Math.PI) * pedalLen);
ctx.stroke();
// 踏板
function drawPedal(px, py) {
ctx.fillStyle = '#555';
ctx.beginPath();
ctx.roundRect(px - 10, py - 3, 20, 6, 2);
ctx.fill();
}
drawPedal(pedalCx + Math.cos(pAngle) * pedalLen, pedalCy + Math.sin(pAngle) * pedalLen);
drawPedal(pedalCx + Math.cos(pAngle + Math.PI) * pedalLen, pedalCy + Math.sin(pAngle + Math.PI) * pedalLen);
// ---- 鹈鹕 ----
const pelX = rearX + 40;
const pelY = wheelY - 80;
const bodyX = pelX;
const bodyY = pelY;
// 身体
ctx.fillStyle = 'white';
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.ellipse(bodyX, bodyY, 36, 26, 0, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
// 左翅膀(骑行姿势)
const wingFlap = Math.sin(pedalAng * 0.8) * 0.2;
ctx.fillStyle = '#f5f5f5';
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(bodyX - 30, bodyY - 5);
ctx.quadraticCurveTo(bodyX - 50, bodyY - 30 + wingFlap * 20, bodyX - 25, bodyY + 15);
ctx.fill();
ctx.stroke();
// 右翅膀
ctx.beginPath();
ctx.moveTo(bodyX + 30, bodyY - 5);
ctx.quadraticCurveTo(bodyX + 55, bodyY - 35 + wingFlap * 15, bodyX + 25, bodyY + 15);
ctx.fill();
ctx.stroke();
// 尾巴
ctx.fillStyle = '#f5f5f5';
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(bodyX - 30, bodyY + 10);
ctx.lineTo(bodyX - 55, bodyY + 25);
ctx.lineTo(bodyX - 45, bodyY + 10);
ctx.lineTo(bodyX - 60, bodyY + 15);
ctx.lineTo(bodyX - 35, bodyY + 0);
ctx.closePath();
ctx.fill();
ctx.stroke();
// 腿(踩踏板)
const legAngle = pedalAng * 0.8;
ctx.strokeStyle = '#f5a623';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
// 左腿(到踏板)
const pedPosX = pedalCx + Math.cos(pedalAng) * pedalLen;
const pedPosY = pedalCy + Math.sin(pedalAng) * pedalLen;
const hipX = bodyX - 5;
const hipY = bodyY + 22;
ctx.beginPath();
ctx.moveTo(hipX, hipY);
const kneeX = hipX + 10 + Math.cos(pedalAng + 0.5) * 15;
const kneeY = hipY + 10 + Math.sin(pedalAng + 0.5) * 15;
ctx.quadraticCurveTo(kneeX, kneeY, pedPosX, pedPosY);
ctx.stroke();
// 脚
ctx.fillStyle = '#f5a623';
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pedPosX - 8, pedPosY);
ctx.lineTo(pedPosX + 8, pedPosY + 2);
ctx.lineTo(pedPosX + 5, pedPosY + 6);
ctx.lineTo(pedPosX + 10, pedPosY + 5);
ctx.lineTo(pedPosX + 6, pedPosY + 9);
ctx.lineTo(pedPosX + 2, pedPosY + 7);
ctx.closePath();
ctx.fill();
ctx.stroke();
// 右腿(到另一个踏板)
const pedPosX2 = pedalCx + Math.cos(pedalAng + Math.PI) * pedalLen;
const pedPosY2 = pedalCy + Math.sin(pedalAng + Math.PI) * pedalLen;
ctx.strokeStyle = '#f5a623';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(hipX + 12, hipY);
const kneeX2 = hipX + 22 + Math.cos(pedalAng + Math.PI - 0.5) * 15;
const kneeY2 = hipY + 10 + Math.sin(pedalAng + Math.PI - 0.5) * 15;
ctx.quadraticCurveTo(kneeX2, kneeY2, pedPosX2, pedPosY2);
ctx.stroke();
// 脖子
ctx.fillStyle = 'white';
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(bodyX + 25, bodyY - 10);
ctx.quadraticCurveTo(bodyX + 40, bodyY - 35, bodyX + 35, bodyY - 70);
ctx.quadraticCurveTo(bodyX + 32, bodyY - 85, bodyX + 25, bodyY - 95);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(bodyX + 28, bodyY - 10);
ctx.quadraticCurveTo(bodyX + 45, bodyY - 35, bodyX + 38, bodyY - 70);
ctx.quadraticCurveTo(bodyX + 35, bodyY - 85, bodyX + 28, bodyY - 95);
ctx.stroke();
// 脖子填充
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 12;
ctx.beginPath();
ctx.moveTo(bodyX + 25, bodyY - 10);
ctx.quadraticCurveTo(bodyX + 42, bodyY - 35, bodyX + 36, bodyY - 70);
ctx.quadraticCurveTo(bodyX + 33, bodyY - 85, bodyX + 26, bodyY - 95);
ctx.stroke();
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(bodyX + 25, bodyY - 10);
ctx.quadraticCurveTo(bodyX + 42, bodyY - 35, bodyX + 36, bodyY - 70);
ctx.quadraticCurveTo(bodyX + 33, bodyY - 85, bodyX + 26, bodyY - 95);
ctx.stroke();
// 头部
const headX = bodyX + 26;
const headY = bodyY - 100;
ctx.fillStyle = 'white';
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(headX, headY, 16, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
// 头顶羽毛
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(headX - 6, headY - 13);
ctx.quadraticCurveTo(headX - 2, headY - 28, headX + 4, headY - 13);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(headX + 2, headY - 15);
ctx.quadraticCurveTo(headX + 6, headY - 32, headX + 10, headY - 15);
ctx.stroke();
// 眼睛
ctx.fillStyle = '#333';
ctx.beginPath();
ctx.arc(headX + 8, headY - 3, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(headX + 9, headY - 4, 1.5, 0, Math.PI * 2);
ctx.fill();
// 上喙
ctx.fillStyle = '#f5a623';
ctx.strokeStyle = '#333';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(headX + 14, headY + 2);
ctx.quadraticCurveTo(headX + 34, headY - 8, headX + 48, headY - 3);
ctx.quadraticCurveTo(headX + 34, headY + 2, headX + 16, headY + 6);
ctx.fill();
ctx.stroke();
// 下喙/喉囊
ctx.fillStyle = '#f5a623';
ctx.strokeStyle = '#333';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(headX + 16, headY + 6);
ctx.quadraticCurveTo(headX + 30, headY + 16, headX + 46, headY + 8);
ctx.quadraticCurveTo(headX + 30, headY + 22, headX + 18, headY + 16);
ctx.fill();
ctx.stroke();
// 喉囊颜色
ctx.fillStyle = '#ffcc80';
ctx.beginPath();
ctx.moveTo(headX + 20, headY + 10);
ctx.quadraticCurveTo(headX + 30, headY + 20, headX + 42, headY + 12);
ctx.fill();
// 嘴尖点
ctx.fillStyle = '#e65100';
ctx.beginPath();
ctx.arc(headX + 46, headY - 2, 2, 0, Math.PI * 2);
ctx.fill();
// 运动头带
ctx.strokeStyle = '#e53935';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(headX - 10, headY - 8);
ctx.quadraticCurveTo(headX + 2, headY - 16, headX + 14, headY - 10);
ctx.stroke();
// 头带飘带
ctx.strokeStyle = '#e53935';
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.moveTo(headX - 10, headY - 8);
ctx.quadraticCurveTo(headX - 20, headY - 2, headX - 26, headY + 6);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(headX - 12, headY - 5);
ctx.quadraticCurveTo(headX - 18, headY + 2, headX - 22, headY + 10);
ctx.stroke();
// 汗水
const sweatAngle = pedalAng * 0.5;
ctx.fillStyle = '#4fc3f7';
ctx.beginPath();
ctx.arc(headX - 6 + Math.sin(sweatAngle) * 2, headY - 6 + Math.cos(sweatAngle * 1.3) * 3, 2, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(headX - 8 + Math.sin(sweatAngle + 1) * 3, headY - 2 + Math.cos(sweatAngle * 0.9 + 0.5) * 2, 1.5, 0, Math.PI * 2);
ctx.fill();
}
// roundRect polyfill for Canvas
if (!CanvasRenderingContext2D.prototype.roundRect) {
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
if (r > w/2) r = w/2;
if (r > h/2) r = h/2;
this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, y + h, r);
this.arcTo(x, y + h, x, y, r);
this.arcTo(x, y, x + w, y, r);
return this;
};
}
// 动画循环
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新角度
pedalAngle += 0.08;
wheelAngle += 0.05;
// 移动自行车(来回)
bikeX += direction * 1.2;
if (bikeX > 150 || bikeX < 30) {
direction *= -1;
// 转身时反向踩踏
}
// 绘制
drawPelicanBike(bikeX, pedalAngle, wheelAngle);
// 速度线
ctx.strokeStyle = 'rgba(144, 164, 174, 0.4)';
ctx.lineWidth = 1.5;
ctx.setLineDash([4, 6]);
for (let i = 0; i < 5; i++) {
const y = 280 + i * 12;
const xOff = (i * 7 + bikeX * 0.1) % 30;
ctx.beginPath();
ctx.moveTo(20 + xOff, y);
ctx.lineTo(70 + xOff, y);
ctx.stroke();
}
ctx.setLineDash([]);
// 太阳
const sunX = 340;
const sunY = 55;
const grad = ctx.createRadialGradient(sunX, sunY, 5, sunX, sunY, 30);
grad.addColorStop(0, '#ffd54f');
grad.addColorStop(1, 'rgba(255, 213, 79, 0.3)');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(sunX, sunY, 30, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffd54f';
ctx.beginPath();
ctx.arc(sunX, sunY, 22, 0, Math.PI * 2);
ctx.fill();
// 太阳光线
ctx.strokeStyle = 'rgba(255, 213, 79, 0.6)';
ctx.lineWidth = 3;
for (let i = 0; i < 8; i++) {
const a = i * Math.PI / 4 + pedalAngle * 0.02;
ctx.beginPath();
ctx.moveTo(sunX + Math.cos(a) * 28, sunY + Math.sin(a) * 28);
ctx.lineTo(sunX + Math.cos(a) * 38, sunY + Math.sin(a) * 38);
ctx.stroke();
}
// 云朵
ctx.fillStyle = 'rgba(255,255,255,0.8)';
const cloudX = (bikeX * 0.2 + 40) % 380;
ctx.beginPath();
ctx.arc(cloudX, 60, 18, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cloudX + 25, 50, 24, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cloudX + 50, 60, 18, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cloudX + 22, 72, 14, 0, Math.PI * 2);
ctx.fill();
requestAnimationFrame(animate);
}
// 启动动画
animate();