独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PE手套微负压腔吸附原理</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;700;900&family=Oxanium:wght@400;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
:root {
--bg: #060b18;
--bg2: #0d1425;
--card: #111b2e;
--fg: #e8e0d4;
--muted: #5e7291;
--accent: #00e5ff;
--accent-glow: rgba(0,229,255,0.25);
--glove: #d4a847;
--glove-fill: rgba(212,168,71,0.55);
--glass: #7fb8d8;
--skin: #e8b89d;
--danger: #ff6b6b;
--success: #4ade80;
--border: rgba(94,114,145,0.2);
}
*{margin:0;padding:0;box-sizing:border-box;}
html,body{height:100%;overflow:hidden;}
body{
background:var(--bg);
color:var(--fg);
font-family:'Noto Sans SC',sans-serif;
display:flex;flex-direction:column;
}
/* 背景网格 */
body::before{
content:'';position:fixed;inset:0;z-index:0;pointer-events:none;
background-image:
linear-gradient(rgba(0,229,255,0.03) 1px,transparent 1px),
linear-gradient(90deg,rgba(0,229,255,0.03) 1px,transparent 1px);
background-size:60px 60px;
}
header{
position:relative;z-index:1;
padding:18px 32px 10px;
display:flex;align-items:baseline;gap:18px;
border-bottom:1px solid var(--border);
background:linear-gradient(180deg,rgba(13,20,37,0.95),rgba(6,11,24,0.85));
backdrop-filter:blur(10px);
}
header h1{
font-family:'Oxanium',sans-serif;
font-weight:800;font-size:22px;letter-spacing:1px;
color:var(--accent);
text-shadow:0 0 20px var(--accent-glow);
}
header p{
font-size:13px;color:var(--muted);font-weight:300;
}
main{
flex:1;display:flex;position:relative;z-index:1;
overflow:hidden;
}
/* 左侧主动画区 */
.main-view{
flex:1;display:flex;align-items:center;justify-content:center;
position:relative;padding:12px;
}
.main-view svg{width:100%;height:100%;max-height:calc(100vh - 80px);}
/* 右侧面板 */
.side-panel{
width:320px;min-width:280px;
border-left:1px solid var(--border);
background:var(--bg2);
display:flex;flex-direction:column;
overflow-y:auto;
scrollbar-width:thin;scrollbar-color:var(--muted) transparent;
}
.panel-section{
padding:18px 20px;
border-bottom:1px solid var(--border);
}
.panel-section:last-child{border-bottom:none;}
.panel-title{
font-family:'Oxanium',sans-serif;
font-size:11px;font-weight:700;
text-transform:uppercase;letter-spacing:2px;
color:var(--muted);margin-bottom:12px;
}
/* 六边形阵列小图 */
.hex-view{width:100%;aspect-ratio:1;border-radius:8px;overflow:hidden;
background:rgba(0,0,0,0.3);border:1px solid var(--border);}
.hex-view svg{width:100%;height:100%;}
/* 参数列表 */
.param-list{list-style:none;}
.param-list li{
display:flex;justify-content:space-between;align-items:center;
padding:7px 0;border-bottom:1px solid rgba(94,114,145,0.1);
font-size:13px;
}
.param-list li:last-child{border-bottom:none;}
.param-label{color:var(--muted);}
.param-value{
font-family:'Oxanium',sans-serif;font-weight:600;
color:var(--fg);font-size:13px;
}
.param-value.accent{color:var(--accent);}
/* 阶段指示器 */
.phase-list{list-style:none;display:flex;flex-direction:column;gap:6px;}
.phase-item{
display:flex;align-items:center;gap:10px;
padding:8px 12px;border-radius:8px;
font-size:13px;color:var(--muted);
transition:all 0.4s ease;
background:transparent;
}
.phase-item.active{
background:rgba(0,229,255,0.08);
color:var(--fg);
box-shadow:inset 0 0 0 1px rgba(0,229,255,0.2);
}
.phase-dot{
width:8px;height:8px;border-radius:50%;
background:var(--muted);flex-shrink:0;
transition:all 0.4s ease;
}
.phase-item.active .phase-dot{
background:var(--accent);
box-shadow:0 0 8px var(--accent);
}
/* 控制区 */
.control-row{
display:flex;align-items:center;gap:10px;margin-bottom:12px;
}
.control-row:last-child{margin-bottom:0;}
.btn{
display:inline-flex;align-items:center;justify-content:center;
width:36px;height:36px;border-radius:8px;border:1px solid var(--border);
background:var(--card);color:var(--fg);cursor:pointer;
transition:all 0.2s;font-size:14px;
}
.btn:hover{border-color:var(--accent);color:var(--accent);}
.btn.active{background:rgba(0,229,255,0.12);border-color:var(--accent);color:var(--accent);}
.slider-wrap{flex:1;display:flex;flex-direction:column;gap:4px;}
.slider-label{font-size:11px;color:var(--muted);display:flex;justify-content:space-between;}
.slider-label span:last-child{font-family:'Oxanium',sans-serif;color:var(--fg);}
input[type=range]{
-webkit-appearance:none;width:100%;height:6px;
border-radius:3px;background:var(--card);outline:none;
border:1px solid var(--border);
}
input[type=range]::-webkit-slider-thumb{
-webkit-appearance:none;width:16px;height:16px;
border-radius:50%;background:var(--accent);cursor:pointer;
box-shadow:0 0 8px var(--accent-glow);
}
/* IFR 卡片 */
.ifr-card{
background:linear-gradient(135deg,rgba(0,229,255,0.06),rgba(0,229,255,0.02));
border:1px solid rgba(0,229,255,0.15);
border-radius:10px;padding:14px;
}
.ifr-card .ifr-tag{
font-family:'Oxanium',sans-serif;font-size:10px;font-weight:700;
text-transform:uppercase;letter-spacing:2px;
color:var(--accent);margin-bottom:8px;
}
.ifr-card .ifr-text{
font-size:13px;line-height:1.7;color:var(--fg);font-weight:300;
}
.ifr-card .ifr-text strong{color:var(--accent);font-weight:700;}
/* SVG 内文字样式 */
.svg-label{
font-family:'Oxanium',sans-serif;fill:var(--muted);font-size:11px;
}
.svg-label-bright{
font-family:'Oxanium',sans-serif;fill:var(--accent);font-size:12px;font-weight:700;
}
.svg-dim-line{
stroke:var(--muted);stroke-width:0.8;stroke-dasharray:4 3;
fill:none;
}
.svg-dim-text{
font-family:'Oxanium',sans-serif;fill:var(--fg);font-size:10px;
}
/* 动画 - 呼吸效果 */
@keyframes pulse-glow{
0%,100%{opacity:0.5;}
50%{opacity:1;}
}
@keyframes float-particle{
0%,100%{transform:translateY(0);}
50%{transform:translateY(-3px);}
}
/* 响应式 */
@media(max-width:900px){
.side-panel{width:260px;min-width:220px;}
}
@media(max-width:700px){
main{flex-direction:column;}
.side-panel{width:100%;max-height:40vh;border-left:none;border-top:1px solid var(--border);}
}
</style>
</head>
<body>
<header>
<h1>MICRO-SUCTION PE GLOVE</h1>
<p>微负压腔防滑原理 — 化"薄"为利,主动吸附</p>
</header>
<main>
<div class="main-view">
<svg id="mainSvg" viewBox="0 0 960 620" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 皮肤渐变 -->
<linearGradient id="skinGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f0c8a8"/>
<stop offset="100%" stop-color="#d4a080"/>
</linearGradient>
<!-- 手套渐变 -->
<linearGradient id="gloveGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(212,168,71,0.7)"/>
<stop offset="100%" stop-color="rgba(180,140,50,0.85)"/>
</linearGradient>
<!-- 玻璃渐变 -->
<linearGradient id="glassGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#9ccce6"/>
<stop offset="40%" stop-color="#7fb8d8"/>
<stop offset="100%" stop-color="#5a9ab8"/>
</linearGradient>
<!-- 负压发光 -->
<radialGradient id="vacuumGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="rgba(0,229,255,0.5)"/>
<stop offset="70%" stop-color="rgba(0,229,255,0.15)"/>
<stop offset="100%" stop-color="rgba(0,229,255,0)"/>
</radialGradient>
<!-- 空气颜色 -->
<radialGradient id="airGrad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="rgba(136,204,255,0.5)"/>
<stop offset="100%" stop-color="rgba(136,204,255,0.1)"/>
</radialGradient>
<!-- 发光滤镜 -->
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowStrong" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- 箭头标记 -->
<marker id="arrowDown" markerWidth="8" markerHeight="8" refX="4" refY="8" orient="auto">
<path d="M1,0 L4,8 L7,0" fill="var(--danger)" stroke="none"/>
</marker>
<marker id="arrowUp" markerWidth="8" markerHeight="8" refX="4" refY="0" orient="auto">
<path d="M1,8 L4,0 L7,8" fill="var(--accent)" stroke="none"/>
</marker>
<marker id="arrowRight" markerWidth="8" markerHeight="8" refX="8" refY="4" orient="auto">
<path d="M0,1 L8,4 L0,7" fill="var(--muted)" stroke="none"/>
</marker>
</defs>
<!-- 背景装饰 -->
<rect x="0" y="0" width="960" height="620" fill="none"/>
<!-- 玻璃表面 (固定) -->
<g id="glassGroup">
<rect x="80" y="490" width="800" height="100" rx="2" fill="url(#glassGrad)" opacity="0.9"/>
<!-- 玻璃高光 -->
<line x1="100" y1="492" x2="860" y2="492" stroke="rgba(255,255,255,0.4)" stroke-width="1"/>
<line x1="100" y1="495" x2="700" y2="495" stroke="rgba(255,255,255,0.15)" stroke-width="0.5"/>
<!-- 玻璃标签 -->
<text x="480" y="555" text-anchor="middle" font-family="'Oxanium',sans-serif" font-size="13" fill="rgba(255,255,255,0.6)" font-weight="600">SMOOTH SURFACE / 光滑表面</text>
</g>
<!-- 手指+手套 组 (整体移动) -->
<g id="assemblyGroup">
<!-- 手指截面 -->
<g id="fingerGroup">
<path id="fingerPath" d="M200,300 Q200,100 480,80 Q760,100 760,300 Z" fill="url(#skinGrad)" stroke="rgba(180,130,100,0.3)" stroke-width="1"/>
<!-- 手指纹理线 -->
<path d="M280,200 Q480,185 680,200" fill="none" stroke="rgba(180,130,100,0.15)" stroke-width="0.8"/>
<path d="M300,230 Q480,218 660,230" fill="none" stroke="rgba(180,130,100,0.12)" stroke-width="0.8"/>
<path d="M320,260 Q480,250 640,260" fill="none" stroke="rgba(180,130,100,0.1)" stroke-width="0.8"/>
<!-- 手指标签 -->
<text x="480" y="190" text-anchor="middle" font-family="'Oxanium',sans-serif" font-size="14" fill="rgba(100,70,50,0.6)" font-weight="700">FINGER</text>
</g>
<!-- PE手套层 -->
<g id="gloveGroup">
<!-- 手套主体 (非凹坑区域) -->
<path id="gloveBody" d="" fill="url(#gloveGrad)" stroke="rgba(212,168,71,0.4)" stroke-width="0.8"/>
<!-- 凹坑组 -->
<g id="pitsGroup"></g>
<!-- 薄膜组 -->
<g id="filmsGroup"></g>
</g>
<!-- 空气粒子组 -->
<g id="airParticlesGroup"></g>
<!-- 负压效果组 -->
<g id="vacuumGroup"></g>
</g>
<!-- 力箭头组 -->
<g id="forceArrowsGroup"></g>
<!-- 尺寸标注组 -->
<g id="dimensionsGroup"></g>
<!-- 关键提示 -->
<g id="calloutGroup" opacity="0">
<rect x="620" y="200" width="280" height="70" rx="8" fill="rgba(0,229,255,0.08)" stroke="rgba(0,229,255,0.3)" stroke-width="1"/>
<text x="635" y="224" font-family="'Oxanium',sans-serif" font-size="11" fill="var(--accent)" font-weight="700" letter-spacing="1">VACUUM FORMED</text>
<text x="635" y="244" font-family="'Noto Sans SC',sans-serif" font-size="13" fill="var(--fg)" font-weight="400">薄膜顺应表面 → 密封 → 微负压腔</text>
<text x="635" y="261" font-family="'Noto Sans SC',sans-serif" font-size="12" fill="var(--muted)" font-weight="300">化被动防滑为主动吸附</text>
<!-- 连接线 -->
<line id="calloutLine" x1="620" y1="250" x2="550" y2="380" stroke="rgba(0,229,255,0.4)" stroke-width="1" stroke-dasharray="4 3"/>
</g>
</svg>
</div>
<aside class="side-panel">
<!-- 六边形阵列俯视图 -->
<div class="panel-section">
<div class="panel-title">Hexagonal Micro-Pit Array</div>
<div class="hex-view">
<svg id="hexSvg" viewBox="0 0 280 280"></svg>
</div>
</div>
<!-- 关键参数 -->
<div class="panel-section">
<div class="panel-title">Key Parameters</div>
<ul class="param-list">
<li><span class="param-label">凹坑直径</span><span class="param-value">1.5 mm</span></li>
<li><span class="param-label">凹坑深度</span><span class="param-value">0.15 mm</span></li>
<li><span class="param-label">凹坑区壁厚</span><span class="param-value accent">0.025 mm</span></li>
<li><span class="param-label">常规区壁厚</span><span class="param-value">0.04 mm</span></li>
<li><span class="param-label">凹坑形状</span><span class="param-value">六边形</span></li>
<li><span class="param-label">中指长度(小号)</span><span class="param-value">75 mm</span></li>
</ul>
</div>
<!-- 动画阶段 -->
<div class="panel-section">
<div class="panel-title">Animation Phase</div>
<ul class="phase-list" id="phaseList">
<li class="phase-item" data-phase="0"><span class="phase-dot"></span>接触光滑表面</li>
<li class="phase-item" data-phase="1"><span class="phase-dot"></span>施加按压力</li>
<li class="phase-item" data-phase="2"><span class="phase-dot"></span>薄膜变形排气</li>
<li class="phase-item" data-phase="3"><span class="phase-dot"></span>微负压吸附</li>
<li class="phase-item" data-phase="4"><span class="phase-dot"></span>破真空释放</li>
</ul>
<!-- 进度条 -->
<div style="margin-top:12px;height:3px;background:var(--card);border-radius:2px;overflow:hidden;">
<div id="progressBar" style="height:100%;width:0%;background:var(--accent);transition:width 0.1s;border-radius:2px;"></div>
</div>
</div>
<!-- 控制 -->
<div class="panel-section">
<div class="panel-title">Controls</div>
<div class="control-row">
<button class="btn active" id="btnPlay" title="播放/暂停"><i class="fas fa-pause"></i></button>
<button class="btn" id="btnReset" title="重置"><i class="fas fa-redo"></i></button>
<button class="btn" id="btnManual" title="手动模式"><i class="fas fa-sliders-h"></i></button>
</div>
<div class="slider-wrap" id="manualSliderWrap" style="display:none;">
<div class="slider-label"><span>按压力度</span><span id="pressureVal">0%</span></div>
<input type="range" id="pressureSlider" min="0" max="100" value="0">
</div>
<div class="slider-wrap">
<div class="slider-label"><span>播放速度</span><span id="speedVal">1.0x</span></div>
<input type="range" id="speedSlider" min="20" max="200" value="100">
</div>
</div>
<!-- IFR 卡片 -->
<div class="panel-section">
<div class="ifr-card">
<div class="ifr-tag">Ideal Final Result</div>
<div class="ifr-text">
手套<strong>自行吸附</strong>光滑表面,无需增加任何外部机构——将PE薄膜"软且薄"的缺陷<strong>转化为</strong>能顺应表面产生密封的优势,化被动防滑为<strong>主动吸附</strong>。
</div>
</div>
</div>
</aside>
</main>
<script>
(function(){
/* ====== 配置 ====== */
const NS = 'http://www.w3.org/2000/svg';
const PIT_COUNT = 5;
const PIT_CENTERS = [220, 340, 460, 580, 700]; // 凹坑中心X
const PIT_HALF_TOP = 38; // 凹坑口半宽
const PIT_HALF_BOT = 22; // 凹坑底半宽
const PIT_DEPTH = 55; // 凹坑深度
const GLOVE_Y = 300; // 手套顶面Y(非凹坑区域底面)
const GLOVE_THICK = 42; // 手套厚度(非凹坑区)
const GLASS_Y = 490; // 玻璃表面Y
const MAX_PRESS_OFFSET = 100; // 最大下压位移
const PARTICLES_PER_PIT = 7;
/* ====== 状态 ====== */
let progress = 0; // 0~1 循环进度
let playing = true;
let manualMode = false;
let speed = 1.0;
let lastTime = 0;
/* ====== 工具函数 ====== */
function easeInOut(t){ return t<0.5?2*t*t:1-Math.pow(-2*t+2,2)/2; }
function easeOut(t){ return 1-Math.pow(1-t,3); }
function lerp(a,b,t){ return a+(b-a)*t; }
function clamp(v,mn,mx){ return Math.max(mn,Math.min(mx,v)); }
/* 根据全局进度计算各阶段局部进度 */
function getPhase(p){
// 阶段边界
const bounds = [0, 0.10, 0.28, 0.52, 0.74, 1.0];
const names = ['接触','按压','排气密封','负压吸附','释放'];
for(let i=0;i<5;i++){
if(p >= bounds[i] && p < bounds[i+1]){
return {phase:i, name:names[i], local:clamp((p-bounds[i])/(bounds[i+1]-bounds[i]),0,1)};
}
}
return {phase:4, name:'释放', local:1};
}
/* 计算下压位移 */
function getPressOffset(phaseInfo){
const {phase, local} = phaseInfo;
if(phase===0) return lerp(0, MAX_PRESS_OFFSET*0.6, easeInOut(local));
if(phase===1) return lerp(MAX_PRESS_OFFSET*0.6, MAX_PRESS_OFFSET, easeInOut(local));
if(phase===2) return MAX_PRESS_OFFSET;
if(phase===3) return MAX_PRESS_OFFSET;
return lerp(MAX_PRESS_OFFSET, 0, easeInOut(local));
}
/* 计算凹坑变形量 0=未变形 1=完全贴合 */
function getDeformation(phaseInfo){
const {phase, local} = phaseInfo;
if(phase<=1) return 0;
if(phase===2) return easeOut(local);
if(phase===3) return 1;
return lerp(1, 0, easeInOut(local));
}
/* 计算负压强度 0~1 */
function getVacuumIntensity(phaseInfo){
const {phase, local} = phaseInfo;
if(phase<=1) return 0;
if(phase===2) return easeOut(Math.max(0, local-0.3)/0.7);
if(phase===3){
// 脉冲效果
return 0.7 + 0.3*Math.sin(local*Math.PI*3);
}
return lerp(0.7, 0, easeInOut(local));
}
/* 计算排气程度 0~1 */
function getAirExpel(phaseInfo){
const {phase, local} = phaseInfo;
if(phase<=0) return 0;
if(phase===1) return easeInOut(local)*0.5;
if(phase===2) return lerp(0.5, 1, easeOut(local));
if(phase===3) return 1;
return lerp(1, 0, easeInOut(local));
}
/* ====== 创建SVG元素 ====== */
const mainSvg = document.getElementById('mainSvg');
const assemblyGroup = document.getElementById('assemblyGroup');
const pitsGroup = document.getElementById('pitsGroup');
const filmsGroup = document.getElementById('filmsGroup');
const airParticlesGroup = document.getElementById('airParticlesGroup');
const vacuumGroup = document.getElementById('vacuumGroup');
const forceArrowsGroup = document.getElementById('forceArrowsGroup');
const dimensionsGroup = document.getElementById('dimensionsGroup');
const calloutGroup = document.getElementById('calloutGroup');
/* 创建凹坑区域 */
const pitData = [];
for(let i=0;i<PIT_COUNT;i++){
const cx = PIT_CENTERS[i];
// 凹坑填充(空气/负压)
const pitFill = document.createElementNS(NS,'path');
pitFill.setAttribute('fill','rgba(136,204,255,0.25)');
pitFill.setAttribute('stroke','none');
pitsGroup.appendChild(pitFill);
// 凹坑壁
const pitWall = document.createElementNS(NS,'path');
pitWall.setAttribute('fill','none');
pitWall.setAttribute('stroke','rgba(212,168,71,0.6)');
pitWall.setAttribute('stroke-width','1.2');
pitsGroup.appendChild(pitWall);
// 薄膜底线(加粗高亮)
const filmLine = document.createElementNS(NS,'path');
filmLine.setAttribute('fill','none');
filmLine.setAttribute('stroke','#d4a847');
filmLine.setAttribute('stroke-width','2.5');
filmLine.setAttribute('stroke-linecap','round');
filmsGroup.appendChild(filmLine);
// 负压发光
const vacGlow = document.createElementNS(NS,'ellipse');
vacGlow.setAttribute('fill','url(#vacuumGlow)');
vacGlow.setAttribute('opacity','0');
vacuumGroup.appendChild(vacGlow);
// 负压小箭头(向上吸力)
const suctionArrows = [];
for(let j=0;j<3;j++){
const arrow = document.createElementNS(NS,'line');
arrow.setAttribute('stroke','var(--accent)');
arrow.setAttribute('stroke-width','1.5');
arrow.setAttribute('marker-end','url(#arrowUp)');
arrow.setAttribute('opacity','0');
vacuumGroup.appendChild(arrow);
suctionArrows.push(arrow);
}
// 空气粒子
const particles = [];
for(let j=0;j<PARTICLES_PER_PIT;j++){
const p = document.createElementNS(NS,'circle');
p.setAttribute('r','2.5');
p.setAttribute('fill','rgba(136,204,255,0.7)');
p.setAttribute('opacity','0.8');
// 初始位置:凹坑内部随机
const angle = (j/PARTICLES_PER_PIT)*Math.PI*2 + Math.random()*0.5;
const radius = 8 + Math.random()*12;
p._baseX = cx + Math.cos(angle)*radius;
p._baseY = GLOVE_Y + GLOVE_THICK + PIT_DEPTH*0.4 + Math.sin(angle)*radius*0.5;
p._offsetX = (Math.random()-0.5)*20;
p._offsetY = -30 - Math.random()*50;
p._phase = Math.random()*Math.PI*2;
airParticlesGroup.appendChild(p);
particles.push(p);
}
pitData.push({cx, pitFill, pitWall, filmLine, vacGlow, suctionArrows, particles});
}
/* 手套主体路径(含凹坑轮廓) */
const gloveBody = document.getElementById('gloveBody');
/* 力箭头 */
const pressArrow = document.createElementNS(NS,'line');
pressArrow.setAttribute('stroke','var(--danger)');
pressArrow.setAttribute('stroke-width','2.5');
pressArrow.setAttribute('marker-end','url(#arrowDown)');
pressArrow.setAttribute('opacity','0');
forceArrowsGroup.appendChild(pressArrow);
const pressLabel = document.createElementNS(NS,'text');
pressLabel.setAttribute('font-family','"Oxanium",sans-serif');
pressLabel.setAttribute('font-size','11');
pressLabel.setAttribute('fill','var(--danger)');
pressLabel.setAttribute('font-weight','600');
pressLabel.setAttribute('opacity','0');
forceArrowsGroup.appendChild(pressLabel);
// 侧滑阻力箭头
const slideArrow = document.createElementNS(NS,'line');
slideArrow.setAttribute('stroke','var(--muted)');
slideArrow.setAttribute('stroke-width','2');
slideArrow.setAttribute('marker-end','url(#arrowRight)');
slideArrow.setAttribute('opacity','0');
slideArrow.setAttribute('stroke-dasharray','6 3');
forceArrowsGroup.appendChild(slideArrow);
const resistArrow = document.createElementNS(NS,'line');
resistArrow.setAttribute('stroke','var(--accent)');
resistArrow.setAttribute('stroke-width','2.5');
resistArrow.setAttribute('marker-end','url(#arrowRight)'); // 会被动态改方向
resistArrow.setAttribute('opacity','0');
forceArrowsGroup.appendChild(resistArrow);
/* 尺寸标注 */
function createDimLine(x1,y1,x2,y2,text,side){
const g = document.createElementNS(NS,'g');
g.setAttribute('opacity','0.7');
const line = document.createElementNS(NS,'line');
line.setAttribute('x1',x1);line.setAttribute('y1',y1);
line.setAttribute('x2',x2);line.setAttribute('y2',y2);
line.setAttribute('class','svg-dim-line');
g.appendChild(line);
// 端点
[x1,x2].forEach((x,i)=>{
const tick = document.createElementNS(NS,'line');
const yy = i===0?y1:y2;
tick.setAttribute('x1',x);tick.setAttribute('y1',yy-4);
tick.setAttribute('x2',x);tick.setAttribute('y2',yy+4);
tick.setAttribute('stroke','var(--muted)');tick.setAttribute('stroke-width','0.8');
g.appendChild(tick);
});
const txt = document.createElementNS(NS,'text');
const mx = (x1+x2)/2, my = (y1+y2)/2;
txt.setAttribute('x', mx + (side==='h'?0:8));
txt.setAttribute('y', my + (side==='h'?-6:4));
txt.setAttribute('text-anchor', side==='h'?'middle':'start');
txt.setAttribute('class','svg-dim-text');
txt.textContent = text;
g.appendChild(txt);
dimensionsGroup.appendChild(g);
return g;
}
/* 六边形俯视图 */
function buildHexView(){
const svg = document.getElementById('hexSvg');
const cx = 140, cy = 140;
const r = 14;
const rows = 9, cols = 9;
const sqrt3 = Math.sqrt(3);
const dx = sqrt3 * r;
const dy = 1.5 * r;
for(let row=0;row<rows;row++){
for(let col=0;col<cols;col++){
const hx = cx + (col - cols/2)*dx + (row%2?dx/2:0);
const hy = cy + (row - rows/2)*dy;
// 判断是否在指尖区域(中心椭圆内)
const distX = (hx-cx)/70;
const distY = (hy-cy)/90;
const inActive = distX*distX + distY*distY < 1;
const hex = document.createElementNS(NS,'polygon');
let pts = '';
for(let k=0;k<6;k++){
const angle = Math.PI/6 + k*Math.PI/3;
const px = hx + r*Math.cos(angle);
const py = hy + r*Math.sin(angle);
pts += `${px},${py} `;
}
hex.setAttribute('points',pts.trim());
if(inActive){
hex.setAttribute('fill','rgba(0,229,255,0.12)');
hex.setAttribute('stroke','rgba(0,229,255,0.5)');
hex.setAttribute('stroke-width','1');
} else {
hex.setAttribute('fill','rgba(212,168,71,0.08)');
hex.setAttribute('stroke','rgba(212,168,71,0.2)');
hex.setAttribute('stroke-width','0.5');
}
svg.appendChild(hex);
// 活跃区域加内部凹坑
if(inActive){
const inner = document.createElementNS(NS,'circle');
inner.setAttribute('cx',hx);inner.setAttribute('cy',hy);
inner.setAttribute('r',r*0.45);
inner.setAttribute('fill','rgba(0,229,255,0.08)');
inner.setAttribute('stroke','rgba(0,229,255,0.3)');
inner.setAttribute('stroke-width','0.5');
svg.appendChild(inner);
}
}
}
// 标签
const label1 = document.createElementNS(NS,'text');
label1.setAttribute('x',cx);label1.setAttribute('y',20);
label1.setAttribute('text-anchor','middle');
label1.setAttribute('font-family','"Oxanium",sans-serif');
label1.setAttribute('font-size','9');label1.setAttribute('fill','var(--accent)');
label1.setAttribute('font-weight','600');label1.setAttribute('letter-spacing','1.5');
label1.textContent='ACTIVE ZONE';
svg.appendChild(label1);
const label2 = document.createElementNS(NS,'text');
label2.setAttribute('x',cx);label2.setAttribute('y',268);
label2.setAttribute('text-anchor','middle');
label2.setAttribute('font-family','"Noto Sans SC",sans-serif');
label2.setAttribute('font-size','9');label2.setAttribute('fill','var(--muted)');
label2.textContent='指尖/掌心前1/3 区域';
svg.appendChild(label2);
}
buildHexView();
/* ====== 更新函数 ====== */
function updateGloveBody(offset, deformation){
const baseY = GLOVE_Y + offset;
const bottomY = baseY + GLOVE_THICK;
// 构建手套底面路径(含凹坑轮廓)
let d = `M160,${baseY} L${PIT_CENTERS[0]-PIT_HALF_TOP},${bottomY} `;
for(let i=0;i<PIT_COUNT;i++){
const cx = PIT_CENTERS[i];
const pitOpenY = bottomY;
const pitBotY = bottomY + PIT_DEPTH * (1 - deformation*0.65);
const curveAmt = 10 * (1 - deformation);
// 左壁
d += `L${cx-PIT_HALF_BOT},${pitBotY - curveAmt} `;
// 底部曲线
d += `Q${cx},${pitBotY + curveAmt} ${cx+PIT_HALF_BOT},${pitBotY - curveAmt} `;
// 右壁到下一个凹坑口或结尾
if(i < PIT_COUNT-1){
const nextCx = PIT_CENTERS[i+1];
d += `L${nextCx-PIT_HALF_TOP},${bottomY} `;
}
}
d += `L800,${bottomY} L800,${baseY} Z`;
gloveBody.setAttribute('d', d);
}
function updatePits(offset, deformation, airExpel, vacuumIntensity){
const baseY = GLOVE_Y + offset;
const bottomY = baseY + GLOVE_THICK;
pitData.forEach((pit,i)=>{
const cx = pit.cx;
const pitBotY = bottomY + PIT_DEPTH * (1 - deformation*0.65);
const curveAmt = 10 * (1 - deformation);
// 凹坑填充路径
const fillD = `M${cx-PIT_HALF_TOP},${bottomY} L${cx-PIT_HALF_BOT},${pitBotY - curveAmt} Q${cx},${pitBotY + curveAmt} ${cx+PIT_HALF_BOT},${pitBotY - curveAmt} L${cx+PIT_HALF_TOP},${bottomY} Z`;
pit.pitFill.setAttribute('d', fillD);
// 凹坑壁路径
const wallD = `M${cx-PIT_HALF_TOP},${bottomY} L${cx-PIT_HALF_BOT},${pitBotY - curveAmt} Q${cx},${pitBotY + curveAmt} ${cx+PIT_HALF_BOT},${pitBotY - curveAmt} L${cx+PIT_HALF_TOP},${bottomY}`;
pit.pitWall.setAttribute('d', wallD);
// 薄膜底线
const filmD = `M${cx-PIT_HALF_BOT},${pitBotY - curveAmt} Q${cx},${pitBotY + curveAmt} ${cx+PIT_HALF_BOT},${pitBotY - curveAmt}`;
pit.filmLine.setAttribute('d', filmD);
// 薄膜厚度视觉反馈:变形时变亮变细
if(deformation > 0.3){
pit.filmLine.setAttribute('stroke-width', lerp(2.5, 1.5, deformation));
pit.filmLine.setAttribute('stroke', `rgba(0,229,255,${0.3+deformation*0.5})`);
} else {
pit.filmLine.setAttribute('stroke-width', '2.5');
pit.filmLine.setAttribute('stroke', '#d4a847');
}
// 负压发光
const glowCy = (bottomY + pitBotY) / 2;
pit.vacGlow.setAttribute('cx', cx);
pit.vacGlow.setAttribute('cy', glowCy);
pit.vacGlow.setAttribute('rx', PIT_HALF_BOT + 5);
pit.vacGlow.setAttribute('ry', (pitBotY - bottomY)/2 + 5);
pit.vacGlow.setAttribute('opacity', vacuumIntensity * 0.8);
// 凹坑填充颜色:空气→负压
if(vacuumIntensity > 0.1){
const r = Math.round(lerp(136, 0, vacuumIntensity));
const g = Math.round(lerp(204, 229, vacuumIntensity));
const b = Math.round(lerp(255, 255, vacuumIntensity));
const a = lerp(0.25, 0.4, vacuumIntensity);
pit.pitFill.setAttribute('fill', `rgba(${r},${g},${b},${a})`);
} else {
pit.pitFill.setAttribute('fill', 'rgba(136,204,255,0.25)');
}
// 吸力小箭头
pit.suctionArrows.forEach((arrow,j)=>{
const arrowX = cx + (j-1)*14;
const arrowBot = pitBotY - 5;
const arrowTop = lerp(pitBotY - 5, bottomY + 5, vacuumIntensity);
arrow.setAttribute('x1', arrowX);
arrow.setAttribute('y1', arrowBot);
arrow.setAttribute('x2', arrowX);
arrow.setAttribute('y2', arrowTop);
arrow.setAttribute('opacity', vacuumIntensity * 0.8);
});
// 空气粒子
pit.particles.forEach((p,j)=>{
const floatT = performance.now()/1000;
const floatX = Math.sin(floatT*1.5 + p._phase)*2;
const floatY = Math.cos(floatT*2 + p._phase)*1.5;
if(airExpel < 0.1){
// 在凹坑内浮动
p.setAttribute('cx', p._baseX + offset*0 + floatX);
p.setAttribute('cy', p._baseY + offset + floatY);
p.setAttribute('opacity', '0.7');
p.setAttribute('fill','rgba(136,204,255,0.7)');
p.setAttribute('r','2.5');
} else {
// 被挤出凹坑
const ex = p._baseX + p._offsetX * airExpel + floatX * (1-airExpel);
const ey = p._baseY + p._offsetY * airExpel + offset;
p.setAttribute('cx', ex);
p.setAttribute('cy', ey);
p.setAttribute('opacity', clamp(1 - airExpel*0.7, 0.1, 0.7));
if(airExpel > 0.8){
p.setAttribute('fill','rgba(136,204,255,0.3)');
p.setAttribute('r','1.5');
} else {
p.setAttribute('fill','rgba(136,204,255,0.7)');
p.setAttribute('r','2.5');
}
}
});
});
}
function updateForceArrows(offset, phaseInfo){
const baseY = GLOVE_Y + offset;
const {phase, local} = phaseInfo;
// 按压力箭头
if(phase >= 0 && phase <= 2){
const arrowOpacity = phase===0? easeInOut(local) : (phase===2? 1-local*0.5 : 1);
pressArrow.setAttribute('x1','480');
pressArrow.setAttribute('y1', String(baseY - 40));
pressArrow.setAttribute('x2','480');
pressArrow.setAttribute('y2', String(baseY - 10));
pressArrow.setAttribute('opacity', String(arrowOpacity * 0.9));
pressLabel.setAttribute('x','500');
pressLabel.setAttribute('y', String(baseY - 20));
pressLabel.textContent = 'PRESS';
pressLabel.setAttribute('opacity', String(arrowOpacity * 0.9));
} else {
pressArrow.setAttribute('opacity','0');
pressLabel.setAttribute('opacity','0');
}
// 侧滑尝试 & 阻力(负压吸附阶段)
if(phase === 3){
const slideOpacity = 0.4 + 0.3*Math.sin(local*Math.PI*4);
slideArrow.setAttribute('x1','350');
slideArrow.setAttribute('y1', String(baseY + GLOVE_THICK + PIT_DEPTH*0.35));
slideArrow.setAttribute('x2','420');
slideArrow.setAttribute('y2', String(baseY + GLOVE_THICK + PIT_DEPTH*0.35));
slideArrow.setAttribute('opacity', String(slideOpacity));
// 阻力标记(X号表示无法滑动)
resistArrow.setAttribute('opacity', String(slideOpacity*1.2));
} else {
slideArrow.setAttribute('opacity','0');
resistArrow.setAttribute('opacity','0');
}
}
function updateDimensions(offset, deformation, phaseInfo){
// 清空
while(dimensionsGroup.firstChild) dimensionsGroup.removeChild(dimensionsGroup.firstChild);
const baseY = GLOVE_Y + offset;
const bottomY = baseY + GLOVE_THICK;
const {phase} = phaseInfo;
if(phase >= 1){
// 凹坑直径标注
const cx = PIT_CENTERS[2]; // 中间凹坑
createDimLine(
cx - PIT_HALF_TOP, bottomY + PIT_DEPTH + 15,
cx + PIT_HALF_TOP, bottomY + PIT_DEPTH + 15,
'⌀1.5mm', 'h'
);
// 凹坑深度标注
const dimX = cx + PIT_HALF_TOP + 18;
createDimLine(
dimX, bottomY,
dimX, bottomY + PIT_DEPTH*(1-deformation*0.65),
'0.15mm', 'v'
);
// 薄膜厚度标注(高亮)
if(deformation > 0.3){
const filmY = bottomY + PIT_DEPTH*(1-deformation*0.65);
const g = document.createElementNS(NS,'g');
g.setAttribute('opacity','0.9');
const rect = document.createElementNS(NS,'rect');
rect.setAttribute('x', cx - 8);
rect.setAttribute('y', filmY - 2);
rect.setAttribute('width', 16);
rect.setAttribute('height', 4);
rect.setAttribute('fill','none');
rect.setAttribute('stroke','var(--accent)');
rect.setAttribute('stroke-width','1');
rect.setAttribute('rx','1');
g.appendChild(rect);
const txt = document.createElementNS(NS,'text');
txt.setAttribute('x', cx + 15);
txt.setAttribute('y', filmY + 3);
txt.setAttribute('font-family','"Oxanium",sans-serif');
txt.setAttribute('font-size','9');
txt.setAttribute('fill','var(--accent)');
txt.setAttribute('font-weight','700');
txt.textContent = '0.025mm';
g.appendChild(txt);
dimensionsGroup.appendChild(g);
}
}
}
function updateCallout(vacuumIntensity, offset){
const baseY = GLOVE_Y + offset;
calloutGroup.setAttribute('opacity', String(clamp(vacuumIntensity*1.5, 0, 1)));
// 更新连接线终点
const line = document.getElementById('calloutLine');
if(line){
line.setAttribute('y2', String(baseY + GLOVE_THICK + PIT_DEPTH*0.3));
}
}
function updatePhaseUI(phaseInfo){
const items = document.querySelectorAll('.phase-item');
items.forEach((item,i)=>{
item.classList.toggle('active', i===phaseInfo.phase);
});
document.getElementById('progressBar').style.width = (progress*100)+'%';
}
/* ====== 主循环 ====== */
function tick(timestamp){
if(!lastTime) lastTime = timestamp;
const dt = (timestamp - lastTime) / 1000;
lastTime = timestamp;
if(playing && !manualMode){
progress += dt * 0.09 * speed; // ~11秒一个周期
if(progress > 1) progress = 0;
}
const phaseInfo = getPhase(progress);
const offset = getPressOffset(phaseInfo);
const deformation = getDeformation(phaseInfo);
const airExpel = getAirExpel(phaseInfo);
const vacuum = getVacuumIntensity(phaseInfo);
// 更新组件位移
assemblyGroup.setAttribute('transform', `translate(0, ${offset})`);
// 更新各部分
updateGloveBody(0, deformation); // 手套体在assembly内部,offset由group处理
updatePits(0, deformation, airExpel, vacuum);
updateForceArrows(0, phaseInfo);
updateDimensions(0, deformation, phaseInfo);
updateCallout(vacuum, 0);
updatePhaseUI(phaseInfo);
// 同步手动滑块
if(manualMode){
document.getElementById('pressureSlider').value = Math.round(progress*100);
document.getElementById('pressureVal').textContent = Math.round(progress*100)+'%';
}
requestAnimationFrame(tick);
}
/* ====== UI 控制 ====== */
const btnPlay = document.getElementById('btnPlay');
const btnReset = document.getElementById('btnReset');
const btnManual = document.getElementById('btnManual');
const manualSliderWrap = document.getElementById('manualSliderWrap');
const pressureSlider = document.getElementById('pressureSlider');
const speedSlider = document.getElementById('speedSlider');
btnPlay.addEventListener('click', ()=>{
playing = !playing;
btnPlay.innerHTML = playing ? '<i class="fas fa-pause"></i>' : '<i class="fas fa-play"></i>';
btnPlay.classList.toggle('active', playing);
});
btnReset.addEventListener('click', ()=>{
progress = 0;
lastTime = 0;
if(!playing){
playing = true;
btnPlay.innerHTML = '<i class="fas fa-pause"></i>';
btnPlay.classList.toggle('active', true);
}
});
btnManual.addEventListener('click', ()=>{
manualMode = !manualMode;
btnManual.classList.toggle('active', manualMode);
manualSliderWrap.style.display = manualMode ? 'block' : 'none';
if(manualMode){
pressureSlider.value = Math.round(progress*100);
document.getElementById('pressureVal').textContent = Math.round(progress*100)+'%';
}
});
pressureSlider.addEventListener('input', (e)=>{
if(manualMode){
progress = parseInt(e.target.value)/100;
document.getElementById('pressureVal').textContent = e.target.value+'%';
}
});
speedSlider.addEventListener('input', (e)=>{
speed = parseInt(e.target.value)/100;
document.getElementById('speedVal').textContent = speed.toFixed(1)+'x';
});
/* ====== 启动 ====== */
requestAnimationFrame(tick);
})();
</script>
</body>
</html>
这个实现的核心设计思路:
视觉叙事聚焦 IFR(最终理想解):动画直接展示微负压腔的工作原理,不做前后对比,而是通过5个连续阶段——接触→按压→排气密封→负压吸附→释放——完整呈现"化薄为利"的核心创新。
关键视觉引导:
- 颜色语言:琥珀色代表PE手套基材,天青色(
#00e5ff)专用于负压效果,当薄膜密封形成时,凹坑内部从淡蓝(空气)渐变为发光的青色(真空),配合脉动效果强调吸附力 - 薄膜高亮:变形阶段薄膜线从琥珀色过渡到青色发光,视觉上直接指向创新核心
- 吸力箭头:负压阶段每个凹坑内出现向上的青色箭头,直观表达"主动吸附"
- 尺寸标注:关键参数(0.025mm 薄膜厚度)在密封形成时以高亮标注浮现
交互控制:
- 播放/暂停、重置、速度调节
- 手动模式:切换后可通过滑块精确控制按压力度(0~100%),自由探索每个变形细节
- 阶段指示器实时高亮当前阶段
六边形俯视图:右侧面板展示微凹坑阵列的顶视布局,高亮指尖/掌心活跃区域,帮助理解空间分布。
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
