<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>V型渐缩滑道被动定向落料原理</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=IBM+Plex+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root{--bg:#060a12;--fg:#c8d4e0;--muted:#4a5568;--accent:#00e8a0;--accent2:#00b8ff;--shaft:#e89030;--shaft-hi:#f0c858;--metal:#1e2d3d;--metal-lt:#2a3d50;--metal-edge:#3a5068;--danger:#ff4060;--card:#0c1420;--border:#162030}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'IBM Plex Mono',monospace;min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:20px 16px;overflow-x:hidden}
.hdr{text-align:center;margin-bottom:14px}
.hdr h1{font-family:'Syne',sans-serif;font-weight:800;font-size:clamp(20px,3.2vw,30px);letter-spacing:-.5px;color:var(--fg)}
.hdr .sub{font-size:12px;color:var(--accent);margin-top:4px;font-weight:400;letter-spacing:.5px}
.wrap{width:100%;max-width:1020px;background:var(--card);border:1px solid var(--border);border-radius:12px;overflow:hidden;position:relative}
.wrap svg{display:block;width:100%;height:auto}
.ctrls{display:flex;align-items:center;gap:14px;margin-top:14px;padding:10px 18px;background:var(--card);border:1px solid var(--border);border-radius:10px;flex-wrap:wrap;justify-content:center}
.btn{padding:7px 18px;border:1px solid var(--border);border-radius:6px;background:var(--metal);color:var(--fg);font-family:'IBM Plex Mono',monospace;font-size:12px;cursor:pointer;transition:all .2s;user-select:none}
.btn:hover{background:var(--metal-lt);border-color:var(--accent)}
.btn.on{background:var(--accent);color:var(--bg);border-color:var(--accent)}
.sg{display:flex;align-items:center;gap:6px}
.sg label{font-size:11px;color:var(--muted);white-space:nowrap}
.sg input[type=range]{width:90px;accent-color:var(--accent);cursor:pointer}
.sg .v{font-size:11px;color:var(--accent);min-width:36px}
.pbar{display:flex;gap:3px;margin-top:10px;flex-wrap:wrap;justify-content:center}
.pi{padding:4px 10px;font-size:10px;border-radius:4px;background:var(--metal);color:var(--muted);transition:all .3s;white-space:nowrap}
.pi.on{background:var(--accent);color:var(--bg)}
.pi.warn{background:var(--danger);color:#fff}
.info{margin-top:14px;padding:14px 18px;background:var(--card);border:1px solid var(--border);border-radius:10px;max-width:1020px;width:100%}
.info h3{font-family:'Syne',sans-serif;font-size:14px;color:var(--accent);margin-bottom:6px}
.info p{font-size:11px;color:var(--muted);line-height:1.7}
.tip{font-size:11px;color:var(--danger);margin-top:8px;display:none}
.tip.show{display:block}
@keyframes pulse{0%,100%{opacity:.6}50%{opacity:1}}
</style>
</head>
<body>
<div class="hdr">
<h1>V型渐缩滑道 · 被动定向落料</h1>
<div class="sub">IFR 最终理想解 — 以几何约束替代主动翻转,零能耗自动定向</div>
</div>
<div class="wrap">
<svg id="scene" viewBox="0 0 1000 680" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gMetal" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3a5068"/><stop offset="50%" stop-color="#283e52"/><stop offset="100%" stop-color="#1c2e40"/>
</linearGradient>
<linearGradient id="gMetalH" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#2a3d50"/><stop offset="50%" stop-color="#3a5068"/><stop offset="100%" stop-color="#2a3d50"/>
</linearGradient>
<linearGradient id="gShaft" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f0c858"/><stop offset="40%" stop-color="#e89830"/><stop offset="100%" stop-color="#c07018"/>
</linearGradient>
<linearGradient id="gShaftS" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f8d868"/><stop offset="40%" stop-color="#e8a838"/><stop offset="100%" stop-color="#c87820"/>
</linearGradient>
<filter id="glow"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<filter id="glowS"><feGaussianBlur stdDeviation="8" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<filter id="shadow"><feDropShadow dx="2" dy="4" stdDeviation="3" flood-color="#000" flood-opacity=".4"/></filter>
<marker id="arrG" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#00e8a0"/></marker>
<marker id="arrR" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="#ff4060"/></marker>
<clipPath id="holeClip"><rect x="488" y="370" width="104" height="210"/></clipPath>
</defs>
<!-- 背景 -->
<rect width="1000" height="680" fill="#080e18"/>
<!-- 微网格 -->
<g opacity=".04" stroke="#4a6a8a" stroke-width=".5" id="gridG"></g>
<!-- 标题 -->
<text x="310" y="48" font-family="Syne,sans-serif" font-weight="700" font-size="15" fill="#8a9aaa" text-anchor="middle" letter-spacing="1">纯被动结构:滑移 → 翻转 → 导向</text>
<!-- V型导轨 -->
<g id="vgroove">
<!-- 主体 -->
<path d="M80 232 L540 267 L540 288 L80 253 Z" fill="url(#gMetal)" stroke="#3a5068" stroke-width="1"/>
<!-- 顶面高光 -->
<line x1="80" y1="232" x2="540" y2="267" stroke="#5a7a90" stroke-width="1.2"/>
<!-- 左端面 -->
<line x1="80" y1="232" x2="80" y2="253" stroke="#4a6a80" stroke-width="1"/>
<!-- 右端面(关键边缘) -->
<line id="pivotEdgeLine" x1="540" y1="267" x2="540" y2="288" stroke="#6a8a9a" stroke-width="2.5"/>
<!-- 底部支撑 -->
<path d="M80 253 L80 310 L65 310 L65 340 L555 340 L555 310 L540 310 L540 288" fill="url(#gMetalH)" stroke="#2a3d50" stroke-width="1"/>
<path d="M65 340 L65 360 L555 360 L555 340 Z" fill="#141e2a" stroke="#2a3d50" stroke-width="1"/>
<!-- V槽纹理线 -->
<g opacity=".15" stroke="#5a7a90" stroke-width=".5">
<line x1="120" y1="234" x2="120" y2="255"/><line x1="180" y1="237" x2="180" y2="258"/>
<line x1="240" y1="241" x2="240" y2="261"/><line x1="300" y1="244" x2="300" y2="265"/>
<line x1="360" y1="248" x2="360" y2="268"/><line x1="420" y1="251" x2="420" y2="272"/>
<line x1="480" y1="255" x2="480" y2="275"/>
</g>
</g>
<!-- 间隙虚线 -->
<line x1="540" y1="288" x2="540" y2="370" stroke="#2a3d50" stroke-width="1" stroke-dasharray="4,4"/>
<!-- 落料座 -->
<g id="dropseat">
<!-- 主体 -->
<path id="dsBody" d="M470 370 L488 370 L500 378 L500 402 L517 402 L517 572 L563 572 L563 402 L580 402 L580 378 L592 370 L610 370 L610 572 L470 572 Z" fill="url(#gMetal)" stroke="#3a5068" stroke-width="1.5"/>
<!-- 孔内暗面 -->
<path d="M500 378 L500 402 L517 402 L517 572 L563 572 L563 402 L580 402 L580 378 Z" fill="#060c16" opacity=".85"/>
<!-- 倒角高亮 -->
<path id="chL" d="M488 370 L500 378" stroke="#00e8a0" stroke-width="0" opacity="0"/>
<path id="chR" d="M592 370 L580 378" stroke="#00e8a0" stroke-width="0" opacity="0"/>
<!-- 台阶面 -->
<line x1="500" y1="402" x2="517" y2="402" stroke="#5a7a90" stroke-width="1.5"/>
<line x1="563" y1="402" x2="580" y2="402" stroke="#5a7a90" stroke-width="1.5"/>
<!-- 孔壁 -->
<line x1="500" y1="378" x2="500" y2="402" stroke="#4a6a80" stroke-width="1"/>
<line x1="580" y1="378" x2="580" y2="402" stroke="#4a6a80" stroke-width="1"/>
<line x1="517" y1="402" x2="517" y2="572" stroke="#4a6a80" stroke-width="1"/>
<line x1="563" y1="402" x2="563" y2="572" stroke="#4a6a80" stroke-width="1"/>
</g>
<!-- 尺寸标注 -->
<g id="dims" opacity=".55">
<text x="46" y="228" font-family="IBM Plex Mono" font-size="10" fill="#5a7a90">V槽 60°</text>
<text x="618" y="395" font-family="IBM Plex Mono" font-size="10" fill="#5a7a90">Φ3.6</text>
<text x="568" y="460" font-family="IBM Plex Mono" font-size="10" fill="#5a7a90">Φ2.1</text>
<text x="618" y="376" font-family="IBM Plex Mono" font-size="9" fill="#00e8a0" opacity=".8">30°倒角</text>
<!-- 倒角角度标注弧 -->
<path d="M494 370 L500 370 L500 376" fill="none" stroke="#00e8a0" stroke-width=".8" opacity=".5"/>
</g>
<!-- 轨迹线 -->
<path id="traj" d="" fill="none" stroke="#00b8ff" stroke-width="1.8" opacity=".35" stroke-linecap="round"/>
<path id="trajGlow" d="" fill="none" stroke="#00b8ff" stroke-width="4" opacity=".1" stroke-linecap="round"/>
<!-- 支点指示器 -->
<g id="pivotInd" opacity="0">
<circle cx="540" cy="267" r="7" fill="none" stroke="#00e8a0" stroke-width="2" filter="url(#glow)"/>
<circle cx="540" cy="267" r="2.5" fill="#00e8a0"/>
<text x="555" y="260" font-family="IBM Plex Mono" font-size="10" fill="#00e8a0">支点</text>
</g>
<!-- 重心指示 -->
<g id="cogInd" opacity="0">
<circle id="cogDot" cx="0" cy="0" r="3.5" fill="#ff4060" filter="url(#glow)"/>
<line id="cogLine" x1="0" y1="4" x2="0" y2="28" stroke="#ff4060" stroke-width="1" stroke-dasharray="2,2" marker-end="url(#arrR)"/>
<text id="cogTxt" x="8" y="22" font-family="IBM Plex Mono" font-size="9" fill="#ff4060">G</text>
</g>
<!-- 重力箭头 -->
<g id="gravArr" opacity="0">
<line id="gravLine" x1="0" y1="0" x2="0" y2="38" stroke="#ff4060" stroke-width="1.8" marker-end="url(#arrR)"/>
</g>
<!-- 台阶轴(动画主体) -->
<g id="shaft" filter="url(#shadow)">
<!-- 大端 -->
<rect x="0" y="-70" width="60" height="70" rx="2" fill="url(#gShaft)" stroke="#a06018" stroke-width="1.2"/>
<!-- 小端 -->
<rect x="60" y="-40" width="60" height="40" rx="2" fill="url(#gShaftS)" stroke="#a06018" stroke-width="1.2"/>
<!-- 台阶面 -->
<line x1="60" y1="-40" x2="60" y2="0" stroke="#a06018" stroke-width="1.5"/>
<!-- 大端顶面高光 -->
<line x1="2" y1="-68" x2="58" y2="-68" stroke="#f8d868" stroke-width=".8" opacity=".5"/>
<!-- 小端顶面高光 -->
<line x1="62" y1="-38" x2="118" y2="-38" stroke="#f8e078" stroke-width=".8" opacity=".5"/>
<!-- 标注 -->
<text x="30" y="-46" font-family="IBM Plex Mono" font-size="9" fill="#a06828" text-anchor="middle" opacity=".75">Φ3.5</text>
<text x="90" y="-17" font-family="IBM Plex Mono" font-size="9" fill="#a06828" text-anchor="middle" opacity=".75">Φ2.0</text>
</g>
<!-- V槽截面插图 -->
<g transform="translate(850,110)">
<rect x="-60" y="-20" width="120" height="110" rx="8" fill="#0a1420" stroke="#2a3d50" stroke-width="1"/>
<text x="0" y="-5" font-family="IBM Plex Mono" font-size="9" fill="#5a7a90" text-anchor="middle">V槽截面</text>
<line x1="0" y1="18" x2="-32" y2="72" stroke="#4a6a80" stroke-width="2.5" stroke-linecap="round"/>
<line x1="0" y1="18" x2="32" y2="72" stroke="#4a6a80" stroke-width="2.5" stroke-linecap="round"/>
<line x1="-32" y1="72" x2="32" y2="72" stroke="#4a6a80" stroke-width="2"/>
<circle cx="0" cy="44" r="13" fill="url(#gShaft)" stroke="#a06018" stroke-width="1" opacity=".75"/>
<path d="M-13 24 A16 16 0 0 1 13 24" fill="none" stroke="#00e8a0" stroke-width="1" opacity=".6"/>
<text x="0" y="19" font-family="IBM Plex Mono" font-size="8" fill="#00e8a0" text-anchor="middle" opacity=".8">60°</text>
</g>
<!-- 阶段标签 -->
<text id="phaseLbl" x="310" y="640" font-family="Syne,sans-serif" font-weight="600" font-size="17" fill="#00e8a0" text-anchor="middle" opacity="0"></text>
<!-- IFR 消息 -->
<g id="ifrMsg" opacity="0">
<rect x="90" y="510" width="280" height="62" rx="8" fill="#0a1420" stroke="#00e8a0" stroke-width="1.2" opacity=".85"/>
<text x="230" y="534" font-family="Syne,sans-serif" font-weight="700" font-size="13" fill="#00e8a0" text-anchor="middle">理想解:零能耗自动定向</text>
<text x="230" y="556" font-family="IBM Plex Mono" font-size="10" fill="#5a7a90" text-anchor="middle">重力 + 几何约束 ≡ 主动翻转机构</text>
</g>
<!-- 状态提示 -->
<text id="statusTxt" x="310" y="618" font-family="IBM Plex Mono" font-size="11" fill="#5a7a90" text-anchor="middle" opacity="0"></text>
</svg>
</div>
<div class="ctrls">
<button class="btn" id="playBtn">播放</button>
<button class="btn" id="resetBtn">重置</button>
<button class="btn" id="loopBtn">循环:关</button>
<div class="sg"><label>速度</label><input type="range" id="spdR" min="0.2" max="3" step="0.1" value="1"><span class="v" id="spdV">1.0x</span></div>
<div class="sg"><label>悬空量</label><input type="range" id="ohR" min="30" max="100" step="1" value="72"><span class="v" id="ohV">72</span></div>
<div class="sg"><label>进度</label><input type="range" id="pgR" min="0" max="1000" step="1" value="0"><span class="v" id="pgV">0%</span></div>
</div>
<div class="pbar" id="pbar">
<div class="pi" data-p="0">滑入</div>
<div class="pi" data-p="1">悬空失稳</div>
<div class="pi" data-p="2">绕支点翻转</div>
<div class="pi" data-p="3">倒角导向</div>
<div class="pi" data-p="4">台阶限位落定</div>
</div>
<div class="info">
<h3>IFR 原理解析</h3>
<p>删除刚性翻转机械臂,替换为"V型渐缩滑道 + 阶梯落料孔"纯被动结构。台阶轴在V型槽内靠重力下滑,当前端悬空时因重心偏移自动翻转,落料孔口30°导向倒角捕获并扶正轴体,阶梯面限位确保小端优先插入2.1mm孔。全过程无需主动驱动,仅依赖重力与几何约束完成姿态转换。</p>
<div class="tip" id="tipMsg">⚠ 当前悬空量不足以使重心越过支点,轴不会翻转——请增大悬空量</div>
</div>
<script>
/* ===== 常量 ===== */
const SW=120,SLW=60,SLH=70,SSW=60,SSH=40;
const VSX=100,VSY=232,VEX=540,VEY=267;
const PX=VEX,PY=VEY;
const DST=370;
/* 重心位置(距左端) */
const COG=(30*SLW*SLH+90*SSW*SSH)/(SLW*SLH+SSW*SSH);
/* 临界悬空量 */
const CRIT_OH=SW-COG;
/* ===== 状态 ===== */
let prog=0,playing=false,spd=1,ohLen=72,looping=false,manualPg=false,lastT=null;
let trajPts=[];
/* ===== 工具 ===== */
const lerp=(a,b,t)=>a+(b-a)*t;
const clamp=(v,lo,hi)=>Math.max(lo,Math.min(hi,v));
function easeIO(t){return t<.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2}
function easeIn(t){return t*t*t}
function easeOut(t){return 1-Math.pow(1-t,3)}
function easeOutB(t){const n=7.5625,d=2.75;if(t<1/d)return n*t*t;if(t<2/d)return n*(t-=1.5/d)*t+.75;if(t<2.5/d)return n*(t-=2.25/d)*t+.9375;return n*(t-=2.625/d)*t+.984375}
function vY(x){return VSY+(x-VSX)/(VEX-VSX)*(VEY-VSY)}
/* ===== 变换计算 ===== */
function xformPt(lx,ly,tx,ty,ang,ox,oy){
const a=ang*Math.PI/180;
const x1=lx+ox,y1=ly+oy;
const x2=x1*Math.cos(a)-y1*Math.sin(a);
const y2=x1*Math.sin(a)+y1*Math.cos(a);
return{x:x2+tx,y:y2+ty};
}
function getState(p){
const po=SW-ohLen;
const canTip=ohLen>CRIT_OH;
/* 不翻转模式 */
if(!canTip){
if(p<.5){
const t=p/.5,e=easeIO(t);
const x=lerp(VSX,PX-po,e),y=vY(x);
return{tf:`translate(${x},${y}) rotate(0)`,ang:0,ph:-1,pn:'重心未过支点 — 稳定',cx:x+COG,cy:y-35};
}
const x=PX-po,y=vY(x);
return{tf:`translate(${x},${y}) rotate(0)`,ang:0,ph:-1,pn:'重心未过支点 — 无法翻转',cx:x+COG,cy:y-35};
}
/* 阶段0:滑入 */
if(p<.25){
const t=p/.25,e=easeIO(t);
const x=lerp(VSX,PX-po,e),y=vY(x);
return{tf:`translate(${x},${y}) rotate(0)`,ang:0,ph:0,pn:'台阶轴沿V型槽滑入',cx:x+COG,cy:y-35};
}
/* 阶段1:悬空失稳 */
if(p<.36){
const t=(p-.25)/.11;
const ang=lerp(0,10,easeIn(t));
const tf=`translate(${PX},${PY}) rotate(${ang}) translate(${-po},0)`;
const c=xformPt(COG,-35,PX,PY,ang,-po,0);
return{tf,ang,ph:1,pn:'前端悬空 · 重心偏移',cx:c.x,cy:c.y};
}
/* 阶段2:翻转 */
if(p<.63){
const t=(p-.36)/.27;
const ang=lerp(10,90,easeIn(t));
const tf=`translate(${PX},${PY}) rotate(${ang}) translate(${-po},0)`;
const c=xformPt(COG,-35,PX,PY,ang,-po,0);
return{tf,ang,ph:2,pn:'绕支点翻转90°落下',cx:c.x,cy:c.y};
}
/* 阶段3:导向 */
if(p<.81){
const t=(p-.63)/.18;
const fd=lerp(0,88,easeOut(t));
const lat=lerp(0,-22,easeIO(t));
const tf=`translate(${PX+lat},${PY+fd}) rotate(90) translate(${-po},0)`;
const c=xformPt(COG,-35,PX+lat,PY+fd,90,-po,0);
return{tf,ang:90,ph:3,pn:'30°倒角导向 · 自动扶正',cx:c.x,cy:c.y};
}
/* 阶段4:落定 */
if(p<.96){
const t=(p-.81)/.15;
const fd=lerp(88,128,easeOutB(t));
const lat=lerp(-22,-22,t);
const tf=`translate(${PX+lat},${PY+fd}) rotate(90) translate(${-po},0)`;
const c=xformPt(COG,-35,PX+lat,PY+fd,90,-po,0);
return{tf,ang:90,ph:4,pn:'台阶面限位 · 小端插入Φ2.1孔',cx:c.x,cy:c.y};
}
/* 完成 */
const fd=128,lat=-22;
const tf=`translate(${PX+lat},${PY+fd}) rotate(90) translate(${-po},0)`;
const c=xformPt(COG,-35,PX+lat,PY+fd,90,-po,0);
return{tf,ang:90,ph:5,pn:'定向完成',cx:c.x,cy:c.y};
}
/* ===== 场景更新 ===== */
const $=id=>document.getElementById(id);
function updateScene(){
const s=getState(prog);
$('shaft').setAttribute('transform',s.tf);
/* 阶段指示 */
document.querySelectorAll('.pi').forEach(el=>{
const pp=parseInt(el.dataset.p);
el.classList.toggle('on',pp===s.ph);
el.classList.toggle('warn',s.ph===-1&&pp===1);
});
/* 阶段标签 */
const lbl=$('phaseLbl');
lbl.textContent=s.pn;
lbl.setAttribute('opacity',prog>0?'1':'0');
/* 支点指示器 */
const showPivot=s.ph===1||s.ph===2;
$('pivotInd').setAttribute('opacity',showPivot?'1':'0');
if(showPivot){
const pulse=.6+.4*Math.sin(Date.now()*.006);
$('pivotInd').setAttribute('opacity',pulse.toFixed(2));
}
/* 重心指示 */
const showCOG=s.ph>=0&&s.ph<=2;
const cogG=$('cogInd');
if(showCOG){
cogG.setAttribute('opacity','1');
$('cogDot').setAttribute('cx',s.cx);
$('cogDot').setAttribute('cy',s.cy);
$('cogLine').setAttribute('x1',s.cx);$('cogLine').setAttribute('y1',s.cy+5);
$('cogLine').setAttribute('x2',s.cx);$('cogLine').setAttribute('y2',s.cy+30);
$('cogTxt').setAttribute('x',s.cx+8);$('cogTxt').setAttribute('y',s.cy+24);
/* 重心是否过支点 */
const pastPivot=s.cx>PX;
$('cogDot').setAttribute('fill',pastPivot?'#ff4060':'#00e8a0');
$('cogLine').setAttribute('stroke',pastPivot?'#ff4060':'#00e8a0');
$('cogTxt').setAttribute('fill',pastPivot?'#ff4060':'#00e8a0');
$('cogTxt').textContent=pastPivot?'G(失稳)':'G';
}else{
cogG.setAttribute('opacity','0');
}
/* 重力箭头 */
const showGrav=s.ph===2;
const gA=$('gravArr');
if(showGrav){
gA.setAttribute('opacity','1');
$('gravLine').setAttribute('x1',s.cx);$('gravLine').setAttribute('y1',s.cy+5);
$('gravLine').setAttribute('x2',s.cx);$('gravLine').setAttribute('y2',s.cy+42);
}else{
gA.setAttribute('opacity','0');
}
/* 倒角高亮 */
const showCh=s.ph===3;
['chL','chR'].forEach(id=>{
$(id).setAttribute('stroke-width',showCh?'3':'0');
$(id).setAttribute('opacity',showCh?'1':'0');
});
/* IFR 消息 */
$('ifrMsg').setAttribute('opacity',s.ph>=4?'1':'0');
/* 状态文字 */
const st=$('statusTxt');
if(s.ph===-1){
st.textContent='悬空量 < 临界值 ('+CRIT_OH.toFixed(0)+'px),轴不会翻转';
st.setAttribute('opacity','1');st.setAttribute('fill','#ff4060');
}else if(s.ph>=4){
st.textContent='IFR实现:零主动驱动,纯几何约束完成定向';
st.setAttribute('opacity','1');st.setAttribute('fill','#00e8a0');
}else{
st.setAttribute('opacity','0');
}
/* 临界提示 */
$('tipMsg').classList.toggle('show',ohLen<=CRIT_OH);
/* 轨迹 */
if(playing&&!manualPg&&prog>0.02){
const tip=xformPt(SW,0,s.tf);
trajPts.push(tip);
if(trajPts.length>1){
let d='M'+trajPts[0].x.toFixed(1)+','+trajPts[0].y.toFixed(1);
for(let i=1;i<trajPts.length;i++){
d+=' L'+trajPts[i].x.toFixed(1)+','+trajPts[i].y.toFixed(1);
}
$('traj').setAttribute('d',d);
$('trajGlow').setAttribute('d',d);
}
}
/* 进度滑块 */
if(!manualPg){
$('pgR').value=Math.round(prog*1000);
$('pgV').textContent=Math.round(prog*100)+'%';
}
}
/* ===== 动画循环 ===== */
function tick(ts){
if(!playing){lastT=null;return}
if(lastT===null)lastT=ts;
const dt=(ts-lastT)/1000;
lastT=ts;
if(!manualPg){
prog+=dt*spd*.18;
if(prog>=1){
prog=1;playing=false;
$('playBtn').textContent='重播';
if(looping)setTimeout(()=>{resetAnim();togglePlay()},1800);
}
}
updateScene();
if(playing)requestAnimationFrame(tick);
}
function togglePlay(){
if(prog>=1)resetAnim();
playing=!playing;
$('playBtn').textContent=playing?'暂停':'播放';
$('playBtn').classList.toggle('on',playing);
if(playing)requestAnimationFrame(tick);
}
function resetAnim(){
prog=0;playing=false;lastT=null;trajPts=[];
$('traj').setAttribute('d','');$('trajGlow').setAttribute('d','');
$('playBtn').textContent='播放';$('playBtn').classList.remove('on');
updateScene();
}
/* ===== 事件绑定 ===== */
$('playBtn').onclick=togglePlay;
$('resetBtn').onclick=resetAnim;
$('loopBtn').onclick=function(){looping=!looping;this.textContent='循环:'+(looping?'开':'关');this.classList.toggle('on',looping)};
$('spdR').oninput=function(){spd=parseFloat(this.value);$('spdV').textContent=spd.toFixed(1)+'x'};
$('ohR').oninput=function(){ohLen=parseInt(this.value);$('ohV').textContent=ohLen;resetAnim()};
$('pgR').oninput=function(){manualPg=true;prog=parseInt(this.value)/1000;$('pgV').textContent=Math.round(prog*100)+'%';updateScene()};
$('pgR').onchange=function(){manualPg=false};
/* ===== 初始化网格 ===== */
(function(){
const g=$('gridG');let s='';
for(let x=0;x<=1000;x+=40)s+=`<line x1="${x}" y1="0" x2="${x}" y2="680"/>`;
for(let y=0;y<=680;y+=40)s+=`<line x1="0" y1="${y}" x2="1000" y2="${y}"/>`;
g.innerHTML=s;
})();
/* ===== 启动 ===== */
updateScene();
</script>
</body>
</html>
实现说明:
动画核心逻辑:整个动画分为5个阶段(滑入→悬空失稳→绕支点翻转→倒角导向→台阶限位落定),使用 SVG transform 组合(translate + rotate)精确模拟台阶轴在V型槽内的运动学行为。翻转阶段采用 easeIn 缓动模拟重力加速,落定阶段采用弹跳缓动表现微小弹跳效果。
IFR 思想体现:
- 聚焦理想状态——动画直接展示纯被动结构的工作过程,无需对比旧方案
- 重心标记(红色/绿色)实时显示失稳临界点,直观揭示"重力即驱动力"的资源利用逻辑
- 倒角高亮动画突出几何约束如何替代主动扶正机构
- 完成时显示 IFR 等式:"重力 + 几何约束 ≡ 主动翻转机构"
交互设计:
- 悬空量滑块:可调节轴伸出V槽边缘的长度,低于临界值(≈68px)时轴不会翻转,直观体验关键设计参数的敏感性
- 进度条:支持手动拖拽逐帧观察
- 速度控制:0.2x~3x 变速
- 循环模式:自动重复播放
视觉引导:支点脉冲光晕、重心颜色变化(绿→红表示失稳)、倒角高亮、轨迹线等共同引导关注核心创新点。
积分规则:第一轮对话扣减6分,后续每轮扣4分
等待动画代码生成...
