独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TRIZ 理想最终解 (IFR) - 主动悬挂与行星越障</title>
<style>
:root {
--bg-color: #0b0f19;
--grid-color: rgba(30, 41, 59, 0.6);
--terrain-color: #1e293b;
--terrain-stroke: #334155;
/* 机械核心色 */
--mech-base: #475569;
--mech-accent: #94a3b8;
--mech-dark: #1e293b;
/* IFR 亮点色 (主动悬挂 & 稳定载荷) */
--ifr-glow: #0ea5e9;
--ifr-extend: #38bdf8;
--ifr-compress: #f59e0b;
--payload-base: #fbbf24;
--payload-glow: rgba(251, 191, 36, 0.2);
--stable-green: #10b981;
/* 字体 */
--font-mono: 'JetBrains Mono', 'SF Mono', Consolas, 'Courier New', monospace;
}
body {
margin: 0;
padding: 0;
background-color: var(--bg-color);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
overflow: hidden;
font-family: var(--font-mono);
color: #f8fafc;
}
#canvas-container {
position: relative;
width: 100%;
max-width: 1600px;
aspect-ratio: 16 / 9;
box-shadow: 0 0 100px rgba(0, 0, 0, 0.8) inset;
border: 1px solid rgba(255, 255, 255, 0.05);
background: radial-gradient(circle at center, #111827 0%, #030712 100%);
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* 动画与特效类 */
.glow-cyan { filter: drop-shadow(0 0 8px var(--ifr-glow)); }
.glow-green { filter: drop-shadow(0 0 8px var(--stable-green)); }
.glow-amber { filter: drop-shadow(0 0 12px var(--payload-base)); }
.laser-beam {
stroke: var(--stable-green);
stroke-width: 2;
stroke-dasharray: 10 5;
animation: dash 1s linear infinite;
opacity: 0.8;
}
.data-text {
font-size: 14px;
fill: #94a3b8;
letter-spacing: 1px;
}
.data-value {
font-weight: bold;
fill: #f8fafc;
}
.data-highlight {
fill: var(--stable-green);
font-weight: bold;
filter: drop-shadow(0 0 4px rgba(16, 185, 129, 0.5));
}
@keyframes dash {
to { stroke-dashoffset: -15; }
}
/* 控制面板覆盖层 */
#hud {
position: absolute;
top: 30px;
left: 30px;
pointer-events: none;
}
.hud-panel {
background: rgba(15, 23, 42, 0.7);
border: 1px solid rgba(56, 189, 248, 0.3);
border-left: 4px solid var(--ifr-glow);
padding: 20px;
border-radius: 4px;
backdrop-filter: blur(10px);
}
.hud-title {
font-size: 12px;
text-transform: uppercase;
color: var(--ifr-glow);
margin: 0 0 15px 0;
letter-spacing: 2px;
}
.hud-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
width: 250px;
}
.hud-label { color: #64748b; }
.hud-val { color: #f8fafc; font-weight: bold; font-variant-numeric: tabular-nums; }
.status-badge {
display: inline-block;
padding: 2px 8px;
background: rgba(16, 185, 129, 0.2);
color: var(--stable-green);
border-radius: 12px;
font-size: 12px;
border: 1px solid rgba(16, 185, 129, 0.5);
animation: pulse-green 2s infinite;
}
@keyframes pulse-green {
0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); }
70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
}
#controls {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: rgba(15, 23, 42, 0.8);
padding: 10px 20px;
border-radius: 30px;
border: 1px solid rgba(255,255,255,0.1);
display: flex;
gap: 20px;
backdrop-filter: blur(5px);
}
.control-group {
display: flex;
align-items: center;
gap: 10px;
font-size: 12px;
color: #94a3b8;
}
input[type=range] {
accent-color: var(--ifr-glow);
width: 100px;
}
</style>
</head>
<body>
<div id="canvas-container">
<div id="hud">
<div class="hud-panel">
<h2 class="hud-title">System Telemetry // IFR</h2>
<div class="hud-row">
<span class="hud-label">Chassis Pitch:</span>
<span class="hud-val" id="val-pitch">0.0°</span>
</div>
<div class="hud-row">
<span class="hud-label">Payload Posture:</span>
<span class="hud-val" style="color: var(--stable-green);">0.0°</span>
</div>
<div class="hud-row">
<span class="hud-label">Actuator L Stroke:</span>
<span class="hud-val" id="val-actL">0.0 mm</span>
</div>
<div class="hud-row">
<span class="hud-label">Actuator R Stroke:</span>
<span class="hud-val" id="val-actR">0.0 mm</span>
</div>
<div class="hud-row" style="margin-top: 15px;">
<span class="hud-label">Gyro Response:</span>
<span class="hud-val" style="color: var(--ifr-glow);">4.2 ms</span>
</div>
<div style="margin-top: 15px;">
<span class="status-badge">IFR TARGET LOCKED</span>
</div>
</div>
</div>
<div id="controls">
<div class="control-group">
<label>Environment Speed</label>
<input type="range" id="speedCtrl" min="0" max="10" value="4">
</div>
</div>
<svg id="scene" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid meet">
<!-- 定义背景网格与滤镜 -->
<defs>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<rect width="40" height="40" fill="none" stroke="var(--grid-color)" stroke-width="0.5" />
<circle cx="40" cy="40" r="1" fill="#334155" />
</pattern>
<pattern id="hatch" width="10" height="10" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="10" stroke="#fbbf24" stroke-width="2" opacity="0.3"/>
</pattern>
<!-- 渐变遮罩边缘 -->
<linearGradient id="fadeEdge" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="rgba(11, 15, 25, 1)"/>
<stop offset="10%" stop-color="rgba(11, 15, 25, 0)"/>
<stop offset="90%" stop-color="rgba(11, 15, 25, 0)"/>
<stop offset="100%" stop-color="rgba(11, 15, 25, 1)"/>
</linearGradient>
</defs>
<!-- 背景 -->
<rect width="1600" height="900" fill="url(#grid)" />
<!-- 环境基准线 -->
<line x1="0" y1="650" x2="1600" y2="650" stroke="#1e293b" stroke-width="2" stroke-dasharray="10 10"/>
<text x="50" y="640" class="data-text" fill="#475569">BASE ELEVATION ZERO</text>
<!-- 动态地形 (跑步机原理) -->
<g id="terrain-group">
<path id="terrain-path" d="" fill="var(--terrain-color)" stroke="var(--terrain-stroke)" stroke-width="6" stroke-linejoin="round"/>
<path id="terrain-path-glow" d="" fill="none" stroke="var(--ifr-glow)" stroke-width="2" opacity="0.3" filter="blur(4px)"/>
</g>
<!-- 边缘遮罩以隐藏地形生成和消失 -->
<rect width="1600" height="900" fill="url(#fadeEdge)" pointer-events="none" />
<!-- 机器人总成 (固定在屏幕中央) -->
<g id="robot" transform="translate(800, 0)">
<!-- ====== 底层:产生问题的部分 (深色系) ====== -->
<!-- 后轮总成 -->
<g id="rear-wheel-group">
<!-- 悬挂臂 -->
<line id="rear-arm" x1="0" y1="0" x2="-180" y2="0" stroke="var(--mech-base)" stroke-width="12" stroke-linecap="round" />
<circle id="rear-wheel" cx="-180" cy="0" r="45" fill="var(--mech-dark)" stroke="var(--mech-accent)" stroke-width="6" />
<circle id="rear-wheel-hub" cx="-180" cy="0" r="15" fill="var(--mech-base)" />
<line id="rear-wheel-spoke" x1="-180" y1="0" x2="-180" y2="-45" stroke="var(--mech-accent)" stroke-width="4" />
</g>
<!-- 前轮:行星轮越障机构 -->
<g id="front-wheel-group">
<!-- 悬挂臂 -->
<line id="front-arm" x1="0" y1="0" x2="180" y2="0" stroke="var(--mech-base)" stroke-width="12" stroke-linecap="round" />
<!-- 行星架中心 -->
<g id="planetary-hub" transform="translate(180, 0)">
<circle cx="0" cy="0" r="60" fill="none" stroke="#334155" stroke-width="2" stroke-dasharray="8 4"/>
<!-- 三臂支架 -->
<polygon points="0,-45 38.97,22.5 -38.97,22.5" fill="var(--mech-base)" stroke="var(--mech-accent)" stroke-width="4" stroke-linejoin="round"/>
<circle cx="0" cy="0" r="15" fill="var(--mech-dark)" stroke="var(--mech-accent)" stroke-width="3"/>
<!-- 三个子轮 -->
<g transform="translate(0, -45)">
<circle cx="0" cy="0" r="25" fill="var(--mech-dark)" stroke="var(--mech-accent)" stroke-width="4"/>
<line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-accent)" stroke-width="2"/>
<line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-accent)" stroke-width="2"/>
</g>
<g transform="translate(38.97, 22.5)">
<circle cx="0" cy="0" r="25" fill="var(--mech-dark)" stroke="var(--mech-accent)" stroke-width="4"/>
<line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-accent)" stroke-width="2"/>
<line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-accent)" stroke-width="2"/>
</g>
<g transform="translate(-38.97, 22.5)">
<circle cx="0" cy="0" r="25" fill="var(--mech-dark)" stroke="var(--mech-accent)" stroke-width="4"/>
<line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-accent)" stroke-width="2"/>
<line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-accent)" stroke-width="2"/>
</g>
</g>
</g>
<!-- 刚性底盘主梁 -->
<g id="chassis-beam">
<rect x="-220" y="-15" width="440" height="30" rx="10" fill="var(--mech-base)" stroke="var(--mech-accent)" stroke-width="3" />
<circle cx="-180" cy="0" r="8" fill="var(--mech-dark)" />
<circle cx="180" cy="0" r="8" fill="var(--mech-dark)" />
<text x="0" y="5" font-size="12" fill="#94a3b8" text-anchor="middle" dominant-baseline="middle" font-weight="bold">RIGID CHASSIS BASE</text>
</g>
<!-- ====== IFR 核心创新:主动悬挂 (亮色系) ====== -->
<!-- 后置主动推杆 (Actuator L) -->
<g id="actuator-l-group">
<!-- 外筒 (固定在底盘) -->
<line id="act-l-base" x1="-120" y1="-15" x2="-120" y2="-100" stroke="var(--mech-accent)" stroke-width="16" stroke-linecap="round"/>
<!-- 内杆 (连接平台) -->
<line id="act-l-rod" x1="-120" y1="-60" x2="-120" y2="-180" stroke="var(--ifr-glow)" stroke-width="8" stroke-linecap="round" class="glow-cyan"/>
<!-- 关节连接点 -->
<circle id="act-l-joint-bottom" cx="-120" cy="-15" r="6" fill="#f8fafc" />
<circle id="act-l-joint-top" cx="-120" cy="-180" r="6" fill="#f8fafc" />
</g>
<!-- 前置主动推杆 (Actuator R) -->
<g id="actuator-r-group">
<line id="act-r-base" x1="120" y1="-15" x2="120" y2="-100" stroke="var(--mech-accent)" stroke-width="16" stroke-linecap="round"/>
<line id="act-r-rod" x1="120" y1="-60" x2="120" y2="-180" stroke="var(--ifr-glow)" stroke-width="8" stroke-linecap="round" class="glow-cyan"/>
<circle id="act-r-joint-bottom" cx="120" cy="-15" r="6" fill="#f8fafc" />
<circle id="act-r-joint-top" cx="120" cy="-180" r="6" fill="#f8fafc" />
</g>
<!-- ====== 最终理想状态 (水平载荷平台) ====== -->
<!-- 载货平台 -->
<g id="payload-platform" transform="translate(0, -180)">
<!-- 平台基座 -->
<rect x="-160" y="-10" width="320" height="20" rx="5" fill="#334155" stroke="var(--ifr-glow)" stroke-width="2" class="glow-cyan"/>
<!-- 货物箱 -->
<path d="M -120 -10 L -120 -120 L 120 -120 L 120 -10 Z" fill="url(#hatch)" />
<rect x="-120" y="-120" width="240" height="110" rx="8" fill="var(--payload-glow)" stroke="var(--payload-base)" stroke-width="3" class="glow-amber"/>
<!-- 货物标识 -->
<text x="0" y="-60" font-size="24" fill="var(--payload-base)" text-anchor="middle" font-weight="900" letter-spacing="4">SENSITIVE CARGO</text>
<circle cx="0" cy="-120" r="4" fill="var(--payload-base)" class="glow-amber"/>
<!-- IFR 绝对水平激光束指示器 -->
<g class="glow-green">
<line x1="-800" y1="-135" x2="800" y2="-135" class="laser-beam" />
<!-- 陀螺仪图标 -->
<circle cx="0" cy="-135" r="12" fill="#0f172a" stroke="var(--stable-green)" stroke-width="2"/>
<path d="M -8 -135 L 8 -135 M 0 -143 L 0 -127" stroke="var(--stable-green)" stroke-width="2"/>
<text x="20" y="-145" font-size="12" fill="var(--stable-green)" font-weight="bold">IFR HORIZON LOCKED</text>
</g>
</g>
</g>
</svg>
</div>
<script>
/**
* 核心逻辑与运动学解算
*/
// 配置参数
const config = {
wheelbase: 360, // 前后轮轴距
wheelRadiusRear: 45, // 后轮半径
planetaryRadius: 45, // 行星轮旋转外切等效半径 (约等于支架长度)
planetaryArm: 45, // 行星架臂长
platformHeight: -220, // 平台相对于屏幕的固定绝对高度 (实现IFR绝对隔离)
mountOffset: 120, // 推杆安装点距中心距离
baseStrokeLength: 160 // 推杆静止长度
};
// 状态变量
let worldOffset = 0;
let speedMult = 4;
let animationFrameId;
// DOM 元素引用
const els = {
terrain: document.getElementById('terrain-path'),
terrainGlow: document.getElementById('terrain-path-glow'),
chassis: document.getElementById('chassis-beam'),
rearArm: document.getElementById('rear-arm'),
frontArm: document.getElementById('front-arm'),
rearWheelSpoke: document.getElementById('rear-wheel-spoke'),
planetaryHub: document.getElementById('planetary-hub'),
actLBase: document.getElementById('act-l-base'),
actLRod: document.getElementById('act-l-rod'),
actLJointB: document.getElementById('act-l-joint-bottom'),
actLJointT: document.getElementById('act-l-joint-top'),
actRBase: document.getElementById('act-r-base'),
actRRod: document.getElementById('act-r-rod'),
actRJointB: document.getElementById('act-r-joint-bottom'),
actRJointT: document.getElementById('act-r-joint-top'),
platform: document.getElementById('payload-platform'),
// HUD
valPitch: document.getElementById('val-pitch'),
valActL: document.getElementById('val-actL'),
valActR: document.getElementById('val-actR'),
speedCtrl: document.getElementById('speedCtrl')
};
// 监听滑块控制速度
els.speedCtrl.addEventListener('input', (e) => {
speedMult = parseFloat(e.target.value);
});
/**
* 缓动函数 (生成平滑不规则台阶)
*/
function smoothstep(edge0, edge1, x) {
const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));
return t * t * (3 - 2 * t);
}
/**
* 地形高度生成函数
* 基于世界坐标 X 返回对应的地面 Y 坐标
*/
function getTerrainY(worldX) {
// 循环周期 1600
const period = 2000;
let x = ((worldX % period) + period) % period;
let baseY = 650; // 基础地面高度
// 构建不规则连续台阶 (模拟问题场景)
// 阶梯 1: 暴力突变
if (x > 300 && x <= 450) baseY -= smoothstep(300, 380, x) * 120;
else if (x > 450 && x <= 800) baseY -= 120;
// 阶梯 2: 继续上升
else if (x > 800 && x <= 950) baseY -= 120 + smoothstep(800, 880, x) * 80;
else if (x > 950 && x <= 1300) baseY -= 200;
// 下坡:断崖式
else if (x > 1300 && x <= 1500) baseY -= 200 - smoothstep(1300, 1400, x) * 200;
// 叠加一些微小颠簸
baseY += Math.sin(x * 0.05) * 5;
return baseY;
}
/**
* 颜色映射:根据推杆形变计算颜色
*/
function getActuatorColor(currentLength) {
const diff = currentLength - config.baseStrokeLength;
const maxStrain = 50; // 50mm 形变达到最大颜色深度
const ratio = Math.max(-1, Math.min(1, diff / maxStrain));
if (ratio > 0) {
// 拉伸:变蓝
return `color-mix(in srgb, var(--ifr-extend) ${ratio * 100}%, var(--ifr-glow))`;
} else {
// 压缩:变橙
return `color-mix(in srgb, var(--ifr-compress) ${Math.abs(ratio) * 100}%, var(--ifr-glow))`;
}
}
/**
* 主渲染循环
*/
function render() {
// 1. 更新世界偏移量 (跑步机移动)
worldOffset += speedMult;
// 2. 绘制可视地形
let pathD = `M -100 ${getTerrainY(-100 + worldOffset)} `;
for (let screenX = -50; screenX <= 1650; screenX += 20) {
let wx = screenX + worldOffset;
pathD += `L ${screenX} ${getTerrainY(wx)} `;
}
pathD += `L 1650 1000 L -100 1000 Z`;
els.terrain.setAttribute('d', pathD);
els.terrainGlow.setAttribute('d', pathD);
// 3. 机器人运动学解算 (Robot 固定在 screenX = 800)
const centerScreenX = 0; // 相对机器人 Group 的内部坐标系系
const frontWorldX = 800 + (config.wheelbase / 2) + worldOffset;
const rearWorldX = 800 - (config.wheelbase / 2) + worldOffset;
// 获取前后轮与地面的接触高度
const frontY = getTerrainY(frontWorldX) - config.planetaryRadius;
const rearY = getTerrainY(rearWorldX) - config.wheelRadiusRear;
// 计算底盘倾角 (弧度)
// dx = wheelbase, dy = frontY - rearY
const chassisAngleRad = Math.atan2(frontY - rearY, config.wheelbase);
const chassisAngleDeg = chassisAngleRad * (180 / Math.PI);
// 计算底盘中心绝对高度
const chassisCenterY = (frontY + rearY) / 2;
// --- 应用底盘变换 (展现问题:剧烈颠簸) ---
// 悬挂臂作为刚体连接,通过改变 Y 坐标和旋转来体现
els.chassis.setAttribute('transform', `translate(0, ${chassisCenterY}) rotate(${chassisAngleDeg})`);
els.rearArm.setAttribute('y2', rearY - chassisCenterY); // 连接中心到后轮
els.frontArm.setAttribute('y2', frontY - chassisCenterY); // 连接中心到前轮
document.getElementById('rear-wheel').setAttribute('cy', rearY);
document.getElementById('rear-wheel-hub').setAttribute('cy', rearY);
document.getElementById('rear-wheel-spoke').setAttribute('transform', `translate(-180, ${rearY}) rotate(${(worldOffset * 2) % 360}) translate(180, ${-rearY})`);
// 行星轮越障动画逻辑:当遇到陡坡时,模拟行星架翻转
// 简单计算地形导数来决定是否触发翻转
const slope = getTerrainY(frontWorldX + 10) - getTerrainY(frontWorldX - 10);
let extraPlanetaryRot = 0;
if (slope < -10) { // 上坡
extraPlanetaryRot = slope * -5;
}
const baseSpin = (worldOffset * 1.5) % 360;
els.planetaryHub.setAttribute('transform', `translate(180, ${frontY}) rotate(${baseSpin + extraPlanetaryRot})`);
// --- 应用核心 IFR 方案:主动悬挂反向补偿 ---
// 目标:平台保持绝对高度和 0 度倾角 (即不随底盘翻转或上下颠簸)
// 在 SVG 的 Robot 局部坐标系中,平台中心坐标固定为 (0, config.platformHeight)
const platformY_local = config.platformHeight;
els.platform.setAttribute('transform', `translate(0, ${platformY_local})`);
// 解算左右液压杆形态
// 底盘上的安装点 (随底盘中心高度和倾角变化)
const A = chassisAngleRad;
const L_baseX_local = -config.mountOffset * Math.cos(A);
const L_baseY_local = chassisCenterY - config.mountOffset * Math.sin(A);
const R_baseX_local = config.mountOffset * Math.cos(A);
const R_baseY_local = chassisCenterY + config.mountOffset * Math.sin(A);
// 平台上的安装点 (恒定水平,固定于平台)
const L_topX_local = -config.mountOffset;
const L_topY_local = platformY_local;
const R_topX_local = config.mountOffset;
const R_topY_local = platformY_local;
// 计算当前推杆长度
const strokeL = Math.sqrt(Math.pow(L_baseX_local - L_topX_local, 2) + Math.pow(L_baseY_local - L_topY_local, 2));
const strokeR = Math.sqrt(Math.pow(R_baseX_local - R_topX_local, 2) + Math.pow(R_baseY_local - R_topY_local, 2));
// 更新左侧推杆 SVG 坐标
els.actLJointB.setAttribute('cx', L_baseX_local); els.actLJointB.setAttribute('cy', L_baseY_local);
els.actLJointT.setAttribute('cx', L_topX_local); els.actLJointT.setAttribute('cy', L_topY_local);
els.actLBase.setAttribute('x1', L_baseX_local); els.actLBase.setAttribute('y1', L_baseY_local);
els.actLBase.setAttribute('x2', L_baseX_local + (L_topX_local - L_baseX_local)*0.5);
els.actLBase.setAttribute('y2', L_baseY_local + (L_topY_local - L_baseY_local)*0.5); // 外筒指向
els.actLRod.setAttribute('x1', L_baseX_local); els.actLRod.setAttribute('y1', L_baseY_local);
els.actLRod.setAttribute('x2', L_topX_local); els.actLRod.setAttribute('y2', L_topY_local);
// 更新右侧推杆 SVG 坐标
els.actRJointB.setAttribute('cx', R_baseX_local); els.actRJointB.setAttribute('cy', R_baseY_local);
els.actRJointT.setAttribute('cx', R_topX_local); els.actRJointT.setAttribute('cy', R_topY_local);
els.actRBase.setAttribute('x1', R_baseX_local); els.actRBase.setAttribute('y1', R_baseY_local);
els.actRBase.setAttribute('x2', R_baseX_local + (R_topX_local - R_baseX_local)*0.5);
els.actRBase.setAttribute('y2', R_baseY_local + (R_topY_local - R_baseY_local)*0.5);
els.actRRod.setAttribute('x1', R_baseX_local); els.actRRod.setAttribute('y1', R_baseY_local);
els.actRRod.setAttribute('x2', R_topX_local); els.actRRod.setAttribute('y2', R_topY_local);
// 动态颜色反馈应力
els.actLRod.style.stroke = getActuatorColor(strokeL);
els.actRRod.style.stroke = getActuatorColor(strokeR);
// 4. 更新 UI 遥测数据
// 底盘因地势引起的俯仰 (负值为仰头,此处转为正负直观展示)
els.valPitch.textContent = (-chassisAngleDeg).toFixed(1) + '°';
// 警示色:当倾角过大时底盘数据标红,但载荷保持绿
els.valPitch.style.color = Math.abs(chassisAngleDeg) > 15 ? '#ef4444' : '#f8fafc';
// 推杆动作量 (相对静止长度的形变)
const deltaL = config.baseStrokeLength - strokeL;
const deltaR = config.baseStrokeLength - strokeR;
els.valActL.textContent = (deltaL > 0 ? '+' : '') + deltaL.toFixed(1) + ' mm';
els.valActR.textContent = (deltaR > 0 ? '+' : '') + deltaR.toFixed(1) + ' mm';
// 循环请求下一帧
animationFrameId = requestAnimationFrame(render);
}
// 页面加载完成后自动启动动画,无须用户交互
window.addEventListener('DOMContentLoaded', () => {
render();
});
// 确保 iframe 重载时也能正确触发
window.addEventListener('load', () => {
if(!animationFrameId) {
render();
}
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
