【问题标题】:Reducing the number of times a helper function is run减少辅助函数的运行次数
【发布时间】: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]})
})

链接:https://jsfiddle.net/amotor/mdg7rzL1/4/

【问题讨论】:

  • 每次发生碰撞时,您都会更新元素的位置,然后调用 checkForCollisions,我认为这很好,但是您从中调用该方法的 for 循环只会继续运行!尝试添加一个“break;”在每个递归 checkForCollisions 调用之后。这应该已经减少了循环的数量。
  • checkForCollisions 可以只返回是否存在冲突,调用函数可以处理计算一个新的并再次调用它,这将避免您的堆栈深度问题。但是,如果一个元素与另一个元素位于相同的垂直或水平平面,但不一定同时存在,那么您的代码似乎会发生冲突。我不知道这是不是有意的。
  • 感谢您指出@IllusiveBrian。那不是故意的。我会更新代码。
  • 我更新了代码并更改了 checkForCollisions 函数,如果高度和宽度在现有元素的范围内,则返回 true,否则返回 false。在渲染每个元素的 map 函数中,我设置了一个 while 循环来检查是否有任何重叠元素。我认为这会起作用,它实际上确实减少了重叠单词的数量,但仍有一些单词重叠。我认为这是一个空间问题,但父 div 中仍有足够的空间来移动单词。我用新代码更新了 jsFiddle。

标签: javascript recursion


【解决方案1】:

要确保它正常工作,还有很多工作要做,尤其是要确保代码不会产生任何错误!

一般的想法是遵循 IllsuiveBrian 的评论,以确保 checkForCollision 仅执行检查是否存在碰撞的工作,并且另一个函数负责在必要时重新计算位置,然后重新评估潜在的碰撞。

function checkForCollisions(element, wordCloud, injectedSpan) {
   for(var i = 0; i < ranges.length; i++) {
     let current = ranges[i];
     // return true if there is a collision (you probably have to update the code you are using here to truly avoid collisions!)
     if (collision) { return true; }
   }
    return false; // return false otherwise
}

最后,这部分将负责重新计算位置并重新检查碰撞:

ranges.forEach(function(injectedSpan) {

    // Get position of span and change coordinites if there is a collision
    let spanPosition = injectedSpan.getBoundingClientRect();
    if (spanPosition) {
        while (checkForCollisions(spanPosition, wordCloud, injectedSpan)) {
            injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, element.width) + "px";
            injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, element.height) + "px";
    }
 }

});

这里有一个关于如何进入这个方向的快速想法:https://jsfiddle.net/euvbax1r/4/

【讨论】:

  • 谢谢。我确实更新了我的代码,并且能够将单词呈现到屏幕上,但单词云仍然呈现重叠的单词。我的 checkForCollisions 函数现在只接受一个参数,它是 injectionSpan 并循环遍历范围数组并比较宽度和高度。如果它们都在范围内,则返回 true。如果不是,则返回 false。我还想我可以放弃 forEach 函数,只提取 while 循环。我认为这会运行,找到任何碰撞,然后再次检查。我更新了上面链接的 jsFiddle 上的代码
  • 我很高兴它至少对崩溃有所帮助!不幸的是,您上面帖子中的 jsFiddle 链接似乎没有更新?
  • 显然,当我保存旧文件时,它会生成一个新链接。对此感到抱歉,但我更新了原始帖子中的链接
猜你喜欢
  • 2021-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-12
  • 2014-01-23
  • 2021-05-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多