分享图
A
动画渲染工坊
就绪

这是为您生成的一个高保真SVG原理动画,用于直观展示机械蛇如何通过“扭转→螺旋膨胀波→地面摩擦”的单一驱动实现蜿蜒爬行。

<!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>
    <style>
        :root {
            --bg: #14181e;
            --surface: #1c2128;
            --border: #2a3039;
            --text: #c8cdd4;
            --text-secondary: #88909b;
            --accent: #e8953b;
            --accent-glow: #f5b461;
            --highlight: #ffb347;
            --danger: #d4534a;
            --steel: #9da5b0;
            --rubber: #6b4e3d;
            --rubber-light: #8b6a52;
            --ground: #3a3530;
            --motor: #4d535c;
            --wave-glow: #ff9d3c;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: #0d1116;
            font-family: 'IBM Plex Mono', 'SF Mono', 'Cascadia Code', 'Consolas', 'Menlo', monospace;
            color: var(--text);
            user-select: none;
            -webkit-user-select: none;
            overflow-x: hidden;
        }

        .main-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 16px;
            width: 100%;
            max-width: 1060px;
            padding: 20px;
        }

        .header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            width: 100%;
            gap: 16px;
            flex-wrap: wrap;
        }

        .title-block {
            display: flex;
            align-items: center;
            gap: 12px;
        }

        .title-icon {
            width: 36px;
            height: 36px;
            border-radius: 8px;
            background: linear-gradient(135deg, #e8953b, #d4534a);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 18px;
            flex-shrink: 0;
        }

        .title-text h2 {
            font-size: 1.05rem;
            font-weight: 600;
            letter-spacing: 0.03em;
            color: #e8ecf0;
            line-height: 1.3;
        }
        .title-text span {
            font-size: 0.7rem;
            color: var(--text-secondary);
            letter-spacing: 0.06em;
            text-transform: uppercase;
        }

        .badge {
            display: inline-flex;
            align-items: center;
            gap: 5px;
            padding: 5px 10px;
            border-radius: 20px;
            font-size: 0.68rem;
            letter-spacing: 0.04em;
            font-weight: 500;
            white-space: nowrap;
        }
        .badge-ifr {
            background: #1a2a1f;
            color: #5ec97a;
            border: 1px solid #2a4a30;
        }
        .badge-triz {
            background: #1f1a2a;
            color: #a08cd4;
            border: 1px solid #302a45;
        }

        .svg-wrapper {
            position: relative;
            width: 100%;
            max-width: 1020px;
            aspect-ratio: 1020 / 560;
            background: var(--surface);
            border-radius: 14px;
            border: 1px solid var(--border);
            overflow: hidden;
            box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.02);
        }

        svg {
            display: block;
            width: 100%;
            height: 100%;
        }

        .controls-panel {
            display: flex;
            align-items: center;
            gap: 20px;
            flex-wrap: wrap;
            justify-content: center;
            width: 100%;
            max-width: 1020px;
            background: var(--surface);
            border-radius: 12px;
            border: 1px solid var(--border);
            padding: 14px 22px;
        }

        .control-group {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .control-group label {
            font-size: 0.7rem;
            letter-spacing: 0.05em;
            color: var(--text-secondary);
            text-transform: uppercase;
            white-space: nowrap;
        }
        .control-group output {
            font-size: 0.75rem;
            font-weight: 600;
            color: var(--accent-glow);
            min-width: 48px;
            text-align: center;
        }

        input[type="range"] {
            -webkit-appearance: none;
            width: 140px;
            height: 6px;
            border-radius: 3px;
            background: var(--border);
            outline: none;
            cursor: pointer;
        }
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 22px;
            height: 22px;
            border-radius: 50%;
            background: var(--accent);
            cursor: pointer;
            border: 2px solid #fff3;
            box-shadow: 0 0 12px rgba(232, 149, 59, 0.4);
            transition: box-shadow 0.2s;
        }
        input[type="range"]::-webkit-slider-thumb:active {
            box-shadow: 0 0 22px rgba(232, 149, 59, 0.7);
        }

        .btn {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            padding: 8px 16px;
            border-radius: 20px;
            font-size: 0.72rem;
            font-weight: 600;
            letter-spacing: 0.04em;
            cursor: pointer;
            transition: all 0.2s;
            border: 1px solid var(--border);
            background: var(--surface);
            color: var(--text);
            font-family: inherit;
            white-space: nowrap;
        }
        .btn:hover {
            background: #2a303b;
            border-color: #4a5059;
        }
        .btn.active-btn {
            background: #2a2018;
            border-color: #8b5e3c;
            color: var(--accent-glow);
            box-shadow: 0 0 14px rgba(232, 149, 59, 0.2);
        }
        .btn-play {
            min-width: 80px;
            justify-content: center;
        }

        .legend-row {
            display: flex;
            gap: 18px;
            flex-wrap: wrap;
            justify-content: center;
            font-size: 0.65rem;
            letter-spacing: 0.04em;
            color: var(--text-secondary);
        }
        .legend-dot {
            display: inline-block;
            width: 9px;
            height: 9px;
            border-radius: 50%;
            margin-right: 4px;
            flex-shrink: 0;
        }

        @keyframes pulse-glow {
            0%,
            100% {
                filter: drop-shadow(0 0 3px rgba(255, 157, 60, 0.5));
            }
            50% {
                filter: drop-shadow(0 0 10px rgba(255, 157, 60, 0.9));
            }
        }

        @keyframes subtle-float {
            0%,
            100% {
                transform: translateY(0);
            }
            50% {
                transform: translateY(-2px);
            }
        }

        .contact-glow {
            animation: pulse-glow 1.5s ease-in-out infinite;
        }

        @media (max-width: 768px) {
            .controls-panel {
                gap: 10px;
                padding: 10px 14px;
            }
            input[type="range"] {
                width: 90px;
            }
            .title-text h2 {
                font-size: 0.9rem;
            }
            .btn {
                padding: 6px 10px;
                font-size: 0.65rem;
            }
        }
    </style>
</head>
<body>
    <div class="main-container">
        <!-- 头部信息 -->
        <div class="header">
            <div class="title-block">
                <div class="title-icon">⚙️</div>
                <div class="title-text">
                    <h2>螺旋行波推进 · 单驱动机械蛇</h2>
                    <span>Torsion → Helical Radial Wave → Frictional Thrust</span>
                </div>
            </div>
            <div style="display:flex;gap:8px;flex-wrap:wrap;">
                <span class="badge badge-ifr">◈ IFR 最终理想解</span>
                <span class="badge badge-triz">△ TRIZ 矛盾破除</span>
            </div>
        </div>

        <!-- SVG 动画区域 -->
        <div class="svg-wrapper" id="svgContainer">
            <svg id="mainSvg" viewBox="0 0 1020 560" xmlns="http://www.w3.org/2000/svg">
                <!-- 定义 -->
                <defs>
                    <!-- 渐变 -->
                    <radialGradient id="groundGrad" cx="50%" cy="0%" r="80%">
                        <stop offset="0%" stop-color="#4a423a" />
                        <stop offset="100%" stop-color="#2a2520" />
                    </radialGradient>
                    <linearGradient id="snakeBodyGrad" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stop-color="#7d5c45" />
                        <stop offset="35%" stop-color="#9b7358" />
                        <stop offset="60%" stop-color="#6b4a35" />
                        <stop offset="100%" stop-color="#4a3022" />
                    </linearGradient>
                    <linearGradient id="coreGrad" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stop-color="#d8dce3" />
                        <stop offset="50%" stop-color="#a8b0ba" />
                        <stop offset="100%" stop-color="#7a828c" />
                    </linearGradient>
                    <linearGradient id="motorGrad" x1="0" y1="0" x2="1" y2="0">
                        <stop offset="0%" stop-color="#5a5f68" />
                        <stop offset="50%" stop-color="#737982" />
                        <stop offset="100%" stop-color="#4a4f58" />
                    </linearGradient>
                    <linearGradient id="massGrad" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stop-color="#e06050" />
                        <stop offset="100%" stop-color="#9b2d25" />
                    </linearGradient>
                    <!-- 发光滤镜 -->
                    <filter id="glowFilter" x="-40%" y="-40%" width="180%" height="180%">
                        <feGaussianBlur stdDeviation="4" result="blur" />
                        <feMerge>
                            <feMergeNode in="blur" />
                            <feMergeNode in="blur" />
                            <feMergeNode in="SourceGraphic" />
                        </feMerge>
                    </filter>
                    <filter id="softGlow" x="-30%" y="-30%" width="160%" height="160%">
                        <feGaussianBlur stdDeviation="2.5" result="blur" />
                        <feMerge>
                            <feMergeNode in="blur" />
                            <feMergeNode in="SourceGraphic" />
                        </feMerge>
                    </filter>
                    <filter id="shadowFilter" x="-5%" y="-10%" width="110%" height="140%">
                        <feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000000" flood-opacity="0.45" />
                    </filter>
                    <!-- 箭头标记 -->
                    <marker id="arrowOrange" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
                        <polygon points="0,0 8,3 0,6" fill="#f5903e" />
                    </marker>
                    <marker id="arrowGold" markerWidth="7" markerHeight="5" refX="6" refY="2.5" orient="auto">
                        <polygon points="0,0 7,2.5 0,5" fill="#ffb347" />
                    </marker>
                    <marker id="arrowWhite" markerWidth="7" markerHeight="5" refX="6" refY="2.5" orient="auto">
                        <polygon points="0,0 7,2.5 0,5" fill="#c8cdd4" />
                    </marker>
                    <!-- 纹理图案 -->
                    <pattern id="groundTexture" patternUnits="userSpaceOnUse" width="30" height="8">
                        <line x1="0" y1="4" x2="30" y2="4" stroke="#3a3530" stroke-width="0.6" opacity="0.5" />
                        <circle cx="8" cy="3" r="0.7" fill="#3a3530" opacity="0.3" />
                        <circle cx="22" cy="6" r="0.5" fill="#3a3530" opacity="0.3" />
                    </pattern>
                    <pattern id="rubberTexture" patternUnits="userSpaceOnUse" width="4" height="8">
                        <line x1="2" y1="0" x2="2" y2="8" stroke="#3d2518" stroke-width="0.8" opacity="0.5" />
                    </pattern>
                    <!-- 裁剪路径 -->
                    <clipPath id="snakeClip">
                        <rect id="snakeClipRect" x="95" y="215" width="760" height="95" rx="16" />
                    </clipPath>
                </defs>

                <!-- 背景 -->
                <rect width="1020" height="560" fill="#14181e" />
                <rect x="0" y="0" width="1020" height="560" fill="url(#groundTexture)" opacity="0.15" />

                <!-- 网格参考线 -->
                <g opacity="0.06" stroke="#88909b" stroke-width="0.5">
                    <line x1="50" y1="280" x2="970" y2="280" stroke-dasharray="4,12" />
                    <line x1="50" y1="380" x2="970" y2="380" stroke-dasharray="8,16" />
                </g>

                <!-- 地面 -->
                <g id="groundGroup">
                    <rect x="30" y="378" width="960" height="70" fill="url(#groundGrad)" rx="3" />
                    <rect x="30" y="378" width="960" height="70" fill="url(#groundTexture)" opacity="0.6" rx="3" />
                    <line x1="30" y1="378" x2="990" y2="378" stroke="#5a5248" stroke-width="1.5" />
                    <!-- 地面摩擦指示纹理 -->
                    <g opacity="0.35">
                        <line x1="60" y1="390" x2="78" y2="390" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="100" y1="394" x2="122" y2="394" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="160" y1="388" x2="175" y2="388" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="210" y1="392" x2="232" y2="392" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="280" y1="386" x2="298" y2="386" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="340" y1="390" x2="365" y2="390" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="420" y1="393" x2="440" y2="393" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="500" y1="387" x2="518" y2="387" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="570" y1="391" x2="595" y2="391" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="650" y1="389" x2="668" y2="389" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="720" y1="393" x2="745" y2="393" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="800" y1="386" x2="818" y2="386" stroke="#6b6258" stroke-width="0.8" />
                        <line x1="870" y1="390" x2="895" y2="390" stroke="#6b6258" stroke-width="0.8" />
                    </g>
                </g>

                <!-- 蛇身主体组 -->
                <g id="snakeGroup" filter="url(#shadowFilter)">
                    <!-- 皮囊主体 - 上半部分(固定轮廓) -->
                    <path id="snakeBodyTop"
                    d="M100,255 Q100,228 130,225 L830,225 Q860,228 860,255 L860,280 Q860,300 830,300 L130,300 Q100,300 100,280 Z"
                    fill="url(#snakeBodyGrad)" stroke="#3a2518" stroke-width="1.5" />

                    <!-- 皮囊纵切纹路 -->
                    <g id="rubberRibs" opacity="0.55" stroke="#3d2015" stroke-width="1.2">
                        <!-- 动态生成纹路 -->
                    </g>

                    <!-- 皮囊底部 - 动态轮廓(随行波变化) -->
                    <path id="snakeBodyBottom"
                    d="M100,280 Q100,300 130,300 L830,300 Q860,300 860,280 L860,305 Q860,340 830,340 L130,340 Q100,340 100,305 Z"
                    fill="#5a3d2b" stroke="#3a2518" stroke-width="1.2" opacity="0.85" />

                    <!-- 皮囊底部动态凸起轮廓 -->
                    <path id="snakeBellyContour"
                    d="M100,300 L130,300 L830,300 L860,300"
                    fill="none" stroke="#8b5e40" stroke-width="2.5" stroke-linecap="round" opacity="0.9" />

                    <!-- 行波凸起高亮 -->
                    <g id="waveHighlights" filter="url(#softGlow)">
                        <!-- 动态生成 -->
                    </g>

                    <!-- 芯轴 -->
                    <line id="coreShaft" x1="115" y1="272" x2="845" y2="272" stroke="url(#coreGrad)" stroke-width="7"
                    stroke-linecap="round" />
                    <line x1="115" y1="272" x2="845" y2="272" stroke="#e8ecf2" stroke-width="1.5" opacity="0.4"
                    stroke-linecap="round" />

                    <!-- 芯轴扭转指示线(螺旋缠绕线在侧面视图中的投影) -->
                    <g id="torsionIndicators" opacity="0.5">
                        <!-- 动态生成扭转标记 -->
                    </g>

                    <!-- 偏心质量块 -->
                    <g id="massBlocks">
                        <!-- 动态生成12个三角形质量块 -->
                    </g>

                    <!-- 质量块旋转轨迹指示 -->
                    <g id="orbitIndicators" opacity="0.25">
                        <!-- 动态生成 -->
                    </g>
                </g>

                <!-- 头部电机组 -->
                <g id="motorGroup">
                    <!-- 电机外壳 -->
                    <rect x="70" y="240" width="48" height="44" rx="10" fill="url(#motorGrad)" stroke="#3a3f46"
                    stroke-width="1.8" />
                    <rect x="74" y="244" width="40" height="36" rx="7" fill="none" stroke="#6a7078" stroke-width="0.8"
                        opacity="0.5" />
                    <!-- 电机旋转指示环 -->
                    <circle cx="94" cy="262" r="11" fill="none" stroke="#8a9099" stroke-width="2.5" opacity="0.7"
                    id="motorRing" />
                    <!-- 旋转标记 -->
                    <line id="motorTick" x1="94" y1="251" x2="94" y2="257" stroke="#ffb347" stroke-width="2.5"
                    stroke-linecap="round" />
                    <!-- 电机标签 -->
                    <text x="94" y="298" text-anchor="middle" font-size="8" fill="#88909b" letter-spacing="0.05em">无刷电机</text>
                    <text x="94" y="310" text-anchor="middle" font-size="7" fill="#6a7078" letter-spacing="0.04em">
                        BLDC</text>
                </g>

                <!-- 软性联轴器 -->
                <g id="couplingGroup">
                    <rect x="112" y="260" width="16" height="14" rx="3" fill="#6a5e50" stroke="#4a3e35" stroke-width="1"
                    opacity="0.8" />
                    <line x1="114" y1="264" x2="126" y2="264" stroke="#8a7a65" stroke-width="0.7" />
                    <line x1="114" y1="268" x2="126" y2="268" stroke="#8a7a65" stroke-width="0.7" />
                </g>

                <!-- 地面接触点(行波凸起与地面接触处) -->
                <g id="contactPoints" filter="url(#glowFilter)">
                    <!-- 动态生成 -->
                </g>

                <!-- 力流箭头 -->
                <g id="forceArrows">
                    <!-- 动态更新 -->
                </g>

                <!-- 前进位移指示 -->
                <g id="displacementIndicator">
                    <line id="displacementLine" x1="140" y1="420" x2="140" y2="420" stroke="#ffb347" stroke-width="2"
                    stroke-dasharray="6,4" opacity="0" />
                    <text id="displacementText" x="140" y="438" text-anchor="middle" font-size="9" fill="#ffb347"
                    opacity="0">位移: 0mm</text>
                </g>

                <!-- 标注说明 -->
                <g id="annotations" font-family="'IBM Plex Mono','SF Mono',monospace">
                    <!-- 芯轴标注 -->
                    <line x1="480" y1="272" x2="520" y2="195" stroke="#88909b" stroke-width="0.8" stroke-dasharray="3,4"
                    opacity="0.6" />
                    <text x="525" y="192" font-size="8.5" fill="#a8b0ba" letter-spacing="0.03em">弹簧钢芯轴</text>
                    <text x="525" y="203" font-size="7" fill="#6a7078">Ø8mm · 扭转刚度0.5N·m/rad</text>

                    <!-- 质量块标注 -->
                    <line x1="360" y1="255" x2="340" y2="170" stroke="#d4534a" stroke-width="0.8" stroke-dasharray="3,4"
                    opacity="0.6" />
                    <text x="295" y="166" font-size="8.5" fill="#e06050" letter-spacing="0.03em">偏心质量块</text>
                    <text x="295" y="177" font-size="7" fill="#b05045">30g · 螺旋错开30°</text>

                    <!-- 皮囊标注 -->
                    <line x1="620" y1="305" x2="660" y2="440" stroke="#8b6a52" stroke-width="0.8" stroke-dasharray="3,4"
                    opacity="0.6" />
                    <text x="665" y="436" font-size="8.5" fill="#9b7358" letter-spacing="0.03em">橡胶皮囊</text>
                    <text x="665" y="447" font-size="7" fill="#7d5c45">纵切纹路 · 过盈接触0.3mm</text>

                    <!-- 行波标注 -->
                    <text x="445" y="130" font-size="9" fill="#ffb347" letter-spacing="0.05em" text-anchor="middle"
                    opacity="0.85">▼ 螺旋行波传播方向 ▼</text>
                    <line x1="200" y1="138" x2="690" y2="138" stroke="#ffb347" stroke-width="1" opacity="0.4"
                    stroke-dasharray="8,5" marker-end="url(#arrowGold)" />
                </g>

                <!-- 图例区 -->
                <g id="legendGroup" transform="translate(810, 60)" opacity="0.8">
                    <rect x="0" y="0" width="175" height="115" rx="8" fill="#1c2128" stroke="#2a3039" stroke-width="1" />
                    <text x="12" y="18" font-size="8" fill="#88909b" letter-spacing="0.05em">图例</text>
                    <circle cx="16" cy="34" r="4" fill="#e06050" />
                    <text x="26" y="37" font-size="7.5" fill="#a8b0ba">偏心质量块</text>
                    <circle cx="16" cy="52" r="4" fill="#ffb347" filter="url(#softGlow)" />
                    <text x="26" y="55" font-size="7.5" fill="#a8b0ba">行波凸起/接触点</text>
                    <line x1="12" y1="70" x2="20" y2="70" stroke="#ffb347" stroke-width="2" marker-end="url(#arrowGold)" />
                    <text x="26" y="73" font-size="7.5" fill="#a8b0ba">力流方向</text>
                    <circle cx="16" cy="90" r="3.5" fill="none" stroke="#9da5b0" stroke-width="1.5" />
                    <text x="26" y="93" font-size="7.5" fill="#a8b0ba">芯轴旋转轨迹</text>
                </g>
            </svg>
        </div>

        <!-- 控制面板 -->
        <div class="controls-panel" id="controlsPanel">
            <div class="control-group">
                <label>⚡ 电机转速</label>
                <input type="range" id="rpmSlider" min="200" max="600" value="360" step="10" />
                <output id="rpmOutput">360 RPM</output>
            </div>
            <button class="btn btn-play active-btn" id="btnPlay" title="播放/暂停">
                <span id="playIcon">⏸</span> <span id="playText">暂停</span>
            </button>
            <button class="btn" id="btnReset" title="重置动画">↺ 重置</button>
            <button class="btn" id="btnShowInternals" title="显示/隐藏内部结构">🔍 内部结构</button>
            <div class="control-group">
                <label>🐍 速度倍率</label>
                <input type="range" id="speedScaleSlider" min="0.2" max="2.0" value="0.6" step="0.1" />
                <output id="speedOutput">0.6×</output>
            </div>
        </div>

        <!-- 底部图例行 -->
        <div class="legend-row">
            <span><span class="legend-dot" style="background:#e06050;"></span>偏心质量块(12个·螺旋排列)</span>
            <span><span class="legend-dot" style="background:#ffb347;"></span>行波凸起·地面接触点</span>
            <span><span class="legend-dot" style="background:#9da5b0;"></span>弹簧钢芯轴Ø8mm</span>
            <span><span class="legend-dot" style="background:#8b6a52;"></span>橡胶皮囊(纵切纹路)</span>
        </div>
    </div>

    <script>
        (function() {
            // ============ DOM 引用 ============
            const svg = document.getElementById('mainSvg');
            const snakeGroup = document.getElementById('snakeGroup');
            const massBlocksGroup = document.getElementById('massBlocks');
            const orbitIndicatorsGroup = document.getElementById('orbitIndicators');
            const torsionIndicatorsGroup = document.getElementById('torsionIndicators');
            const rubberRibsGroup = document.getElementById('rubberRibs');
            const waveHighlightsGroup = document.getElementById('waveHighlights');
            const contactPointsGroup = document.getElementById('contactPoints');
            const forceArrowsGroup = document.getElementById('forceArrows');
            const snakeBellyContour = document.getElementById('snakeBellyContour');
            const motorTick = document.getElementById('motorTick');
            const displacementLine = document.getElementById('displacementLine');
            const displacementText = document.getElementById('displacementText');

            // 控件
            const rpmSlider = document.getElementById('rpmSlider');
            const rpmOutput = document.getElementById('rpmOutput');
            const speedScaleSlider = document.getElementById('speedScaleSlider');
            const speedOutput = document.getElementById('speedOutput');
            const btnPlay = document.getElementById('btnPlay');
            const playIcon = document.getElementById('playIcon');
            const playText = document.getElementById('playText');
            const btnReset = document.getElementById('btnReset');
            const btnShowInternals = document.getElementById('btnShowInternals');

            // ============ 参数 ============
            const NUM_MASSES = 12; // 质量块数量
            const SNAKE_LENGTH = 720; // 蛇身长度 px (对应720mm)
            const MASS_SPACING = SNAKE_LENGTH / NUM_MASSES; // 质量块间距 ≈60px
            const CORE_Y = 272; // 芯轴Y坐标
            const ECCENTRICITY = 15; // 偏心距 px (对应15mm)
            const BELLY_NORMAL_Y = 300; // 皮囊底部正常Y
            const BELLY_MAX_DEFLECT = 42; // 皮囊最大形变幅度(放大展示)
            const GROUND_Y = 378; // 地面Y坐标
            const SNAKE_START_X = 120; // 蛇身起始X(第一个质量块位置)
            const MOTOR_CX = 94; // 电机中心X
            const MOTOR_CY = 262; // 电机中心Y

            // ============ 状态 ============
            let motorAngle = 0; // 电机旋转角度 (rad)
            let rpm = 360; // 当前转速
            let speedScale = 0.6; // 速度倍率
            let isPlaying = true;
            let showInternals = true;
            let snakeDisplacement = 0; // 蛇身前进总位移 (px)
            let lastTime = null;
            let animId = null;

            // 存储质量块DOM引用
            let massElements = [];
            let orbitElements = [];
            let torsionElements = [];
            let ribElements = [];

            // ============ 初始化SVG元素 ============
            function initSVGElements() {
                // 清空动态组
                massBlocksGroup.innerHTML = '';
                orbitIndicatorsGroup.innerHTML = '';
                torsionIndicatorsGroup.innerHTML = '';
                rubberRibsGroup.innerHTML = '';
                waveHighlightsGroup.innerHTML = '';
                contactPointsGroup.innerHTML = '';
                forceArrowsGroup.innerHTML = '';

                massElements = [];
                orbitElements = [];
                torsionElements = [];
                ribElements = [];

                // 创建12个偏心质量块(三角形)
                for (let i = 0; i < NUM_MASSES; i++) {
                    const cx = SNAKE_START_X + i * MASS_SPACING;
                    // 三角形质量块
                    const triangle = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
                    triangle.setAttribute('fill', 'url(#massGrad)');
                    triangle.setAttribute('stroke', '#6b1a14');
                    triangle.setAttribute('stroke-width', '1.2');
                    triangle.setAttribute('data-index', i);
                    triangle.style.transition = 'opacity 0.3s';
                    massBlocksGroup.appendChild(triangle);
                    massElements.push(triangle);

                    // 旋转轨迹圆(虚线)
                    const orbit = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
                    orbit.setAttribute('cx', cx);
                    orbit.setAttribute('cy', CORE_Y);
                    orbit.setAttribute('r', ECCENTRICITY);
                    orbit.setAttribute('fill', 'none');
                    orbit.setAttribute('stroke', '#6a7078');
                    orbit.setAttribute('stroke-width', '0.8');
                    orbit.setAttribute('stroke-dasharray', '3,4');
                    orbit.setAttribute('data-index', i);
                    orbitIndicatorsGroup.appendChild(orbit);
                    orbitElements.push(orbit);

                    // 扭转指示标记(芯轴上的短线)
                    const tLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
                    tLine.setAttribute('x1', cx);
                    tLine.setAttribute('y1', CORE_Y - 5);
                    tLine.setAttribute('x2', cx);
                    tLine.setAttribute('y2', CORE_Y + 5);
                    tLine.setAttribute('stroke', '#d8dce3');
                    tLine.setAttribute('stroke-width', '1.2');
                    tLine.setAttribute('opacity', '0.55');
                    tLine.setAttribute('data-index', i);
                    torsionIndicatorsGroup.appendChild(tLine);
                    torsionElements.push(tLine);
                }

                // 创建皮囊纵切纹路(沿蛇身分布)
                const ribCount = 60;
                for (let i = 0; i <= ribCount; i++) {
                    const rx = 100 + (i / ribCount) * 760;
                    const ribLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
                    ribLine.setAttribute('x1', rx);
                    ribLine.setAttribute('y1', 228);
                    ribLine.setAttribute('x2', rx);
                    ribLine.setAttribute('y2', 300);
                    ribLine.setAttribute('stroke', '#3d2015');
                    ribLine.setAttribute('stroke-width', '0.9');
                    ribLine.setAttribute('opacity', '0.45');
                    ribLine.setAttribute('data-index', i);
                    rubberRibsGroup.appendChild(ribLine);
                    ribElements.push({ element: ribLine, baseX: rx, baseY1: 228, baseY2: 300 });
                }

                // 创建行波高亮元素
                for (let i = 0; i < 6; i++) {
                    const glowDot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
                    glowDot.setAttribute('r', '5');
                    glowDot.setAttribute('fill', '#ffb347');
                    glowDot.setAttribute('opacity', '0');
                    glowDot.setAttribute('data-wave-index', i);
                    waveHighlightsGroup.appendChild(glowDot);
                }

                // 创建接触点元素
                for (let i = 0; i < 6; i++) {
                    const cp = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
                    cp.setAttribute('rx', '6');
                    cp.setAttribute('ry', '3');
                    cp.setAttribute('fill', '#ff9d3c');
                    cp.setAttribute('opacity', '0');
                    cp.setAttribute('data-cp-index', i);
                    cp.classList.add('contact-glow');
                    contactPointsGroup.appendChild(cp);
                }

                // 力流箭头
                for (let i = 0; i < 8; i++) {
                    const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'line');
                    arrow.setAttribute('stroke', '#f5903e');
                    arrow.setAttribute('stroke-width', '1.8');
                    arrow.setAttribute('marker-end', 'url(#arrowOrange)');
                    arrow.setAttribute('opacity', '0');
                    arrow.setAttribute('data-arrow-index', i);
                    forceArrowsGroup.appendChild(arrow);
                }
            }

            // ============ 计算三角形顶点 ============
            function calcTriangleVertices(cx, cy, angle, size = 10) {
                // 三角形绕(cx,cy)旋转,重心在偏心位置
                const eccX = cx + ECCENTRICITY * Math.cos(angle);
                const eccY = cy + ECCENTRICITY * Math.sin(angle);
                // 三角形顶点:一个指向外(径向),两个在内侧
                const tipAngle = angle; // 尖端指向偏心方向
                const tipX = eccX + size * Math.cos(tipAngle);
                const tipY = eccY + size * Math.sin(tipAngle);
                const base1X = eccX + size * 0.55 * Math.cos(tipAngle + 2.2);
                const base1Y = eccY + size * 0.55 * Math.sin(tipAngle + 2.2);
                const base2X = eccX + size * 0.55 * Math.cos(tipAngle - 2.2);
                const base2Y = eccY + size * 0.55 * Math.sin(tipAngle - 2.2);
                return `${tipX},${tipY} ${base1X},${base1Y} ${base2X},${base2Y}`;
            }

            // ============ 更新皮囊底部轮廓 ============
            function updateBellyContour(offsets) {
                // offsets: 12个质量块处的皮囊底部偏移(正值=向下凸起)
                const points = [];
                const n = NUM_MASSES;
                // 在质量块之间进行更精细的采样
                const samplesPerSegment = 4;
                const totalSamples = n * samplesPerSegment;

                for (let s = 0; s <= totalSamples; s++) {
                    const t = s / totalSamples;
                    const x = SNAKE_START_X + t * SNAKE_LENGTH;
                    // 使用Catmull-Rom样条插值
                    const idxFloat = t * (n - 1);
                    const idx0 = Math.floor(idxFloat);
                    const frac = idxFloat - idx0;

                    // 获取相邻4个控制点的偏移值
                    const getOffset = (i) => {
                        const clamped = Math.max(0, Math.min(n - 1, i));
                        return offsets[clamped];
                    };

                    const o0 = getOffset(idx0 - 1);
                    const o1 = getOffset(idx0);
                    const o2 = getOffset(idx0 + 1);
                    const o3 = getOffset(idx0 + 2);

                    // Catmull-Rom插值
                    const t2 = frac * frac;
                    const t3 = t2 * frac;
                    const y = BELLY_NORMAL_Y - (
                        0.5 * ((2 * o1) +
                            (-o0 + o2) * frac +
                            (2 * o0 - 5 * o1 + 4 * o2 - o3) * t2 +
                            (-o0 + 3 * o1 - 3 * o2 + o3) * t3)
                    );

                    points.push(`${x},${Math.round(y * 10) / 10}`);
                }

                snakeBellyContour.setAttribute('d', `M${points.join(' L')}`);
            }

            // ============ 主动画循环 ============
            function animate(timestamp) {
                if (!isPlaying) {
                    lastTime = null;
                    animId = requestAnimationFrame(animate);
                    return;
                }

                if (lastTime === null) {
                    lastTime = timestamp;
                    animId = requestAnimationFrame(animate);
                    return;
                }

                const dt = Math.min((timestamp - lastTime) / 1000, 0.1); // 限制最大步长
                lastTime = timestamp;

                // 更新电机角度
                const angularVelocity = (rpm / 60) * 2 * Math.PI * speedScale; // rad/s
                motorAngle += angularVelocity * dt;
                // 保持角度在0-2π范围内
                const normalizedAngle = motorAngle % (2 * Math.PI);

                // 计算蛇身前进速度(基于行波推进的简化模型)
                // 前进速度约为行波速度的8-15%(取决于摩擦特性)
                const waveSpeed = angularVelocity * SNAKE_LENGTH / (2 * Math.PI); // 行波速度 px/s
                const advanceEfficiency = 0.1; // 前进效率
                const advanceSpeed = waveSpeed * advanceEfficiency;
                snakeDisplacement += advanceSpeed * dt;

                // 限制位移显示范围
                if (snakeDisplacement > 300) snakeDisplacement -= 300;

                // 计算12个质量块的偏移
                const offsets = [];
                const massPhases = [];
                for (let i = 0; i < NUM_MASSES; i++) {
                    const phase = motorAngle + i * (Math.PI / 6); // 30度 = π/6 错开
                    massPhases.push(phase);
                    const sinVal = Math.sin(phase);
                    // 偏移:sin>0时向下推挤皮囊
                    const deflection = sinVal > 0 ? sinVal * BELLY_MAX_DEFLECT : sinVal * BELLY_MAX_DEFLECT * 0.25;
                    offsets.push(deflection);
                }

                // 更新皮囊底部轮廓
                updateBellyContour(offsets);

                // 更新质量块三角形
                for (let i = 0; i < NUM_MASSES; i++) {
                    const cx = SNAKE_START_X + i * MASS_SPACING;
                    const phase = massPhases[i];
                    const verts = calcTriangleVertices(cx, CORE_Y, phase, 9);
                    massElements[i].setAttribute('points', verts);
                    massElements[i].setAttribute('opacity', showInternals ? '0.9' : '0.15');

                    // 更新轨道
                    orbitElements[i].setAttribute('opacity', showInternals ? '0.3' : '0.05');

                    // 更新扭转指示
                    const twistOffset = Math.sin(phase) * 4;
                    torsionElements[i].setAttribute('x1', cx + twistOffset);
                    torsionElements[i].setAttribute('x2', cx - twistOffset);
                    torsionElements[i].setAttribute('opacity', showInternals ? '0.5' : '0.08');
                }

                // 更新皮囊纵切纹路(底部随凸起变化)
                for (let i = 0; i < ribElements.length; i++) {
                    const rib = ribElements[i];
                    const rx = rib.baseX;
                    // 找到该纹路位置对应的偏移
                    const t = (rx - SNAKE_START_X) / SNAKE_LENGTH;
                    const clampedT = Math.max(0, Math.min(1, t));
                    const idxFloat = clampedT * (NUM_MASSES - 1);
                    const idx0 = Math.floor(idxFloat);
                    const frac = idxFloat - idx0;
                    const o0 = offsets[Math.max(0, Math.min(NUM_MASSES - 1, idx0))];
                    const o1 = offsets[Math.max(0, Math.min(NUM_MASSES - 1, idx0 + 1))];
                    const localDeflection = o0 + (o1 - o0) * frac;
                    const bottomY = BELLY_NORMAL_Y - localDeflection;
                    rib.element.setAttribute('y2', Math.max(228, bottomY));
                }

                // 更新行波高亮
                const waveHighlightDots = waveHighlightsGroup.querySelectorAll('circle[data-wave-index]');
                const numHighlights = waveHighlightDots.length;
                for (let i = 0; i < numHighlights; i++) {
                    const dot = waveHighlightDots[i];
                    // 高亮位置跟随行波波峰
                    const wavePhase = normalizedAngle + i * (2 * Math.PI / numHighlights);
                    const wavePos = (wavePhase % (2 * Math.PI)) / (2 * Math.PI); // 0-1
                    const hx = SNAKE_START_X + wavePos * SNAKE_LENGTH;
                    // 找到该位置的偏移
                    const ht = wavePos;
                    const hidxFloat = ht * (NUM_MASSES - 1);
                    const hidx0 = Math.floor(hidxFloat);
                    const hfrac = hidxFloat - hidx0;
                    const ho0 = offsets[Math.max(0, Math.min(NUM_MASSES - 1, hidx0))];
                    const ho1 = offsets[Math.max(0, Math.min(NUM_MASSES - 1, hidx0 + 1))];
                    const hdeflection = ho0 + (ho1 - ho0) * hfrac;
                    const hy = BELLY_NORMAL_Y - hdeflection;

                    dot.setAttribute('cx', hx);
                    dot.setAttribute('cy', hy);
                    // 只在凸起明显时显示
                    const brightness = hdeflection > 5 ? Math.min(1, hdeflection / BELLY_MAX_DEFLECT) : 0;
                    dot.setAttribute('opacity', brightness * 0.85);
                    dot.setAttribute('r', 3 + brightness * 5);
                }

                // 更新接触点
                const contactDots = contactPointsGroup.querySelectorAll('ellipse[data-cp-index]');
                const numContacts = contactDots.length;
                for (let i = 0; i < numContacts; i++) {
                    const cp = contactDots[i];
                    const wavePhase = normalizedAngle + i * (2 * Math.PI / numContacts) + Math.PI / numContacts;
                    const wavePos = (wavePhase % (2 * Math.PI)) / (2 * Math.PI);
                    const cx = SNAKE_START_X + wavePos * SNAKE_LENGTH;
                    const ct = wavePos;
                    const cidxFloat = ct * (NUM_MASSES - 1);
                    const cidx0 = Math.floor(cidxFloat);
                    const cfrac = cidxFloat - cidx0;
                    const co0 = offsets[Math.max(0, Math.min(NUM_MASSES - 1, cidx0))];
                    const co1 = offsets[Math.max(0, Math.min(NUM_MASSES - 1, cidx0 + 1))];
                    const cdeflection = co0 + (co1 - co0) * cfrac;
                    const bellyY = BELLY_NORMAL_Y - cdeflection;

                    cp.setAttribute('cx', cx);
                    cp.setAttribute('cy', GROUND_Y - 1);
                    // 当皮囊凸起接近地面时显示接触点
                    const contactStrength = bellyY >= GROUND_Y - 8 ? Math.min(1, (bellyY - GROUND_Y + 16) / 16) : 0;
                    cp.setAttribute('opacity', contactStrength * 0.9);
                    cp.setAttribute('rx', 4 + contactStrength * 5);
                    cp.setAttribute('ry', 1.5 + contactStrength * 3);
                }

                // 更新力流箭头
                const arrowElements = forceArrowsGroup.querySelectorAll('line[data-arrow-index]');
                for (let i = 0; i < arrowElements.length; i++) {
                    const arrow = arrowElements[i];
                    const wavePhase = normalizedAngle + i * (2 * Math.PI / arrowElements.length);
                    const wavePos = (wavePhase % (2 * Math.PI)) / (2 * Math.PI);
                    const ax = SNAKE_START_X + wavePos * SNAKE_LENGTH;
                    const at = wavePos;
                    const aidxFloat = at * (NUM_MASSES - 1);
                    const aidx0 = Math.floor(aidxFloat);
                    const afrac = aidxFloat - aidx0;
                    const ao0 = offsets[Math.max(0, Math.min(NUM_MASSES - 1, aidx0))];
                    const ao1 = offsets[Math.max(0, Math.min(NUM_MASSES - 1, aidx0 + 1))];
                    const adeflection = ao0 + (ao1 - ao0) * afrac;
                    const bellyY = BELLY_NORMAL_Y - adeflection;
                    const isContacting = bellyY >= GROUND_Y - 10;

                    if (isContacting && adeflection > 10) {
                        arrow.setAttribute('x1', ax);
                        arrow.setAttribute('y1', Math.min(bellyY, GROUND_Y));
                        arrow.setAttribute('x2', ax + 28);
                        arrow.setAttribute('y2', Math.min(bellyY, GROUND_Y) - 5);
                        arrow.setAttribute('opacity', '0.7');
                    } else {
                        arrow.setAttribute('opacity', '0');
                    }
                }

                // 更新电机旋转标记
                const motorTickAngle = normalizedAngle;
                const tickLen = 9;
                const tx = MOTOR_CX + tickLen * Math.cos(motorTickAngle);
                const ty = MOTOR_CY + tickLen * Math.sin(motorTickAngle);
                motorTick.setAttribute('x1', MOTOR_CX);
                motorTick.setAttribute('y1', MOTOR_CY);
                motorTick.setAttribute('x2', tx);
                motorTick.setAttribute('y2', ty);

                // 更新位移指示
                const dispPx = snakeDisplacement % 300;
                displacementLine.setAttribute('x1', 130 + dispPx);
                displacementLine.setAttribute('x2', 130 + dispPx);
                displacementLine.setAttribute('y1', 408);
                displacementLine.setAttribute('y2', 424);
                displacementLine.setAttribute('opacity', '0.7');
                displacementText.setAttribute('x', 130 + dispPx);
                displacementText.setAttribute('y', 438);
                displacementText.setAttribute('opacity', '0.8');
                displacementText.textContent = `前进 ≈${(snakeDisplacement*0.9).toFixed(0)}mm`;

                animId = requestAnimationFrame(animate);
            }

            // ============ 控件事件 ============
            rpmSlider.addEventListener('input', () => {
                rpm = parseInt(rpmSlider.value);
                rpmOutput.textContent = rpm + ' RPM';
            });

            speedScaleSlider.addEventListener('input', () => {
                speedScale = parseFloat(speedScaleSlider.value);
                speedOutput.textContent = speedScale.toFixed(1) + '×';
            });

            btnPlay.addEventListener('click', () => {
                isPlaying = !isPlaying;
                if (isPlaying) {
                    playIcon.textContent = '⏸';
                    playText.textContent = '暂停';
                    btnPlay.classList.add('active-btn');
                    lastTime = null;
                } else {
                    playIcon.textContent = '▶';
                    playText.textContent = '播放';
                    btnPlay.classList.remove('active-btn');
                }
            });

            btnReset.addEventListener('click', () => {
                motorAngle = 0;
                snakeDisplacement = 0;
                lastTime = null;
                rpm = 360;
                speedScale = 0.6;
                rpmSlider.value = 360;
                rpmOutput.textContent = '360 RPM';
                speedScaleSlider.value = 0.6;
                speedOutput.textContent = '0.6×';
                if (!isPlaying) {
                    isPlaying = true;
                    playIcon.textContent = '⏸';
                    playText.textContent = '暂停';
                    btnPlay.classList.add('active-btn');
                }
                // 重新初始化
                initSVGElements();
            });

            btnShowInternals.addEventListener('click', () => {
                showInternals = !showInternals;
                if (showInternals) {
                    btnShowInternals.classList.add('active-btn');
                    btnShowInternals.textContent = '🔍 内部结构: 显示';
                } else {
                    btnShowInternals.classList.remove('active-btn');
                    btnShowInternals.textContent = '🔍 内部结构: 隐藏';
                }
                // 更新质量块和轨道的透明度
                for (let i = 0; i < NUM_MASSES; i++) {
                    massElements[i].setAttribute('opacity', showInternals ? '0.9' : '0.12');
                    orbitElements[i].setAttribute('opacity', showInternals ? '0.3' : '0.04');
                    torsionElements[i].setAttribute('opacity', showInternals ? '0.5' : '0.06');
                }
            });

            // 初始状态
            btnShowInternals.classList.add('active-btn');
            btnShowInternals.textContent = '🔍 内部结构: 显示';

            // ============ 键盘快捷键 ============
            document.addEventListener('keydown', (e) => {
                switch (e.key.toLowerCase()) {
                    case ' ':
                        e.preventDefault();
                        btnPlay.click();
                        break;
                    case 'r':
                        if (!e.ctrlKey && !e.metaKey) {
                            btnReset.click();
                        }
                        break;
                    case 'i':
                        if (!e.ctrlKey && !e.metaKey) {
                            btnShowInternals.click();
                        }
                        break;
                    case 'arrowleft':
                        e.preventDefault();
                        rpm = Math.max(200, rpm - 30);
                        rpmSlider.value = rpm;
                        rpmOutput.textContent = rpm + ' RPM';
                        break;
                    case 'arrowright':
                        e.preventDefault();
                        rpm = Math.min(600, rpm + 30);
                        rpmSlider.value = rpm;
                        rpmOutput.textContent = rpm + ' RPM';
                        break;
                    case 'arrowup':
                        e.preventDefault();
                        speedScale = Math.min(2.0, speedScale + 0.1);
                        speedScaleSlider.value = speedScale;
                        speedOutput.textContent = speedScale.toFixed(1) + '×';
                        break;
                    case 'arrowdown':
                        e.preventDefault();
                        speedScale = Math.max(0.2, speedScale - 0.1);
                        speedScaleSlider.value = speedScale;
                        speedOutput.textContent = speedScale.toFixed(1) + '×';
                        break;
                }
            });

            // ============ 启动 ============
            initSVGElements();
            animId = requestAnimationFrame(animate);

            // ============ 响应式处理 ============
            window.addEventListener('resize', () => {
                // SVG viewBox保持不变,自动缩放
            });

            console.log('🐍 机械蛇螺旋行波推进动画已就绪');
            console.log('   IFR理念: 弹性脊柱 + 螺旋质量块 → 行波推进');
            console.log('   操作: 空格键暂停 | R重置 | I切换内部结构 | ←→调节转速 | ↑↓调节速度');
        })();
    </script>
</body>
</html>

动画解读

这个页面不是一个普通的演示,而是一个围绕最终理想解,演示“扭转-扩张-前进”如何完成一体式推进的思路模型。

1. 核心机制:从“关节弯曲”到“螺旋行波”
动画通过机械蛇的剖面视图,直观展示了“弹簧钢芯轴”在电机驱动下发生扭转。这根芯轴上的偏心质量块依次挤压“橡胶皮囊”,形成不断向前移动的螺旋形凸起。这模拟了力从内部旋转转化为外部蠕动波的过程,代替了传统机械蛇复杂的多关节协调运动。

2. 视觉与交互设计:突出核心矛盾与资源利用

  • 视觉焦点:动画使用高亮发光点标记皮囊上正在与地面接触的“推进点”,并用橙色箭头指示地面反作用力的方向。这直观地强调了“橡胶皮囊的局部形变”是如何被巧妙利用,成为推进抓地力的关键资源。
  • 操作与控制:底部的控制面板让您可以动态调整电机转速(200-600 RPM),并切换显示内部结构。您可以亲手改变转速,观察行波传播速度与前进效率的关系,深入体验动态原理。
  • 信息标注:画面中直接标注了“弹簧钢芯轴”、“偏心质量块”等关键部件及其参数(如螺旋错开30°),将设计细节与视觉呈现紧密结合。
积分规则:第一轮对话扣减6分,后续每轮扣4分