分享图
动画工坊
引擎就绪
<!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-color: #080c16;
            --grid-color: rgba(0, 240, 255, 0.1);
            --text-main: #00f0ff;
            --text-muted: #6b8299;
            --accent-orange: #ffaa00;
            --accent-blue: #0088ff;
            --accent-green: #00ffaa;
            --panel-bg: rgba(8, 12, 22, 0.85);
            --border-color: rgba(0, 240, 255, 0.3);
        }

        body {
            margin: 0;
            padding: 0;
            background-color: var(--bg-color);
            color: var(--text-main);
            font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
            overflow: hidden;
            width: 100vw;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        /* 绝对定位的UI面板,避开中心动画区域 */
        .info-panel {
            position: absolute;
            background: var(--panel-bg);
            border: 1px solid var(--border-color);
            border-radius: 6px;
            padding: 12px 16px;
            backdrop-filter: blur(4px);
            z-index: 10;
            max-width: 300px;
            pointer-events: none; /* 让信息面板不阻挡鼠标事件 */
        }

        .top-left { top: 20px; left: 20px; }
        .bottom-left { bottom: 20px; left: 20px; }
        .top-right { top: 20px; right: 20px; pointer-events: auto; /* 控件面板需要交互 */ }

        .title {
            font-size: 14px;
            font-weight: 600;
            margin: 0 0 8px 0;
            letter-spacing: 1px;
            color: #fff;
            text-transform: uppercase;
        }

        .desc {
            font-size: 12px;
            color: var(--text-muted);
            line-height: 1.5;
            margin: 0;
        }

        .legend-item {
            display: flex;
            align-items: center;
            font-size: 11px;
            margin-top: 6px;
            color: var(--text-muted);
        }

        .legend-color {
            width: 10px;
            height: 10px;
            border-radius: 2px;
            margin-right: 8px;
        }

        /* 交互控件样式 */
        .control-group {
            margin-top: 10px;
            display: flex;
            flex-direction: column;
            gap: 6px;
        }

        .control-header {
            display: flex;
            justify-content: space-between;
            font-size: 12px;
            color: #fff;
        }

        .value-display {
            color: var(--accent-orange);
            font-family: 'Courier New', monospace;
            font-weight: bold;
        }

        input[type=range] {
            -webkit-appearance: none;
            width: 100%;
            background: transparent;
            cursor: pointer;
        }

        input[type=range]::-webkit-slider-runnable-track {
            width: 100%;
            height: 4px;
            background: rgba(0, 240, 255, 0.2);
            border-radius: 2px;
        }

        input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            height: 16px;
            width: 16px;
            border-radius: 50%;
            background: var(--accent-orange);
            margin-top: -6px;
            box-shadow: 0 0 8px var(--accent-orange);
            transition: transform 0.1s;
        }

        input[type=range]::-webkit-slider-thumb:hover {
            transform: scale(1.2);
        }

        /* SVG 容器 */
        #animation-container {
            width: 100%;
            height: 100%;
            max-width: 1200px;
            max-height: 800px;
        }

        svg {
            width: 100%;
            height: 100%;
            display: block;
        }
    </style>
</head>
<body>

    <!-- 顶部左侧信息栏 -->
    <div class="info-panel top-left">
        <h1 class="title">变向角激振系统 | IFR 模型</h1>
        <p class="desc">
            理想最终解:无需改变系统整体架构,仅通过调节偏心块相位差,即可动态重构合成激振力方向。实现排浆速度与岩屑抛射效果的自适应平衡。
        </p>
    </div>

    <!-- 底部左侧图例 -->
    <div class="info-panel bottom-left">
        <h2 class="title" style="font-size: 12px;">图例说明</h2>
        <div class="legend-item">
            <div class="legend-color" style="background: var(--accent-blue);"></div>
            <span>单轴激振力向量</span>
        </div>
        <div class="legend-item">
            <div class="legend-color" style="background: var(--accent-orange); box-shadow: 0 0 5px var(--accent-orange);"></div>
            <span>合成激振力 (方向角可调)</span>
        </div>
        <div class="legend-item">
            <div class="legend-color" style="background: var(--accent-green);"></div>
            <span>物料抛射轨迹</span>
        </div>
    </div>

    <!-- 顶部右侧控制台 -->
    <div class="info-panel top-right">
        <h2 class="title">动态参数控制</h2>
        <p class="desc" style="margin-bottom: 12px;">通过调节相位改变宏观系统属性</p>
        
        <div class="control-group">
            <div class="control-header">
                <span>振动方向角 (α)</span>
                <span class="value-display" id="angle-val">45°</span>
            </div>
            <input type="range" id="angle-slider" min="45" max="60" value="45" step="1">
        </div>
        
        <div class="control-group" style="margin-top: 16px;">
            <div class="control-header">
                <span>激振频率</span>
                <span class="value-display" style="color: var(--text-main);">1000 r/min</span>
            </div>
            <!-- 视觉演示,频率固定,不提供滑块以保持交互焦点 -->
        </div>
    </div>

    <!-- 核心 SVG 动画区域 -->
    <div id="animation-container">
        <svg viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid meet">
            <defs>
                <!-- 发光滤镜 -->
                <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="4" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="glow-strong" x="-30%" y="-30%" width="160%" height="160%">
                    <feGaussianBlur stdDeviation="8" result="blur" />
                    <feMerge>
                        <feMergeNode in="blur"/>
                        <feMergeNode in="blur"/>
                        <feMergeNode in="SourceGraphic"/>
                    </feMerge>
                </filter>

                <!-- 箭头标记 -->
                <marker id="arrow-blue" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
                    <path d="M 0 0 L 10 5 L 0 10 z" fill="var(--accent-blue)" />
                </marker>
                <marker id="arrow-orange" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
                    <path d="M 0 0 L 10 5 L 0 10 z" fill="var(--accent-orange)" />
                </marker>

                <!-- 网格背景图案 -->
                <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
                    <path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="0.5" />
                    <circle cx="0" cy="0" r="1" fill="var(--grid-color)" />
                </pattern>
                
                <!-- 筛网纹理 -->
                <pattern id="screen-mesh" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(15)">
                    <path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(0, 240, 255, 0.3)" stroke-width="1" />
                </pattern>
            </defs>

            <!-- 1. 背景层 -->
            <rect width="100%" height="100%" fill="url(#grid)" />
            
            <!-- 坐标系参考线 -->
            <g stroke="rgba(0, 240, 255, 0.2)" stroke-width="1" stroke-dasharray="4 4">
                <line x1="100" y1="350" x2="900" y2="350" />
                <line x1="500" y1="100" x2="500" y2="500" />
            </g>

            <!-- 2. 筛机结构层 -->
            <!-- 筛网床面 (倾斜展示排料) -->
            <g transform="translate(0, 0)">
                <path d="M 150 480 L 850 450 L 850 465 L 150 495 Z" fill="rgba(0, 100, 255, 0.1)" stroke="var(--text-main)" stroke-width="2" />
                <path d="M 150 480 L 850 450 L 850 465 L 150 495 Z" fill="url(#screen-mesh)" />
                <line x1="150" y1="480" x2="850" y2="450" stroke="var(--text-main)" stroke-width="3" filter="url(#glow)" />
            </g>

            <!-- 3. 物料/流体动力学层 (通过JS动态生成) -->
            <g id="particles-group" filter="url(#glow)"></g>
            <g id="trails-group" opacity="0.6"></g>

            <!-- 4. 激振器核心机构层 -->
            <g id="exciter-assembly" transform="translate(500, 350)">
                <!-- 机构外壳轮廓 -->
                <rect x="-120" y="-60" width="240" height="120" rx="15" fill="rgba(8, 12, 22, 0.9)" stroke="var(--text-muted)" stroke-width="2" />
                <path d="M -100 -40 L -100 40 M 100 -40 L 100 40" stroke="var(--border-color)" stroke-width="1" stroke-dasharray="2 2" />

                <!-- 左轴 (顺时针旋转) -->
                <g id="shaft-left" transform="translate(-50, 0)">
                    <circle r="35" fill="none" stroke="var(--border-color)" stroke-width="1" stroke-dasharray="4 2" />
                    <!-- 偏心块 -->
                    <path id="mass-left" d="M 0 -30 A 30 30 0 0 1 0 30 L 0 0 Z" fill="var(--text-muted)" opacity="0.7" />
                    <circle r="6" fill="var(--text-main)" filter="url(#glow)" />
                    <!-- 单轴力向量 -->
                    <line id="vec-left" x1="0" y1="0" x2="0" y2="-60" stroke="var(--accent-blue)" stroke-width="2" marker-end="url(#arrow-blue)" opacity="0.6" />
                </g>

                <!-- 右轴 (逆时针旋转) -->
                <g id="shaft-right" transform="translate(50, 0)">
                    <circle r="35" fill="none" stroke="var(--border-color)" stroke-width="1" stroke-dasharray="4 2" />
                    <!-- 偏心块 -->
                    <path id="mass-right" d="M 0 -30 A 30 30 0 0 0 0 30 L 0 0 Z" fill="var(--text-muted)" opacity="0.7" />
                    <circle r="6" fill="var(--text-main)" filter="url(#glow)" />
                    <!-- 单轴力向量 -->
                    <line id="vec-right" x1="0" y1="0" x2="0" y2="-60" stroke="var(--accent-blue)" stroke-width="2" marker-end="url(#arrow-blue)" opacity="0.6" />
                </g>

                <!-- 合成力向量基准线 (展示当前设定的方向角) -->
                <line id="angle-guideline" x1="-150" y1="150" x2="150" y2="-150" stroke="rgba(255, 170, 0, 0.3)" stroke-width="1" stroke-dasharray="5 5" />
                
                <!-- 合成力向量 (动态伸缩) -->
                <line id="vec-resultant" x1="0" y1="0" x2="120" y2="-120" stroke="var(--accent-orange)" stroke-width="4" marker-end="url(#arrow-orange)" filter="url(#glow-strong)" />
                
                <!-- 中心参考点 -->
                <circle cx="0" cy="0" r="3" fill="var(--accent-orange)" />
            </g>

            <!-- 角度标注弧线 -->
            <g transform="translate(500, 350)">
                <path id="angle-arc" d="M 80 0 A 80 80 0 0 0 56.5 -56.5" fill="none" stroke="var(--accent-orange)" stroke-width="1.5" opacity="0.8" />
                <text id="angle-text-svg" x="90" y="-30" fill="var(--accent-orange)" font-size="12" font-family="monospace">45°</text>
            </g>

        </svg>
    </div>

    <script>
        /**
         * 物理与渲染引擎
         */
        const slider = document.getElementById('angle-slider');
        const angleValDisplay = document.getElementById('angle-val');
        
        // SVG 元素引用
        const massLeft = document.getElementById('mass-left');
        const massRight = document.getElementById('mass-right');
        const vecLeft = document.getElementById('vec-left');
        const vecRight = document.getElementById('vec-right');
        const vecResultant = document.getElementById('vec-resultant');
        const angleGuideline = document.getElementById('angle-guideline');
        const angleArc = document.getElementById('angle-arc');
        const angleTextSvg = document.getElementById('angle-text-svg');
        const particlesGroup = document.getElementById('particles-group');
        const trailsGroup = document.getElementById('trails-group');

        // 系统状态
        let targetAngleDeg = parseFloat(slider.value); // 目标角度 (45-60)
        let currentAngleDeg = targetAngleDeg;          // 当前平滑过渡的角度
        let time = 0;                                  // 时间变量,驱动旋转
        const omega = 0.15;                            // 角速度 (模拟频率)
        
        // 几何常量
        const R = 30;     // 偏心块等效半径 (用于渲染)
        const F_max = 65; // 单轴力向量渲染最大长度
        const deckY = (x) => 480 - ((480 - 450) / (850 - 150)) * (x - 150); // 筛面方程 y = f(x)
        const deckAngle = Math.atan2(450 - 480, 850 - 150); // 筛面倾角 (-0.04 rad)

        // 粒子系统
        const particles = [];
        const MAX_PARTICLES = 35;
        let frameCount = 0;

        class Particle {
            constructor() {
                this.reset(true);
            }

            reset(initial = false) {
                // 起点在筛面左侧随机位置
                this.x = 160 + Math.random() * 50; 
                this.y = deckY(this.x);
                
                // 速度由当前的合成力方向(抛射角)决定
                // 角度越高(60°),Y分量越大,X分量越小
                const throwForce = 8 + Math.random() * 2; 
                const rad = currentAngleDeg * Math.PI / 180;
                
                this.vx = throwForce * Math.cos(rad);
                this.vy = -throwForce * Math.sin(rad); // SVG Y轴向下
                
                this.history = [];
                this.active = !initial || Math.random() > 0.5; // 错开初始发射时间
                this.color = Math.random() > 0.3 ? 'var(--accent-green)' : 'rgba(255, 255, 255, 0.7)';
                this.size = 1.5 + Math.random() * 2;
                
                if(!this.element) {
                    this.element = document.createElementNS("http://www.w3.org/2000/svg", "circle");
                    this.element.setAttribute("r", this.size);
                    this.element.setAttribute("fill", this.color);
                    particlesGroup.appendChild(this.element);
                    
                    this.trail = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
                    this.trail.setAttribute("fill", "none");
                    this.trail.setAttribute("stroke", this.color);
                    this.trail.setAttribute("stroke-width", "1");
                    this.trail.setAttribute("opacity", "0.5");
                    trailsGroup.appendChild(this.trail);
                }
            }

            update() {
                if (!this.active) return;

                this.history.push({x: this.x, y: this.y});
                if (this.history.length > 15) this.history.shift();

                // 物理运动: 抛物线
                this.x += this.vx;
                this.y += this.vy;
                this.vy += 0.35; // 重力加速度

                const currentDeckY = deckY(this.x);
                
                // 碰撞检测 (落回筛面)
                if (this.y >= currentDeckY) {
                    this.y = currentDeckY;
                    
                    // 模拟在筛面上受到新的激振力抛射
                    // 如果超过右边界,重置
                    if (this.x > 820) {
                        this.reset();
                    } else {
                        // 继续向前弹跳,弹跳角度跟随当前设定角
                        const throwForce = 7 + Math.random() * 2;
                        const rad = currentAngleDeg * Math.PI / 180;
                        this.vx = throwForce * Math.cos(rad);
                        this.vy = -throwForce * Math.sin(rad);
                    }
                }

                this.draw();
            }

            draw() {
                this.element.setAttribute("cx", this.x);
                this.element.setAttribute("cy", this.y);
                
                if (this.history.length > 1) {
                    const points = this.history.map(p => `${p.x},${p.y}`).join(" ");
                    this.trail.setAttribute("points", points);
                }
            }
        }

        // 初始化粒子
        for (let i = 0; i < MAX_PARTICLES; i++) {
            particles.push(new Particle());
        }

        // 事件监听
        slider.addEventListener('input', (e) => {
            targetAngleDeg = parseFloat(e.target.value);
            angleValDisplay.textContent = targetAngleDeg + '°';
        });

        /**
         * 核心动画循环
         */
        function animate() {
            time += omega;
            
            // 1. 平滑过渡角度 (视觉缓冲效果)
            currentAngleDeg += (targetAngleDeg - currentAngleDeg) * 0.1;
            const alpha = currentAngleDeg * Math.PI / 180; // 目标方向角弧度

            // 2. IFR 核心机制学计算: 
            // 为了使合力方向恒定为 alpha,两偏心块需满足特定相位关系。
            // 左轴(顺时针): theta1 = time + alpha
            // 右轴(逆时针): theta2 = -time + alpha
            // 这样在 time=0, pi, 2pi... 时,两者角度均为 alpha,合力最大且指向 alpha。
            const theta1 = time + alpha;
            const theta2 = -time + alpha;

            // 更新偏心块旋转
            const deg1 = theta1 * 180 / Math.PI;
            const deg2 = theta2 * 180 / Math.PI;
            massLeft.setAttribute('transform', `rotate(${deg1})`);
            massRight.setAttribute('transform', `rotate(${deg2})`);

            // 3. 计算并绘制单轴力向量 (力方向与偏心块质心方向一致)
            const f1_x = F_max * Math.cos(theta1);
            const f1_y = -F_max * Math.sin(theta1); // SVG Y反向
            vecLeft.setAttribute('x2', f1_x);
            vecLeft.setAttribute('y2', f1_y);

            const f2_x = F_max * Math.cos(theta2);
            const f2_y = -F_max * Math.sin(theta2);
            vecRight.setAttribute('x2', f2_x);
            vecRight.setAttribute('y2', f2_y);

            // 4. 计算并绘制合成力向量 (两单轴力之和)
            const res_x = f1_x + f2_x;
            const res_y = f1_y + f2_y;
            vecResultant.setAttribute('x2', res_x);
            vecResultant.setAttribute('y2', res_y);

            // 5. 更新辅助线和角度标注
            // 辅助线长度固定,方向为当前 alpha
            const guide_L = 150;
            angleGuideline.setAttribute('x1', -guide_L * Math.cos(alpha));
            angleGuideline.setAttribute('y1', guide_L * Math.sin(alpha));
            angleGuideline.setAttribute('x2', guide_L * Math.cos(alpha));
            angleGuideline.setAttribute('y2', -guide_L * Math.sin(alpha));

            // 绘制角度圆弧 (从0度到alpha度)
            const arcR = 80;
            const arcX = arcR * Math.cos(alpha);
            const arcY = -arcR * Math.sin(alpha);
            angleArc.setAttribute('d', `M ${arcR} 0 A ${arcR} ${arcR} 0 0 0 ${arcX} ${arcY}`);
            
            angleTextSvg.textContent = Math.round(currentAngleDeg) + '°';
            angleTextSvg.setAttribute('x', arcX + 10);
            angleTextSvg.setAttribute('y', arcY - 10);

            // 6. 更新粒子系统 (物料轨迹)
            frameCount++;
            if (frameCount % 5 === 0) {
                // 周期性激活休眠的粒子,形成连续流
                const inactive = particles.find(p => !p.active);
                if (inactive) inactive.active = true;
            }
            
            particles.forEach(p => p.update());

            requestAnimationFrame(animate);
        }

        // 启动动画 (DOMContentLoaded/Load后自动启动,符合要求)
        requestAnimationFrame(animate);

    </script>
</body>
</html>
积分规则:每轮对话扣10分