内联脚本调用正常,外部app.js调用D3.js碰撞函数报bottom属性错误
SVG文本碰撞避免功能的严格模式错误解决
问题场景
实现SVG文本元素碰撞避免时,内联脚本中的collision()函数运行正常,但将相同逻辑放到外部app.js文件,通过按钮事件监听器调用collisionAvoid()时,抛出错误:
TypeError: Cannot set property bottom of # which has only a getter
错误原因
getBoundingClientRect()返回的DOMRect是只读对象,无法直接修改它的bottom、top等属性:
- 内联脚本未启用严格模式,浏览器会静默忽略对只读属性的修改,因此未报错;
- 外部
app.js开头声明了"use strict";,严格模式下会直接抛出属性修改错误。
解决方案
创建自定义的可修改对象,存储文本元素的位置信息,代替只读的DOMRect。更新文本的y属性后,同步更新自定义对象的对应值,用于后续碰撞检测。
修改后的代码
外部app.js代码
"use strict"; var bt = document.getElementById('btn'); bt.addEventListener('click', function(e){ collisionAvoid(); }, false); function collisionAvoid(){ const svg = d3.select("svg"); let hasCollisions = true; while (hasCollisions) { hasCollisions = false; const textElements = svg.selectAll("text"); textElements.each(function () { const textElement = d3.select(this); // 创建自定义可修改的位置对象,复制DOMRect的属性 const rect = { top: this.getBoundingClientRect().top, bottom: this.getBoundingClientRect().bottom }; textElements.each(function () { if (this !== textElement.node()) { const otherRect = this.getBoundingClientRect(); if (rect.bottom > otherRect.top && rect.top < otherRect.bottom) { const yOffset = Math.max(0, otherRect.bottom - rect.top + 5); // 添加间距 textElement.attr("y", parseFloat(textElement.attr("y")) + yOffset); // 更新自定义对象的属性,而非只读的DOMRect rect.bottom += yOffset; rect.top += yOffset; hasCollisions = true; } } }); }); } }
内联脚本(可选优化,统一规范)
function collision(){ const svg = d3.select("svg"); let hasCollisions = true; while (hasCollisions) { hasCollisions = false; const textElements = svg.selectAll("text"); textElements.each(function () { const textElement = d3.select(this); // 同样使用自定义可修改对象 const rect = { top: this.getBoundingClientRect().top, bottom: this.getBoundingClientRect().bottom }; textElements.each(function () { if (this !== textElement.node()) { const otherRect = this.getBoundingClientRect(); if (rect.bottom > otherRect.top && rect.top < otherRect.bottom) { const yOffset = Math.max(0, otherRect.bottom - rect.top + 5); // 添加间距 textElement.attr("y", parseFloat(textElement.attr("y")) + yOffset); rect.bottom += yOffset; rect.top += yOffset; hasCollisions = true; } } }); }); } }
内容的提问来源于stack exchange,提问作者corsaro
相关产品推荐
相关产品推荐

