You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

新增镜面后光源及所有镜面坐标重置问题求助

问题:新增镜面后光源与所有镜面坐标重置

我用HTML、JavaScript开发简易光反射模型,UI部分已完成,但遇到问题:调整光源和镜面位置/角度后,新增镜面时,所有控件值和对应坐标都会重置为初始值。页面里的range输入框和text输入框双向同步,怀疑是输入框值处理逻辑有问题,排查很久没解决。

问题根源

  1. innerHTML重写丢失原有状态:新增镜面时用attributes.innerHTML += ...拼接HTML,会重新渲染整个容器,原有输入框的修改值被初始值覆盖,同时之前绑定的事件监听全部丢失。
  2. 事件绑定仅执行一次:页面加载时只给当时存在的输入框绑定了双向同步事件,新增的输入框没有绑定事件,无法同步值。
  3. 依赖DOM索引读取值:updateSource和updateMirrors通过DOM元素索引获取值,新增元素后DOM结构变化,索引对应关系混乱,导致值读取错误。

修复方案

  1. 用appendChild替代innerHTML拼接:创建新的DOM元素并设置初始值后添加到容器,保留原有输入框的状态和事件。
  2. 事件委托处理双向同步:给属性容器绑定事件,通过事件冒泡处理所有输入框的change事件,无需给每个新元素单独绑定。
  3. 绑定Mirror实例与DOM元素:创建Mirror时保存对应输入框的引用,避免通过索引查找导致的错误。

修改后的HTML

<html>
    <head>
        <title>Reflection</title>
    </head>
    <body>
        <div style="position:relative;" id="attributes">
            Source X: <input type="text" value="50" class="source x-display" style="width:50px;"> <input type="range" class="source x" value="50">
            &nbsp;Source Y: <input type="text" value="50" class="source y-display" style="width:50px;"> <input type="range" class="source y" value="50">
            &nbsp;Source Angle: <input type="text" value="0" class="source angle-display" style="width:50px;"> <input type="range" class="source angle" value="0"> <br>
        </div> <br>
        <button id="new-straight-mirror">New Straight Mirror</button>
        <script src="script.js"></script>
    </body>
</html>

修改后的JavaScript

const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const source = vector(50, 50);
const mirrors = [];
const newStraightMirror = document.getElementById("new-straight-mirror");
const attributesContainer = document.getElementById("attributes");

canvas.width = window.innerWidth - 50;
canvas.height = window.innerHeight - 50;

document.body.insertBefore(canvas, null);

// 初始化第一个镜面
mirrors.push(mirror(vector(300, 300), 0, 100, mirrors.length + 1));

newStraightMirror.addEventListener("click", e => {
    mirrors.push(mirror(vector(300, 300), 0, 100, mirrors.length + 1));
});

function mirror(position, angle, length, index, inverted) {
    // 创建容器存放当前镜面的控件
    const mirrorControls = document.createElement("div");
    
    // 创建各个输入框
    const xDisplay = document.createElement("input");
    xDisplay.type = "text";
    xDisplay.value = position.x;
    xDisplay.className = `mirror ${index} x-display`;
    xDisplay.style.width = "50px";

    const xRange = document.createElement("input");
    xRange.type = "range";
    xRange.value = position.x;
    xRange.className = `mirror ${index} x`;

    const yDisplay = document.createElement("input");
    yDisplay.type = "text";
    yDisplay.value = position.y;
    yDisplay.className = `mirror ${index} y-display`;
    yDisplay.style.width = "50px";

    const yRange = document.createElement("input");
    yRange.type = "range";
    yRange.value = position.y;
    yRange.className = `mirror ${index} y`;

    const angleDisplay = document.createElement("input");
    angleDisplay.type = "text";
    angleDisplay.value = angle;
    angleDisplay.className = `mirror ${index} angle-display`;
    angleDisplay.style.width = "50px";

    const angleRange = document.createElement("input");
    angleRange.type = "range";
    angleRange.value = angle;
    angleRange.className = `mirror ${index} angle`;

    // 组装控件文本与元素
    mirrorControls.innerHTML = `Mirror${index} X: `;
    mirrorControls.appendChild(xDisplay);
    mirrorControls.appendChild(document.createTextNode(" "));
    mirrorControls.appendChild(xRange);
    mirrorControls.appendChild(document.createTextNode(" &nbsp;Mirror${index} Y: "));
    mirrorControls.appendChild(yDisplay);
    mirrorControls.appendChild(document.createTextNode(" "));
    mirrorControls.appendChild(yRange);
    mirrorControls.appendChild(document.createTextNode(" &nbsp;Mirror${index} Angle: "));
    mirrorControls.appendChild(angleDisplay);
    mirrorControls.appendChild(document.createTextNode(" "));
    mirrorControls.appendChild(angleRange);
    mirrorControls.appendChild(document.createElement("br"));

    // 添加到属性容器
    attributesContainer.appendChild(mirrorControls);

    return new class Mirror {
        constructor() {
            this.position = position;
            this.angle = angle;
            this.length = length;
            this.inverted = !!inverted;
            // 保存对应输入框引用
            this.elements = {
                xDisplay,
                xRange,
                yDisplay,
                yRange,
                angleDisplay,
                angleRange
            };
            // 设置range范围
            this.updateRangeLimits();
        }

        updateRangeLimits() {
            this.elements.xRange.min = 0;
            this.elements.xRange.max = canvas.width;
            this.elements.xRange.style.width = "200px";
            this.elements.yRange.min = 0;
            this.elements.yRange.max = canvas.height;
            this.elements.yRange.style.width = "200px";
            this.elements.angleRange.min = 0;
            this.elements.angleRange.max = 360;
        }
    }
}

function vector(x, y) {
    return new class Vector {
        constructor() {
            this.moveTo(x, y);
        }

        moveTo(x, y) {
            this.x = x;
            this.y = y;
        }
    }
}

// 修复循环逻辑:避免嵌套调用导致的重复执行
function loop(cb) {
    function tick() {
        cb();
        requestAnimationFrame(tick);
    }
    requestAnimationFrame(tick);
}

function draw() {
    drawSource();
    drawMirrors();
}

function drawSource() {
    ctx.beginPath();
    ctx.fillStyle = "black";
    ctx.arc(source.x, source.y, 3, 0, 2*Math.PI);
    ctx.fill();
    ctx.closePath();
}

function drawMirrors() {
    ctx.lineWidth = 5; // 修复strokeWidth拼写错误
    for(let i = 0; i < mirrors.length; i++) {
        let mirror = mirrors[i];
        ctx.beginPath();
        ctx.moveTo(mirror.position.x, mirror.position.y);
        const endX = mirror.position.x + mirror.length * Math.cos(mirror.angle * Math.PI/180);
        const endY = mirror.position.y - mirror.length * Math.sin(mirror.angle * Math.PI/180);
        ctx.lineTo(endX, endY);
        ctx.stroke();
        ctx.closePath();
    }
}

function update() {
    updateSource();
    updateMirrors();
}

function updateSource() {
    const xDisplay = document.querySelector(".source.x-display");
    const xRange = document.querySelector(".source.x");
    const yDisplay = document.querySelector(".source.y-display");
    const yRange = document.querySelector(".source.y");

    // 同步值并处理非法输入
    const xVal = parseInt(xDisplay.value) || 50;
    const yVal = parseInt(yDisplay.value) || 50;

    xRange.value = xVal;
    yRange.value = yVal;

    source.moveTo(xVal, yVal);
}

function updateMirrors() {
    mirrors.forEach(mirror => {
        const xVal = parseInt(mirror.elements.xDisplay.value) || mirror.position.x;
        const yVal = parseInt(mirror.elements.yDisplay.value) || mirror.position.y;
        const angleVal = parseInt(mirror.elements.angleDisplay.value) || mirror.angle;

        // 同步range值
        mirror.elements.xRange.value = xVal;
        mirror.elements.yRange.value = yVal;
        mirror.elements.angleRange.value = angleVal;

        mirror.position.x = xVal;
        mirror.position.y = yVal;
        mirror.angle = angleVal;
    });
}

loop(() => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    update();
    draw();
});

function distance(vector1, vector2) {
    return Math.sqrt((vector1.x-vector2.x)**2+(vector1.y-vector2.y)**2);
}

// 事件委托处理双向同步
attributesContainer.addEventListener("change", e => {
    const target = e.target;
    const parent = target.parentElement;
    const mirrorClass = target.classList[1];

    if (target.classList.contains("x-display")) {
        parent.querySelector(`.${mirrorClass}.x`).value = target.value;
    } else if (target.classList.contains("y-display")) {
        parent.querySelector(`.${mirrorClass}.y`).value = target.value;
    } else if (target.classList.contains("angle-display")) {
        parent.querySelector(`.${mirrorClass}.angle`).value = target.value;
    } else if (target.classList.contains("x")) {
        parent.querySelector(`.${mirrorClass}.x-display`).value = target.value;
    } else if (target.classList.contains("y")) {
        parent.querySelector(`.${mirrorClass}.y-display`).value = target.value;
    } else if (target.classList.contains("angle")) {
        parent.querySelector(`.${mirrorClass}.angle-display`).value = target.value;
    }
});

内容的提问来源于stack exchange,提问作者Zayaan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 10:50:29