Я пытаюсь создать облако слов. Чтобы отобразить текст на экране, я генерирую случайную позицию для каждого слова. Это работает отлично, однако есть много перекрывающихся слов. Чтобы решить эту проблему, я сохраняю положение и размер элементов в массиве, а затем создал вспомогательную функцию, которая проверяет наличие столкновений, создает новую позицию для элемента, если он ее находит, а затем снова вызывает себя, чтобы проверить снова с начала массива. Когда я запускаю свой код, первые 2-3 слова отображаются нормально, но затем я получаю сообщение об ошибке, говорящее о том, что превышен максимальный размер стека вызовов. Я видел, что уже было сообщение об этой такой же проблеме с переполнением стека.
Я увидел, что другой человек использует функцию 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, из которого вы вызываете метод, просто продолжает работать! Попробуйте добавить «перерыв»; после каждого рекурсивного вызова checkForCollisions. Это уже должно уменьшить количество петель.
checkForCollisions
может просто вернуть значение независимо от того, есть ли коллизия, и вызывающая функция может обрабатывать вычисление новой и вызывать ее снова, это позволит избежать проблемы с глубиной стека. Однако ваш код, кажется, имеет коллизию, если элемент находится в той же вертикальной или горизонтальной плоскости, что и другой элемент, но не обязательно в обоих. Я не знаю, так задумано или нет.Спасибо, что указали на это @IllusiveBrian. Это было непреднамеренно. Я обновлю код.
Я обновил код и изменил функцию checkForCollisions, чтобы она возвращала true, если высота и ширина находились в пределах диапазона существующего элемента, и возвращала false в противном случае. В функции карты, которая отображает каждый элемент, я установил цикл while, чтобы проверить, есть ли какие-либо перекрывающиеся элементы. Я думал, что это сработает, и на самом деле это уменьшило количество перекрывающихся слов, но все же есть несколько слов, которые перекрываются. Я думал, что это проблема с пространством, но в родительском div еще достаточно места для перемещения слов. Я обновил jsFiddle новым кодом.