512 lines
16 KiB
HTML
512 lines
16 KiB
HTML
<!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>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Arial', sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 20px;
|
|
color: white;
|
|
}
|
|
|
|
.header {
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 10px;
|
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.animation-container {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 30px;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.animation-card {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 20px;
|
|
padding: 20px;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
backdrop-filter: blur(10px);
|
|
text-align: center;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.animation-card:hover {
|
|
transform: translateY(-5px);
|
|
}
|
|
|
|
.animation-title {
|
|
font-size: 1.4rem;
|
|
margin-bottom: 15px;
|
|
color: #ffeb3b;
|
|
}
|
|
|
|
.canvas-container {
|
|
position: relative;
|
|
width: 300px;
|
|
height: 300px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
canvas {
|
|
background: #2c3e50;
|
|
border-radius: 15px;
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.controls {
|
|
margin-top: 20px;
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
button {
|
|
background: linear-gradient(45deg, #ff6b6b, #ff8e53);
|
|
border: none;
|
|
padding: 10px 20px;
|
|
border-radius: 50px;
|
|
color: white;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
button:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.description {
|
|
margin-top: 15px;
|
|
font-size: 0.9rem;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.animation-container {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>跑步机测试动画集</h1>
|
|
<p>四个不同的测试场景动画演示</p>
|
|
</div>
|
|
|
|
<div class="animation-container">
|
|
<div class="animation-card">
|
|
<h2 class="animation-title">1. 保持头部静止走路</h2>
|
|
<div class="canvas-container">
|
|
<canvas id="animation1" width="300" height="300"></canvas>
|
|
</div>
|
|
<div class="controls">
|
|
<button onclick="startAnimation(1)">开始</button>
|
|
<button onclick="stopAnimation(1)">停止</button>
|
|
</div>
|
|
<p class="description">头部保持静止,脚在跑步机上正常行走</p>
|
|
</div>
|
|
|
|
<div class="animation-card">
|
|
<h2 class="animation-title">2. 左右摇头测试</h2>
|
|
<div class="canvas-container">
|
|
<canvas id="animation2" width="300" height="300"></canvas>
|
|
</div>
|
|
<div class="controls">
|
|
<button onclick="startAnimation(2)">开始</button>
|
|
<button onclick="stopAnimation(2)">停止</button>
|
|
</div>
|
|
<p class="description">脚在跑步机上走路,同时做左右摇头动作</p>
|
|
</div>
|
|
|
|
<div class="animation-card">
|
|
<h2 class="animation-title">3. 上下点头测试</h2>
|
|
<div class="canvas-container">
|
|
<canvas id="animation3" width="300" height="300"></canvas>
|
|
</div>
|
|
<div class="controls">
|
|
<button onclick="startAnimation(3)">开始</button>
|
|
<button onclick="stopAnimation(3)">停止</button>
|
|
</div>
|
|
<p class="description">脚在跑步机上走路,同时做上下点头动作</p>
|
|
</div>
|
|
|
|
<div class="animation-card">
|
|
<h2 class="animation-title">4. 大声说话测试</h2>
|
|
<div class="canvas-container">
|
|
<canvas id="animation4" width="300" height="300"></canvas>
|
|
</div>
|
|
<div class="controls">
|
|
<button onclick="startAnimation(4)">开始</button>
|
|
<button onclick="stopAnimation(4)">停止</button>
|
|
</div>
|
|
<p class="description">脚在跑步机上走路,同时做大声说话动作</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 动画控制变量
|
|
const animations = {
|
|
1: { running: false, frame: null },
|
|
2: { running: false, frame: null },
|
|
3: { running: false, frame: null },
|
|
4: { running: false, frame: null }
|
|
};
|
|
|
|
// 获取Canvas上下文
|
|
const canvases = {
|
|
1: document.getElementById('animation1').getContext('2d'),
|
|
2: document.getElementById('animation2').getContext('2d'),
|
|
3: document.getElementById('animation3').getContext('2d'),
|
|
4: document.getElementById('animation4').getContext('2d')
|
|
};
|
|
|
|
// 开始动画
|
|
function startAnimation(num) {
|
|
if (animations[num].running) return;
|
|
|
|
animations[num].running = true;
|
|
const ctx = canvases[num];
|
|
const canvas = ctx.canvas;
|
|
|
|
// 初始化变量
|
|
let frameCount = 0;
|
|
let treadmillOffset = 0;
|
|
let legAngle = 0;
|
|
let headAngle = 0;
|
|
let headDirection = 1;
|
|
let speechBubble = 0;
|
|
|
|
function draw() {
|
|
if (!animations[num].running) return;
|
|
|
|
// 清除画布
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// 绘制背景
|
|
drawBackground(ctx, canvas.width, canvas.height);
|
|
|
|
// 绘制跑步机
|
|
drawTreadmill(ctx, canvas.width, canvas.height, treadmillOffset);
|
|
|
|
// 根据不同的动画类型绘制不同的人物动作
|
|
switch (num) {
|
|
case 1:
|
|
drawPersonNormal(ctx, canvas.width, canvas.height, frameCount, legAngle);
|
|
break;
|
|
case 2:
|
|
drawPersonHeadShake(ctx, canvas.width, canvas.height, frameCount, legAngle, headAngle);
|
|
break;
|
|
case 3:
|
|
drawPersonHeadNod(ctx, canvas.width, canvas.height, frameCount, legAngle, headAngle);
|
|
break;
|
|
case 4:
|
|
drawPersonTalking(ctx, canvas.width, canvas.height, frameCount, legAngle, speechBubble);
|
|
break;
|
|
}
|
|
|
|
// 更新动画变量
|
|
frameCount++;
|
|
treadmillOffset = (treadmillOffset + 5) % 40;
|
|
legAngle = Math.sin(frameCount * 0.2) * 0.5;
|
|
|
|
if (num === 2 || num === 3) {
|
|
headAngle += 0.1 * headDirection;
|
|
if (Math.abs(headAngle) > 0.5) headDirection *= -1;
|
|
}
|
|
|
|
if (num === 4) {
|
|
speechBubble = (speechBubble + 1) % 60;
|
|
}
|
|
|
|
// 继续动画循环
|
|
animations[num].frame = requestAnimationFrame(draw);
|
|
}
|
|
|
|
draw();
|
|
}
|
|
|
|
// 停止动画
|
|
function stopAnimation(num) {
|
|
if (animations[num].frame) {
|
|
cancelAnimationFrame(animations[num].frame);
|
|
}
|
|
animations[num].running = false;
|
|
|
|
// 清除画布
|
|
const ctx = canvases[num];
|
|
const canvas = ctx.canvas;
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
drawBackground(ctx, canvas.width, canvas.height);
|
|
}
|
|
|
|
// 绘制背景
|
|
function drawBackground(ctx, width, height) {
|
|
// 绘制渐变背景
|
|
const gradient = ctx.createLinearGradient(0, 0, 0, height);
|
|
gradient.addColorStop(0, '#2c3e50');
|
|
gradient.addColorStop(1, '#3498db');
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, width, height);
|
|
|
|
// 绘制地板
|
|
ctx.fillStyle = '#7f8c8d';
|
|
ctx.fillRect(0, height - 50, width, 50);
|
|
|
|
// 绘制地板纹理
|
|
ctx.strokeStyle = '#95a5a6';
|
|
ctx.lineWidth = 1;
|
|
for (let i = 0; i < width; i += 20) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(i, height - 50);
|
|
ctx.lineTo(i, height);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
// 绘制跑步机
|
|
function drawTreadmill(ctx, width, height, offset) {
|
|
const treadmillY = height - 120;
|
|
|
|
// 绘制跑步机底座
|
|
ctx.fillStyle = '#e74c3c';
|
|
ctx.fillRect(width / 2 - 100, treadmillY, 200, 20);
|
|
|
|
// 绘制跑步机传送带
|
|
ctx.fillStyle = '#34495e';
|
|
ctx.fillRect(width / 2 - 90, treadmillY - 5, 180, 5);
|
|
|
|
// 绘制传送带纹理
|
|
ctx.strokeStyle = '#2c3e50';
|
|
ctx.lineWidth = 1;
|
|
for (let i = 0; i < 180; i += 10) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(width / 2 - 90 + ((i + offset) % 180), treadmillY - 5);
|
|
ctx.lineTo(width / 2 - 90 + ((i + offset) % 180), treadmillY);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// 绘制跑步机扶手
|
|
ctx.strokeStyle = '#e74c3c';
|
|
ctx.lineWidth = 5;
|
|
ctx.beginPath();
|
|
ctx.moveTo(width / 2 - 80, treadmillY - 5);
|
|
ctx.lineTo(width / 2 - 80, treadmillY - 60);
|
|
ctx.stroke();
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(width / 2 + 80, treadmillY - 5);
|
|
ctx.lineTo(width / 2 + 80, treadmillY - 60);
|
|
ctx.stroke();
|
|
|
|
ctx.strokeStyle = '#c0392b';
|
|
ctx.lineWidth = 3;
|
|
ctx.beginPath();
|
|
ctx.moveTo(width / 2 - 80, treadmillY - 60);
|
|
ctx.lineTo(width / 2 + 80, treadmillY - 60);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// 绘制正常走路的人物
|
|
function drawPersonNormal(ctx, width, height, frameCount, legAngle) {
|
|
const personX = width / 2;
|
|
const personY = height - 150;
|
|
|
|
// 绘制身体
|
|
ctx.fillStyle = '#3498db';
|
|
ctx.beginPath();
|
|
ctx.arc(personX, personY - 20, 15, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// 绘制躯干
|
|
ctx.fillStyle = '#2980b9';
|
|
ctx.fillRect(personX - 10, personY - 20, 20, 40);
|
|
|
|
// 绘制头部
|
|
ctx.fillStyle = '#ffdbac';
|
|
ctx.beginPath();
|
|
ctx.arc(personX, personY - 40, 15, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// 绘制面部特征
|
|
ctx.fillStyle = '#000';
|
|
ctx.beginPath();
|
|
ctx.arc(personX - 5, personY - 43, 2, 0, Math.PI * 2);
|
|
ctx.arc(personX + 5, personY - 43, 2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// 绘制嘴巴
|
|
ctx.beginPath();
|
|
ctx.arc(personX, personY - 38, 3, 0, Math.PI);
|
|
ctx.stroke();
|
|
|
|
// 绘制腿部
|
|
ctx.fillStyle = '#34495e';
|
|
drawLeg(ctx, personX - 8, personY + 20, legAngle);
|
|
drawLeg(ctx, personX + 8, personY + 20, -legAngle);
|
|
|
|
// 绘制手臂
|
|
ctx.fillStyle = '#2980b9';
|
|
drawArm(ctx, personX - 15, personY - 15, Math.sin(frameCount * 0.2) * 0.3);
|
|
drawArm(ctx, personX + 15, personY - 15, -Math.sin(frameCount * 0.2) * 0.3);
|
|
}
|
|
|
|
// 绘制摇头的人物
|
|
function drawPersonHeadShake(ctx, width, height, frameCount, legAngle, headAngle) {
|
|
drawPersonNormal(ctx, width, height, frameCount, legAngle);
|
|
|
|
const personX = width / 2;
|
|
const personY = height - 150;
|
|
|
|
// 覆盖头部绘制摇头效果
|
|
ctx.fillStyle = '#ffdbac';
|
|
ctx.save();
|
|
ctx.translate(personX, personY - 40);
|
|
ctx.rotate(headAngle);
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, 15, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// 绘制面部特征(随头部旋转)
|
|
ctx.fillStyle = '#000';
|
|
ctx.beginPath();
|
|
ctx.arc(-5, -3, 2, 0, Math.PI * 2);
|
|
ctx.arc(5, -3, 2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// 绘制嘴巴
|
|
ctx.beginPath();
|
|
ctx.arc(0, 2, 3, 0, Math.PI);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
|
|
// 绘制点头的人物
|
|
function drawPersonHeadNod(ctx, width, height, frameCount, legAngle, headAngle) {
|
|
drawPersonNormal(ctx, width, height, frameCount, legAngle);
|
|
|
|
const personX = width / 2;
|
|
const personY = height - 150;
|
|
|
|
// 覆盖头部绘制点头效果
|
|
ctx.fillStyle = '#ffdbac';
|
|
ctx.save();
|
|
ctx.translate(personX, personY - 40);
|
|
ctx.rotate(headAngle * 0.5);
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, 15, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// 绘制面部特征(随头部旋转)
|
|
ctx.fillStyle = '#000';
|
|
ctx.beginPath();
|
|
ctx.arc(-5, -3, 2, 0, Math.PI * 2);
|
|
ctx.arc(5, -3, 2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// 绘制嘴巴
|
|
ctx.beginPath();
|
|
ctx.arc(0, 2, 3, 0, Math.PI);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
|
|
// 绘制说话的人物
|
|
function drawPersonTalking(ctx, width, height, frameCount, legAngle, speechBubble) {
|
|
drawPersonNormal(ctx, width, height, frameCount, legAngle);
|
|
|
|
const personX = width / 2;
|
|
const personY = height - 150;
|
|
|
|
// 绘制说话的嘴巴
|
|
ctx.fillStyle = '#000';
|
|
if (speechBubble < 30) {
|
|
ctx.beginPath();
|
|
ctx.arc(personX, personY - 38, 4, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
} else {
|
|
ctx.beginPath();
|
|
ctx.arc(personX, personY - 38, 3, 0, Math.PI);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// 绘制对话气泡
|
|
if (speechBubble > 40) {
|
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
|
|
ctx.beginPath();
|
|
ctx.ellipse(personX + 30, personY - 70, 20, 15, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
ctx.fillStyle = '#000';
|
|
ctx.font = '12px Arial';
|
|
ctx.fillText('Hello!', personX + 20, personY - 68);
|
|
|
|
// 绘制气泡指针
|
|
ctx.beginPath();
|
|
ctx.moveTo(personX + 20, personY - 60);
|
|
ctx.lineTo(personX + 10, personY - 50);
|
|
ctx.lineTo(personX + 25, personY - 58);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
// 绘制腿部
|
|
function drawLeg(ctx, x, y, angle) {
|
|
ctx.save();
|
|
ctx.translate(x, y);
|
|
ctx.rotate(angle);
|
|
ctx.fillRect(-3, 0, 6, 30);
|
|
|
|
// 绘制脚
|
|
ctx.fillStyle = '#16a085';
|
|
ctx.fillRect(-5, 30, 10, 5);
|
|
ctx.restore();
|
|
}
|
|
|
|
// 绘制手臂
|
|
function drawArm(ctx, x, y, angle) {
|
|
ctx.save();
|
|
ctx.translate(x, y);
|
|
ctx.rotate(angle);
|
|
ctx.fillRect(-3, 0, 6, 25);
|
|
ctx.restore();
|
|
}
|
|
|
|
// 初始化所有画布
|
|
function initCanvases() {
|
|
for (let i = 1; i <= 4; i++) {
|
|
const ctx = canvases[i];
|
|
const canvas = ctx.canvas;
|
|
drawBackground(ctx, canvas.width, canvas.height);
|
|
drawTreadmill(ctx, canvas.width, canvas.height, 0);
|
|
}
|
|
}
|
|
|
|
// 页面加载时初始化
|
|
window.onload = initCanvases;
|
|
</script>
|
|
</body>
|
|
</html> |