分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>唇形密封动态自紧原理 — IFR原理动画</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;700;900&family=Noto+Sans+SC:wght@300;400;500;700;900&family=IBM+Plex+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root{--bg:#060b14;--card:#0c1420;--wall:#3e4c5e;--wall-hi:#6b7d93;--piston:#2a3545;--piston-hi:#3d4f63;--seal:#0e4d40;--seal-hi:#1a7a63;--fluid:#0091b5;--fluid-hi:#00d4ff;--contact:#00ff88;--arrow:#ff6b35;--arrow-hi:#ffab40;--text:#c9d6e3;--dim:#5a6d82;--accent:#ffd600;--danger:#ff4757}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--text);font-family:'Noto Sans SC',sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;overflow:hidden;padding:12px}
.wrap{width:min(98vw,1280px);display:flex;flex-direction:column;align-items:center;gap:14px}
.title{text-align:center;opacity:0;animation:fadeSlide .8s .2s forwards}
.title h1{font-family:'Orbitron',sans-serif;font-size:clamp(.9rem,2vw,1.35rem);font-weight:700;color:var(--fluid-hi);letter-spacing:3px;text-transform:uppercase}
.title p{font-size:clamp(.7rem,1.2vw,.82rem);color:var(--dim);margin-top:4px;font-weight:300}
.svg-box{width:100%;background:var(--card);border:1px solid rgba(0,180,216,.1);border-radius:10px;overflow:hidden;box-shadow:0 0 60px rgba(0,148,181,.06),inset 0 0 80px rgba(0,0,0,.4);opacity:0;animation:fadeSlide .8s .4s forwards}
svg{width:100%;height:auto;display:block}
.ctrl{display:flex;align-items:center;gap:16px;padding:10px 22px;background:var(--card);border:1px solid rgba(0,180,216,.12);border-radius:8px;flex-wrap:wrap;justify-content:center;opacity:0;animation:fadeSlide .8s .6s forwards}
.ctrl label{font-family:'IBM Plex Mono',monospace;font-size:.72rem;color:var(--dim);white-space:nowrap}
.ctrl input[type=range]{-webkit-appearance:none;width:clamp(160px,30vw,320px);height:5px;background:linear-gradient(90deg,var(--piston),var(--fluid));border-radius:3px;outline:none;cursor:pointer}
.ctrl input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;background:var(--fluid-hi);border-radius:50%;cursor:pointer;box-shadow:0 0 10px rgba(0,212,255,.5)}
.pval{font-family:'IBM Plex Mono',monospace;font-size:.82rem;color:var(--fluid-hi);min-width:72px;text-align:right}
.btn{padding:5px 14px;font-family:'IBM Plex Mono',monospace;font-size:.7rem;background:transparent;color:var(--dim);border:1px solid var(--wall);border-radius:4px;cursor:pointer;transition:all .25s;letter-spacing:1px}
.btn.on{color:var(--fluid-hi);border-color:var(--fluid);box-shadow:0 0 10px rgba(0,212,255,.2)}
@keyframes fadeSlide{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
@media(prefers-reduced-motion:reduce){*{animation-duration:0s!important;transition-duration:0s!important}}
</style>
</head>
<body>
<div class="wrap">
  <div class="title">
    <h1>Lip Seal Self-Tightening</h1>
    <p>唇形密封动态自紧原理 — 以流体压力自身驱动密封,消解装配预紧力与高压密封的矛盾</p>
  </div>
  <div class="svg-box">
    <svg id="svg" viewBox="0 0 1100 680" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
  <div class="ctrl">
    <button class="btn on" id="btnAuto">AUTO</button>
    <label>工作压力</label>
    <input type="range" id="slider" min="0" max="100" value="0"/>
    <span class="pval" id="pval">0.0 MPa</span>
    <button class="btn" id="btnManual">MANUAL</button>
  </div>
</div>

<script>
(function(){
/* ─── 常量与状态 ─── */
const NS='http://www.w3.org/2000/svg';
const svg=document.getElementById('svg');
let pressure=0,autoMode=true,time=0,manualP=0;

/* ─── 坐标系定义 ─── */
const W=1100,H=680;
const WTY=148,WBY=508,WH=26;           // 上下壁 y 与厚度
const WIY1=WTY+WH,WIY2=WBY;            // 内腔上下 y
const WX1=75,WX2=1025;                 // 壁左右 x
const PX=625,PW=52;                     // 活塞 x 与宽度
const CX=580,CW=8;                      // 腔体右壁

/* ─── 工具函数 ─── */
function lerp(a,b,t){return a+(b-a)*clamp01(t)}
function clamp01(t){return Math.max(0,Math.min(1,t))}
function easeIO(t){return t<.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2}
function svgEl(tag,attrs){const e=document.createElementNS(NS,tag);for(const k in attrs)e.setAttribute(k,attrs[k]);return e}

/* ─── SVG 定义 ─── */
const defs=svgEl('defs',{});
defs.innerHTML=`
<linearGradient id="gWall" x1="0" y1="0" x2="0" y2="1">
  <stop offset="0%" stop-color="#6b7d93"/><stop offset="100%" stop-color="#2e3d4f"/>
</linearGradient>
<linearGradient id="gPiston" x1="0" y1="0" x2="1" y2="0">
  <stop offset="0%" stop-color="#3d4f63"/><stop offset="100%" stop-color="#1e2a38"/>
</linearGradient>
<linearGradient id="gSeal" x1="1" y1="0" x2="0" y2="0">
  <stop offset="0%" stop-color="#1a7a63"/><stop offset="60%" stop-color="#0e4d40"/><stop offset="100%" stop-color="#0a3a30"/>
</linearGradient>
<linearGradient id="gFluid" x1="0" y1="0" x2="1" y2="0">
  <stop offset="0%" stop-color="#005f7f"/><stop offset="100%" stop-color="#0091b5"/>
</linearGradient>
<filter id="fGlow"><feGaussianBlur stdDeviation="5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<filter id="fGlow2"><feGaussianBlur stdDeviation="8" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<filter id="fSoft"><feGaussianBlur stdDeviation="3"/></filter>
<pattern id="grid" width="25" height="25" patternUnits="userSpaceOnUse">
  <path d="M25 0L0 0 0 25" fill="none" stroke="rgba(0,180,216,0.03)" stroke-width=".5"/>
</pattern>
<marker id="arrO" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto">
  <polygon points="0,0 7,2.5 0,5" fill="#ff6b35"/>
</marker>
`;
svg.appendChild(defs);

/* ─── 静态层 ─── */
// 背景
svg.appendChild(svgEl('rect',{width:W,height:H,fill:'#080d16'}));
svg.appendChild(svgEl('rect',{width:W,height:H,fill:'url(#grid)'}));

// 高压区底色
const fluidBg=svgEl('rect',{x:WX1,y:WIY1,width:PX-WX1,height:WIY2-WIY1,fill:'url(#gFluid)',opacity:'0'});
svg.appendChild(fluidBg);

// 上壁
svg.appendChild(svgEl('rect',{x:WX1,y:WTY,width:WX2-WX1,height:WH,fill:'url(#gWall)',rx:2}));
// 下壁
svg.appendChild(svgEl('rect',{x:WX1,y:WBY,width:WX2-WX1,height:WH,fill:'url(#gWall)',rx:2}));
// 左端盖
svg.appendChild(svgEl('rect',{x:WX1,y:WTY,width:22,height:WBY+WH-WTY,fill:'url(#gWall)',rx:2}));
// 右端开口装饰
svg.appendChild(svgEl('rect',{x:WX2-12,y:WTY,width:12,height:WH,fill:'url(#gWall)',rx:2}));
svg.appendChild(svgEl('rect',{x:WX2-12,y:WBY,width:12,height:WH,fill:'url(#gWall)',rx:2}));

// 活塞
svg.appendChild(svgEl('rect',{x:PX,y:WIY1,width:PW,height:WIY2-WIY1,fill:'url(#gPiston)',rx:2}));
// 活塞右区 (低压侧底色)
svg.appendChild(svgEl('rect',{x:PX+PW,y:WIY1,width:WX2-PX-PW,height:WIY2-WIY1,fill:'#070c14',opacity:.7}));

/* ─── 动态层容器 ─── */
const gArrows=svgEl('g',{});svg.appendChild(gArrows);
const gParticles=svgEl('g',{});svg.appendChild(gParticles);
const gSealBody=svgEl('g',{});svg.appendChild(gSealBody);
const gCavity=svgEl('g',{});svg.appendChild(gCavity);
const gContact=svgEl('g',{});svg.appendChild(gContact);
const gLeak=svgEl('g',{});svg.appendChild(gLeak);
const gLabels=svgEl('g',{});svg.appendChild(gLabels);
const gGauge=svgEl('g',{});svg.appendChild(gGauge);
const gIFR=svgEl('g',{});svg.appendChild(gIFR);

/* ─── 唇形密封路径 ─── */
const sealPath=svgEl('path',{fill:'url(#gSeal)',stroke:'#1a7a63','stroke-width':'1'});
gSealBody.appendChild(sealPath);
// 唇口高光线
const sealHighlight=svgEl('path',{fill:'none',stroke:'rgba(0,255,136,0.15)','stroke-width':'2','stroke-linecap':'round'});
gSealBody.appendChild(sealHighlight);

function sealGeom(p){
  const e=easeIO(p);
  const bk=578,bkr=608;
  const bt=238,bb=418;
  // 上唇
  const ttx=lerp(348,320,e), tty=lerp(190,WIY1+2,e);
  const ttix=lerp(365,338,e), ttiy=lerp(208,WIY1+18,e);
  const toc1x=lerp(440,400,e), toc1y=lerp(192,WIY1+8,e);
  const tic1x=lerp(440,405,e), tic1y=lerp(225,WIY1+30,e);
  // 下唇
  const btx=lerp(348,320,e), bty=lerp(466,WIY2-2,e);
  const btix=lerp(365,338,e), btiy=lerp(448,WIY2-18,e);
  const boc1x=lerp(440,400,e), boc1y=lerp(464,WIY2-8,e);
  const bic1x=lerp(440,405,e), bic1y=lerp(431,WIY2-30,e);
  // 内腔
  const itx=548, ity=lerp(268,258,e*.4);
  const ibx=548, iby=lerp(388,398,e*.4);
  return{bk,bkr,bt,bb,ttx,tty,ttix,ttiy,toc1x,toc1y,tic1x,tic1y,btx,bty,btix,btiy,boc1x,boc1y,bic1x,bic1y,itx,ity,ibx,iby,e};
}

function buildSealPath(g){
  return`M${g.bkr},${g.bt} L${g.bk},${g.bt} Q${g.toc1x},${g.toc1y} ${g.ttx},${g.tty} L${g.ttix},${g.ttiy} Q${g.tic1x},${g.tic1y} ${g.itx},${g.ity} L${g.ibx},${g.iby} Q${g.bic1x},${g.bic1y} ${g.btix},${g.btiy} L${g.btx},${g.bty} Q${g.boc1x},${g.boc1y} ${g.bk},${g.bb} L${g.bkr},${g.bb} Z`;
}

function buildHighlightPath(g){
  // 上唇口外缘高光
  return`M${g.bk},${g.bt} Q${g.toc1x},${g.toc1y} ${g.ttx},${g.tty}`;
}

/* ─── 腔体填充 ─── */
const cavityPath=svgEl('path',{fill:'#0091b5',opacity:'0'});
gCavity.appendChild(cavityPath);

function buildCavityPath(g){
  return`M${g.ttix},${g.ttiy} Q${g.tic1x},${g.tic1y} ${g.itx},${g.ity} L${g.ibx},${g.iby} Q${g.bic1x},${g.bic1y} ${g.btix},${g.btiy} Z`;
}

/* ─── 接触发光 ─── */
const contactTop=svgEl('ellipse',{rx:35,ry:6,fill:'#00ff88',opacity:'0',filter:'url(#fGlow)'});
const contactBot=svgEl('ellipse',{rx:35,ry:6,fill:'#00ff88',opacity:'0',filter:'url(#fGlow)'});
gContact.appendChild(contactTop);gContact.appendChild(contactBot);

/* ─── 压力箭头 ─── */
const arrows=[];
for(let i=0;i<5;i++){
  const a=svgEl('line',{stroke:'#ff6b35','stroke-width':'2.5','marker-end':'url(#arrO)',opacity:'0','stroke-linecap':'round'});
  gArrows.appendChild(a);arrows.push(a);
}

/* ─── 粒子系统 ─── */
const MAXP=80;
let particles=[];
function spawnParticle(p){
  const side=Math.random()<0.7?'hp':'cav';
  let x,y,vx,vy;
  if(side==='hp'){
    x=WX1+30+Math.random()*100;
    y=WIY1+20+Math.random()*(WIY2-WIY1-40);
    vx=1.2+Math.random()*2;
    vy=(Math.random()-.5)*.3;
  }else{
    const g=sealGeom(p);
    x=lerp(g.ttix,g.itx,Math.random());
    y=lerp(g.ttiy+5,g.btiy-5,Math.random());
    vx=.3+Math.random()*.8;
    vy=(Math.random()-.5)*.4;
  }
  const r=1.2+Math.random()*2;
  const el=svgEl('circle',{r:r,fill:'#00d4ff',opacity:'0',filter:'url(#fSoft)'});
  gParticles.appendChild(el);
  return{el,x,y,vx,vy,r,life:0,maxLife:120+Math.random()*100,side};
}

/* ─── 泄漏粒子 ─── */
let leaks=[];
function spawnLeak(p){
  const g=sealGeom(p);
  // 从密封唇口与壁面的间隙泄漏
  const fromTop=Math.random()<.5;
  const tipY=fromTop?g.tty:g.bty;
  const tipX=fromTop?g.ttx:g.btx;
  const el=svgEl('circle',{r:1.5,fill:'var(--danger)',opacity:'0'});
  gLeak.appendChild(el);
  return{el,x:tipX+10,y:tipY,vx:1.5+Math.random(),vy:(Math.random()-.5)*.5,life:0,maxLife:80};
}

/* ─── 标注 ─── */
function makeLabel(x,y,text,fontSize,color,anchor){
  const t=svgEl('text',{x,y,fill:color||'var(--text)','font-size':fontSize||13,'font-family':"'Noto Sans SC',sans-serif",'text-anchor':anchor||'start','font-weight':'400'});
  t.textContent=text;return t;
}
function makeMonoLabel(x,y,text,fontSize,color,anchor){
  const t=svgEl('text',{x,y,fill:color||'var(--dim)','font-size':fontSize||11,'font-family':"'IBM Plex Mono',monospace",'text-anchor':anchor||'start','font-weight':'400'});
  t.textContent=text;return t;
}

// 静态标注
gLabels.appendChild(makeLabel(WX1+40,WTY-10,'缸体壁','#5a6d82',12));
gLabels.appendChild(makeLabel(PX+8,WIY1+22,'活塞','var(--dim)',12));
gLabels.appendChild(makeLabel(WX1+45,WIY1+35,'高压流体区','#0091b5',13));
gLabels.appendChild(makeLabel(PX+PW+15,WIY1+35,'低压侧','#3a4a5c',12));

// 密封圈标注 + 引线
const sealLabel=makeLabel(290,220,'U型唇形密封圈','#1a7a63',13);
gLabels.appendChild(sealLabel);
const sealLeader=svgEl('line',{x1:340,y1:224,x2:370,y2:240,stroke:'#1a7a63','stroke-width':'.8','stroke-dasharray':'3,3'});
gLabels.appendChild(sealLeader);

// 动态标注
const phaseLabel=makeLabel(W/2,H-30,'','14px','var(--text)','middle');
gLabels.appendChild(phaseLabel);

const detailLabel1=makeMonoLabel(0,0,'唇口过盈 0.05mm(仅定位)','10px','var(--dim)');
const detailLabel2=makeMonoLabel(0,0,'唇口张开角 15°~20°','10px','var(--dim)');
gLabels.appendChild(detailLabel1);gLabels.appendChild(detailLabel2);

/* ─── 压力表 ─── */
const gcx=95,gcy=590,gr=48;
gGauge.appendChild(svgEl('circle',{cx:gcx,cy:gcy,r:gr+4,fill:'none',stroke:'#1e2a38','stroke-width':'2'}));
gGauge.appendChild(svgEl('circle',{cx:gcx,cy:gcy,r:gr,fill:'#0a0e17',stroke:'#2a3545','stroke-width':'1.5'}));
// 刻度
for(let i=0;i<=10;i++){
  const a=Math.PI+i*Math.PI/10;
  const x1=gcx+Math.cos(a)*(gr-6),y1=gcy+Math.sin(a)*(gr-6);
  const x2=gcx+Math.cos(a)*(gr-14),y2=gcy+Math.sin(a)*(gr-14);
  gGauge.appendChild(svgEl('line',{x1,y1,x2,y2,stroke:'#3a4a5c','stroke-width':i%5===0?'2':'1'}));
}
const gaugeNeedle=svgEl('line',{x1:gcx,y1:gcy,x2:gcx,y2:gcy-gr+18,stroke:'#ff6b35','stroke-width':'2.5','stroke-linecap':'round'});
gGauge.appendChild(gaugeNeedle);
gGauge.appendChild(svgEl('circle',{cx:gcx,cy:gcy,r:4,fill:'#ff6b35'}));
const gaugeLabel=makeMonoLabel(gcx,gcy+18,'0.0 MPa','10px','#ff6b35','middle');
gGauge.appendChild(gaugeLabel);
gGauge.appendChild(makeMonoLabel(gcx,gcy-gr-10,'PRESSURE','9px','#3a4a5c','middle'));

/* ─── IFR 原理揭示 ─── */
const ifrBox=svgEl('rect',{x:720,y:555,width:330,height:90,rx:6,fill:'rgba(0,255,136,0.04)',stroke:'rgba(0,255,136,0.2)','stroke-width':'1',opacity:'0'});
gIFR.appendChild(ifrBox);
const ifrLine1=makeLabel(885,582,'F密封 = P流体 × A唇口','13px','var(--contact)','middle');
const ifrLine2=makeLabel(885,604,'装配零阻力 · 工作高密封','12px','var(--accent)','middle');
const ifrLine3=makeMonoLabel(885,624,'— 最终理想解 (IFR) —','10px','#3a6b55','middle');
gIFR.appendChild(ifrLine1);gIFR.appendChild(ifrLine2);gIFR.appendChild(ifrLine3);

/* ─── 动画主循环 ─── */
let lastT=0;
function animate(ts){
  const dt=Math.min(ts-lastT,50);lastT=ts;

  // 压力更新
  if(autoMode){
    time+=dt*0.0008;
    pressure=(Math.sin(time-Math.PI/2)+1)/2;
    document.getElementById('slider').value=pressure*100;
  }else{
    pressure=manualP;
  }
  const p=pressure;
  const ep=easeIO(p);

  // 唇形密封
  const g=sealGeom(p);
  sealPath.setAttribute('d',buildSealPath(g));
  sealHighlight.setAttribute('d',buildHighlightPath(g));
  sealHighlight.setAttribute('stroke-opacity',.1+ep*.25);

  // 腔体填充
  cavityPath.setAttribute('d',buildCavityPath(g));
  cavityPath.setAttribute('opacity',ep*.7);

  // 高压区底色
  fluidBg.setAttribute('opacity',ep*.35);

  // 接触发光
  const topDist=Math.abs(g.tty-WIY1);
  const botDist=Math.abs(g.bty-WIY2);
  const topContact=clamp01(1-topDist/30);
  const botContact=clamp01(1-botDist/30);
  const pulse=.7+Math.sin(ts*.005)*.3;
  contactTop.setAttribute('cx',g.ttx+20);contactTop.setAttribute('cy',WIY1+2);
  contactTop.setAttribute('opacity',topContact*pulse*.8);
  contactTop.setAttribute('rx',25+topContact*15);
  contactBot.setAttribute('cx',g.btx+20);contactBot.setAttribute('cy',WIY2-2);
  contactBot.setAttribute('opacity',botContact*pulse*.8);
  contactBot.setAttribute('rx',25+botContact*15);

  // 压力箭头
  const arrowPositions=[.2,.35,.5,.65,.8];
  for(let i=0;i<arrows.length;i++){
    const frac=arrowPositions[i];
    const ay=WIY1+frac*(WIY2-WIY1);
    const ax1=WX1+40+ep*30;
    const ax2=ax1+40+ep*60;
    arrows[i].setAttribute('x1',ax1);arrows[i].setAttribute('y1',ay);
    arrows[i].setAttribute('x2',ax2);arrows[i].setAttribute('y2',ay);
    arrows[i].setAttribute('opacity',ep*.7);
    arrows[i].setAttribute('stroke-width',1.5+ep*1.5);
  }

  // 粒子
  const targetCount=Math.floor(ep*MAXP);
  while(particles.length<targetCount)particles.push(spawnParticle(p));
  while(particles.length>targetCount&&particles.length>0){
    const rem=particles.pop();rem.el.remove();
  }
  for(let i=particles.length-1;i>=0;i--){
    const pt=particles[i];
    pt.x+=pt.vx*(0.5+ep*1.5);
    pt.y+=pt.vy;
    pt.life++;
    const lifeRatio=pt.life/pt.maxLife;
    const alpha=lifeRatio<.1?lifeRatio*10:lifeRatio>.8?(1-lifeRatio)*5:1;
    // 高压区粒子碰到密封面反弹或消失
    if(pt.side==='hp'&&pt.x>g.ttx-10){
      pt.vx*=-0.3;pt.x=g.ttx-10;
    }
    if(pt.x>WX2||pt.x<WX1||pt.y<WIY1||pt.y>WIY2||pt.life>pt.maxLife){
      particles.splice(i,1);pt.el.remove();continue;
    }
    pt.el.setAttribute('cx',pt.x);pt.el.setAttribute('cy',pt.y);
    pt.el.setAttribute('opacity',alpha*ep*.6);
  }

  // 泄漏粒子 (低压时出现)
  if(p<0.4&&Math.random()<0.03*(1-p)){
    if(leaks.length<15)leaks.push(spawnLeak(p));
  }
  for(let i=leaks.length-1;i>=0;i--){
    const lk=leaks[i];
    lk.x+=lk.vx;lk.y+=lk.vy;lk.life++;
    const a=lk.life<10?lk.life/10:lk.life>lk.maxLife-20?(lk.maxLife-lk.life)/20:1;
    if(lk.life>lk.maxLife||lk.x>PX+PW+60){
      leaks.splice(i,1);lk.el.remove();continue;
    }
    lk.el.setAttribute('cx',lk.x);lk.el.setAttribute('cy',lk.y);
    lk.el.setAttribute('opacity',a*(1-ep)*.7);
  }

  // 动态标注
  detailLabel1.setAttribute('x',g.ttx-60);detailLabel1.setAttribute('y',g.tty-15);
  detailLabel2.setAttribute('x',g.btx-60);detailLabel2.setAttribute('y',g.bty+22);
  const detailAlpha=p<0.15?1:clamp01(1-(p-.15)*5);
  detailLabel1.setAttribute('opacity',detailAlpha);
  detailLabel2.setAttribute('opacity',detailAlpha);

  // 阶段标注
  if(p<0.15){
    phaseLabel.textContent='无压状态 · 极小过盈 · 轻松装配';
    phaseLabel.setAttribute('fill','#5a9e8f');
  }else if(p<0.6){
    phaseLabel.textContent='流体加压 · 压力驱动唇口张开';
    phaseLabel.setAttribute('fill','#0091b5');
  }else{
    phaseLabel.textContent='压力越高 · 密封越紧 · 自紧完成';
    phaseLabel.setAttribute('fill','#00ff88');
  }

  // 压力表
  const needleAngle=Math.PI+ep*Math.PI;
  const nx=gcx+Math.cos(needleAngle)*(gr-18);
  const ny=gcy+Math.sin(needleAngle)*(gr-18);
  gaugeNeedle.setAttribute('x2',nx);gaugeNeedle.setAttribute('y2',ny);
  gaugeLabel.textContent=(ep*25).toFixed(1)+' MPa';

  // IFR 揭示
  const ifrAlpha=clamp01((p-.55)*4);
  ifrBox.setAttribute('opacity',ifrAlpha);
  ifrLine1.setAttribute('opacity',ifrAlpha);
  ifrLine2.setAttribute('opacity',ifrAlpha);
  ifrLine3.setAttribute('opacity',ifrAlpha);

  // 压力数值
  document.getElementById('pval').textContent=(ep*25).toFixed(1)+' MPa';

  requestAnimationFrame(animate);
}

/* ─── 控件 ─── */
const slider=document.getElementById('slider');
const btnAuto=document.getElementById('btnAuto');
const btnManual=document.getElementById('btnManual');

slider.addEventListener('input',function(){
  if(!autoMode)manualP=this.value/100;
});

btnAuto.addEventListener('click',function(){
  autoMode=true;btnAuto.classList.add('on');btnManual.classList.remove('on');
});
btnManual.addEventListener('click',function(){
  autoMode=false;manualP=slider.value/100;
  btnManual.classList.add('on');btnAuto.classList.remove('on');
});

/* ─── 初始化 ─── */
// 初始压力设置到 0,自动循环从 0 开始
time=0;
requestAnimationFrame(animate);

})();
</script>
</body>
</html>

这个动画实现了唇形密封动态自紧原理的完整可视化,核心设计思路如下:

视觉叙事聚焦 IFR(最终理想解)

  • 动画直接展示密封圈在流体压力驱动下从松弛到自紧的全过程,不设"问题对照"画面
  • 当压力超过阈值,右下角浮现 IFR 公式 F密封 = P流体 × A唇口 与核心矛盾消解宣言"装配零阻力·工作高密封",强化理想解认知

资源利用的视觉强调

  • 流体粒子从左向右涌入,部分进入 U 型腔体——明确展示"压力本身就是密封力的来源"
  • 腔体填充透明度随压力同步增长,唇口张开幅度与压力正相关,直观呈现"压力越高→密封越紧"的自适应机制
  • 低压时出现红色泄漏粒子,高压时泄漏消失——用正反对比突出资源利用的有效性

交互与自动播放

  • 页面加载即自动循环(正弦压力曲线 0→峰值→0)
  • 提供手动/自动切换与压力滑块,用户可任意拖动体验不同压力下唇口响应
  • 半圆压力表实时跟踪当前压力值

动态反馈层次

  • 接触面绿色脉冲光晕(唇口贴合缸壁时亮起)
  • 橙色压力箭头随压力增粗变长
  • 低压标注"极小过盈 0.05mm"在升压后自然淡出,阶段文案自动切换
积分规则:第一轮对话扣减8分,后续每轮扣6分