新增镜面后光源及所有镜面坐标重置问题求助
问题:新增镜面后光源与所有镜面坐标重置
我用HTML、JavaScript开发简易光反射模型,UI部分已完成,但遇到问题:调整光源和镜面位置/角度后,新增镜面时,所有控件值和对应坐标都会重置为初始值。页面里的range输入框和text输入框双向同步,怀疑是输入框值处理逻辑有问题,排查很久没解决。
问题根源
- innerHTML重写丢失原有状态:新增镜面时用
attributes.innerHTML += ...拼接HTML,会重新渲染整个容器,原有输入框的修改值被初始值覆盖,同时之前绑定的事件监听全部丢失。 - 事件绑定仅执行一次:页面加载时只给当时存在的输入框绑定了双向同步事件,新增的输入框没有绑定事件,无法同步值。
- 依赖DOM索引读取值:
updateSource和updateMirrors通过DOM元素索引获取值,新增元素后DOM结构变化,索引对应关系混乱,导致值读取错误。
修复方案
- 用appendChild替代innerHTML拼接:创建新的DOM元素并设置初始值后添加到容器,保留原有输入框的状态和事件。
- 事件委托处理双向同步:给属性容器绑定事件,通过事件冒泡处理所有输入框的change事件,无需给每个新元素单独绑定。
- 绑定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"> Source Y: <input type="text" value="50" class="source y-display" style="width:50px;"> <input type="range" class="source y" value="50"> 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(" Mirror${index} Y: ")); mirrorControls.appendChild(yDisplay); mirrorControls.appendChild(document.createTextNode(" ")); mirrorControls.appendChild(yRange); mirrorControls.appendChild(document.createTextNode(" 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
相关产品推荐
相关产品推荐

