Canvas动画报错:Uncaught TypeError: Cannot read properties of undefined (reading '310')
隧道渲染函数索引越界问题修复
问题场景
我正在开发隧道效果Demo,尝试让隧道在图像内移动,但渲染函数始终报错。报错触发条件为:读取distanceTable或angleTable时,第一个索引不是y(哪怕只是y加某个值)就会报错,第二个索引似乎不受影响。
相关代码:
function draw(time) { let animation = time / 1000.0; let shiftX = ~~(texWidth * animation); let shiftY = ~~(texHeight * 0.25 * animation); let shiftLookX = (screenWidth / 2) + ~~(screenWidth / 2 * Math.sin(animation)) let shiftLookY = (screenHeight / 2) + ~~(screenHeight / 2 * Math.sin(animation)) for (y = 0; y < buffer.height; y++) { for (x = 0; x < buffer.width; x++) { let id = (y * buffer.width + x) * 4; let d = ~~(distanceTable[y + shiftLookY][x + shiftLookX] + shiftX) % texWidth; let a = ~~(angleTable[y + shiftLookY][x + shiftLookX] + shiftY) % texHeight; let tex = (a * texture.width + d) * 4; buffer.data[id] = texture.data[tex]; buffer.data[id+1] = texture.data[tex+1]; buffer.data[id+2] = texture.data[tex+2]; buffer.data[id+3] = texture.data[tex+3]; } } ctx.putImageData(buffer, 0, 0); window.requestAnimationFrame(draw); }
错误原因
错误的核心是行索引越界:
y的取值范围是0到buffer.height - 1,而shiftLookY的计算值范围是0到screenHeight(当Math.sin(animation)取1时,shiftLookY = screenHeight/2 + screenHeight/2 = screenHeight)。当y接近buffer.height时,y + shiftLookY会超过distanceTable和angleTable的行数(这两个表的高度通常与屏幕/缓冲高度一致),此时distanceTable[y + shiftLookY]返回undefined,再尝试访问其列索引[x + shiftLookX]就会抛出“Cannot read properties of undefined”的错误。- 你观察到第二个索引不受影响,是因为错误在访问行的阶段就已经触发,未执行到列索引的越界检查;或者列索引的计算值刚好在表的宽度范围内,暂时未暴露问题。
解决方案
对行和列索引进行边界约束,确保它们始终在数组的有效范围内。可以选择两种方式:
- 取模循环:让索引超出范围后循环回到数组开头(适合隧道循环移动的视觉效果)
- 截断(Clamp):让索引固定在数组的最小/最大值上(避免索引越界,但视觉上会有边缘截断)
方式1:取模循环修复代码
function draw(time) { let animation = time / 1000.0; let shiftX = ~~(texWidth * animation); let shiftY = ~~(texHeight * 0.25 * animation); let shiftLookX = (screenWidth / 2) + ~~(screenWidth / 2 * Math.sin(animation)) let shiftLookY = (screenHeight / 2) + ~~(screenHeight / 2 * Math.sin(animation)); // 获取表的行列边界 const tableRows = distanceTable.length; const tableCols = distanceTable[0].length; for (let y = 0; y < buffer.height; y++) { for (let x = 0; x < buffer.width; x++) { let id = (y * buffer.width + x) * 4; // 计算行索引,取模确保在有效范围内,处理负数情况 let row = (y + shiftLookY) % tableRows; if (row < 0) row += tableRows; // 计算列索引,同理处理 let col = (x + shiftLookX) % tableCols; if (col < 0) col += tableCols; let d = ~~(distanceTable[row][col] + shiftX) % texWidth; let a = ~~(angleTable[row][col] + shiftY) % texHeight; let tex = (a * texture.width + d) * 4; buffer.data[id] = texture.data[tex]; buffer.data[id+1] = texture.data[tex+1]; buffer.data[id+2] = texture.data[tex+2]; buffer.data[id+3] = texture.data[tex+3]; } } ctx.putImageData(buffer, 0, 0); window.requestAnimationFrame(draw); }
方式2:截断(Clamp)修复代码
将上述代码中的索引计算部分替换为:
let row = Math.max(0, Math.min(y + shiftLookY, tableRows - 1)); let col = Math.max(0, Math.min(x + shiftLookX, tableCols - 1));
内容的提问来源于stack exchange,提问作者Matthew Miller
相关产品推荐
相关产品推荐

