【发布时间】:2021-10-12 18:12:41
【问题描述】:
我正在尝试创建一个词云。为了将文本呈现到屏幕上,我为每个单词生成一个随机位置。这很有效,但是有很多重叠的单词。为了解决这个问题,我将元素的位置和大小存储在一个数组中,然后我创建了一个辅助函数来检查冲突,如果找到,则为元素生成一个新位置,然后再次调用它自身进行检查再次从数组的开头。当我运行我的代码时,前 2-3 个单词呈现得很好,但随后我收到一条错误消息,提示超出最大调用堆栈大小。我看到这个same issue on stack overflow已经有帖子了。
我看到另一个人正在使用一个 forEach 函数,我也是,所以我将它转换为一个 for 循环,就像答案所建议的那样,但它没有做任何事情。我认为这个问题归结为这样一个事实,即有这么多的冲突,但我不知道如何最好地解决这个问题。是否有另一种方法可以为元素生成唯一位置,同时仍然避免碰撞?
代码:
function calculatePosition(parent, child) {
return Math.random() * parent - (child / 2)
}
// needed for rendering position of span elements
var ranges = []
var totalWidthOfWords = 0
var totalHeightOfWords = 0
// reposition element if there is a collision
function checkForCollisions(element, height, width, wordCloud, injectedSpan) {
for(var i = 0; i < ranges.length; i++) {
let current = ranges[i]
if(element.left >= current.width[0] && element.left <= current.width[1]) {
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, width) + "px";
checkForCollisions(element, height, width, wordCloud, injectedSpan)
}
if(element.top >= current.height[0] && element.top <= current.height[1]) {
injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, height) + "px";
checkForCollisions(element, height, width, wordCloud, injectedSpan)
}
}
}
// Create content in DOM
const injectedContent = data.map(line => {
const injectedSpan = document.createElement("span")
const injectedWord = document.createElement("p")
const wordCloud = document.querySelector(".word-cloud")
// mod weight value to get more managable inputs
let weightValue = (line.weight * 100).toFixed(2)
// sets values of words and renders them to the screen
injectedWord.innerText = line.word
injectedSpan.appendChild(injectedWord)
wordCloud.appendChild(injectedSpan)
// sets style attribute based on weight value
injectedWord.setAttribute("style", `--i: ${weightValue}`)
// flips words
if(Math.random() > 0.75) {
injectedWord.style.writingMode = "vertical-rl";
}
// Entrance animation
let left = innerWidth * Math.random()
let top = innerHeight * Math.random()
if(Math.random() < 0.5) {
injectedWord.style.left = "-" + left + "px";
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
} else {
injectedWord.style.left = left + "px";
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
}
if(Math.random() < 0.5) {
injectedWord.style.top = "-" + top + "px";
injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, injectedSpan.clientHeight) + "px";
} else {
injectedWord.style.top = top + "px";
injectedSpan.style.top = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
}
// Get position of span and change coordinites if there is a collision
let spanPosition = injectedSpan.getBoundingClientRect()
console.log(spanPosition)
if(spanPosition) {
checkForCollisions(spanPosition, spanPosition.height, spanPosition.width, wordCloud, injectedSpan)
}
totalWidthOfWords += spanPosition.width
totalHeightOfWords += spanPosition.height
ranges.push({width: [spanPosition.left, spanPosition.right], height: [spanPosition.top, spanPosition.bottom]})
})
【问题讨论】:
-
每次发生碰撞时,您都会更新元素的位置,然后调用 checkForCollisions,我认为这很好,但是您从中调用该方法的 for 循环只会继续运行!尝试添加一个“break;”在每个递归 checkForCollisions 调用之后。这应该已经减少了循环的数量。
-
checkForCollisions可以只返回是否存在冲突,调用函数可以处理计算一个新的并再次调用它,这将避免您的堆栈深度问题。但是,如果一个元素与另一个元素位于相同的垂直或水平平面,但不一定同时存在,那么您的代码似乎会发生冲突。我不知道这是不是有意的。 -
感谢您指出@IllusiveBrian。那不是故意的。我会更新代码。
-
我更新了代码并更改了 checkForCollisions 函数,如果高度和宽度在现有元素的范围内,则返回 true,否则返回 false。在渲染每个元素的 map 函数中,我设置了一个 while 循环来检查是否有任何重叠元素。我认为这会起作用,它实际上确实减少了重叠单词的数量,但仍有一些单词重叠。我认为这是一个空间问题,但父 div 中仍有足够的空间来移动单词。我用新代码更新了 jsFiddle。
标签: javascript recursion