Случайным образом разместите Div на веб-странице и убедитесь, что они не перекрываются

В настоящее время я работаю над проектом, для которого я хочу, чтобы различные ссылки (элементы div) размещались на моей веб-странице случайным образом. Для этого я сначала генерирую набор координат, а затем проверяю, перекрываются ли они (SO thread, пользователь @Robson помог мне во всем разобраться).

Однако, поскольку это мой первый проект JavaScript, длина которого превышает 15 строк кода, я уверен, что пропустил некоторые передовые практики или просто использовал слишком сложный подход. Я счастлив узнать о своих ошибках и о том, как создать более эффективный или чистый код!

Может быть, я смогу немного описать свой мыслительный процесс:

  • getMaxDimension: Чтобы блоки div не переполняли сайт, мне нужно только порождать их в координатах, достаточно далеко от правой и нижней границы экрана, поскольку координаты вычисляются из верхнего левого угла.
  • getOffset: Чтобы проверить, есть ли наложение двух div, я проверяю, находится ли какая-либо точка многоугольника div a внутри div b. Для этого я вычисляю все координаты и сохраняю их в объекте.
  • getOverlap: в основном просто проверяет, находятся ли точки div a и div b друг в друге. Код кажется довольно сложным, но это всего лишь реализация математической записи.
  • getChar: (Я действительно думаю, что есть способы сделать это лучше); Чтобы управлять каждым div однозначно, всем им нужны разные идентификаторы. Однако, id=1, id=2 и так далее не получилось (может не разрешено?). Теперь, чтобы «конвертировать» итерационные переменные i и j в соответствующий div я просто выбрал буквы.

MWE:

 // Returns largest div's width and height
 function getMaxDimension(arr) {
   var maxWidth = 0;
   for (var i = 0; i < div_selection.length; i++) {
     if (div_selection[i].offsetWidth > maxWidth) {
       maxWidth = div_selection[i].offsetWidth;
     }
   }
   var maxHeight = 0;
   for (var i = 0; i < div_selection.length; i++) {
     if (div_selection[i].offsetHeight > maxHeight) {
       maxHeight = div_selection[i].offsetHeight;
     }
   }
   var values = {
     maxWidth: maxWidth,
     maxHeight: maxHeight
   };
   return values;
 }

 // Retruns a random number x; min < x < max
 function getRandomNumber(min, max) {
   return Math.random() * (max - min) + min;
 }

 // returns the position in xy-space of every corner of a rectangular div
 function getOffset(element) {
   var position_x = element.offsetLeft;
   var position_y = element.offsetTop;
   var height_x = element.offsetWidth;
   var height_y = element.offsetHeight;
   var tolerance = 0; // will get doubled
   return {
     A: {
       y: position_y - tolerance,
       x: position_x - tolerance
     },
     B: {
       y: position_y + height_x + tolerance,
       x: position_x - tolerance
     },
     C: {
       y: position_y + height_x + tolerance,
       x: position_x + height_y + tolerance
     },
     D: {
       y: position_y - tolerance,
       x: position_x + height_y + tolerance
     }
   };
 }


 // Returns true if any corner is inside the coordinates of the other div
 function getOverlap(div1, div2) {
   coor_1 = getOffset(document.getElementById(div1));
   coor_2 = getOffset(document.getElementById(div2));
   return (
     (coor_1.A.x <= coor_2.A.x && coor_2.A.x <= coor_1.D.x) && (coor_1.A.y <= coor_2.A.y && coor_2.A.y <= coor_1.B.y) ||
     (coor_1.A.x <= coor_2.B.x && coor_2.B.x <= coor_1.D.x) && (coor_1.A.y <= coor_2.B.y && coor_2.B.y <= coor_1.B.y) ||
     (coor_1.A.x <= coor_2.C.x && coor_2.C.x <= coor_1.D.x) && (coor_1.A.y <= coor_2.C.y && coor_2.C.y <= coor_1.B.y) ||
     (coor_1.A.x <= coor_2.D.x && coor_2.D.x <= coor_1.D.x) && (coor_1.A.y <= coor_2.D.y && coor_2.D.y <= coor_1.B.y)
   );
 }

 // Number to Letter
 function getChar(n) {
   var ordA = 'a'.charCodeAt(0);
   var ordZ = 'z'.charCodeAt(0);
   var len = ordZ - ordA + 1;

   var s = "";
   while (n >= 0) {
     s = String.fromCharCode(n % len + ordA) + s;
     n = Math.floor(n / len) - 1;
   }
   return s;
 }

 var div_selection = document.getElementsByClassName("random");

 maxDimensions = getMaxDimension(div_selection);
 var widthBoundary = maxDimensions.maxWidth;
 var heightBoundary = maxDimensions.maxHeight;

 for (var i = 0; i < div_selection.length; i++) {
   var isOverlapping = false;
   var attemptCount = 0;
   do {
     randomLeft = getRandomNumber(0, window.innerWidth - widthBoundary);
     randomTop = getRandomNumber(0, window.innerHeight - heightBoundary);
     div_selection[i].style.left = randomLeft + "px";
     div_selection[i].style.top = randomTop + "px";     
     isOverlapping = false;
     for (var j = 0; j < i; j++) {
        if (getOverlap(getChar(i), getChar(j))) {
        isOverlapping = true;
        break;
      }
     }
   } while (++attemptCount < 50 && isOverlapping);
 }

 // check every element
 for (var i = 0; i < div_selection.length; i++) {
   for (var j = i + 1; j < div_selection.length; j++) {
     console.log(i, j)
     console.log(getChar(i), getChar(j))
     console.log(getOverlap(getChar(i), getChar(j)))
   }
 }
div {
  width: 60px;
  height: 60px;
  position: absolute;
}

#a {
  background-color: pink;
}

#b {
  background-color: lightblue;
}

#c {
  background-color: lightgreen;
}

#d {
  background-color: silver;
}

#e {
  background-color: yellow;
}
<html>
  <body>
    <div class="random" id="a">Div1</div>
    <div class="random" id="b">Div2</div>
    <div class="random" id="c">Div3</div>
    <div class="random" id="d">Div4</div>
    <div class="random" id="e">Div5</div>
  </body>
</html>

1 ответ
1

Стиль

Некоторые моменты стайлинга

  • Имена. У тебя плохое именование

    • Соглашение об именах JavaScript: camelCase избегать использования snake_case
    • Используйте контекст функции, чтобы придать дополнительный смысл. Например, у вас есть maxWidth и maxHeight в функции, называемой getMaxDimension. Поскольку они единственные переменные (начиная с i) объявлен и как таковой может быть просто width и height
    • getRandomNumber может быть randomNumber или randomNum или randNum
    • arr Избегайте именования переменных после их типа. arr может быть elements. Также всегда старайтесь называть массивы или объекты, подобные массивам, множественным числом.
  • Не повторяйте код. Две петли в getMaxDimension может быть всего одна петля.

  • Не добавляйте неиспользуемый код. Переменная arr в getMaxDimension(arr) никогда не используется. Вы получаете доступ к массиву элементов из его глобального имени. div_selection

  • Избегайте одноразовых переменных, если они не помогают уменьшить количество загроможденных длинных строк.

  • При нахождении только максимального значения используйте Math.max а не оператор if. То же самое с минимумами Math.min

  • Комментарии не должны констатировать очевидное. Если вам необходимо добавить комментарии, убедитесь, что они грамматически правильные.

    НЕ ДОБАВЛЯЙТЕ комментарии, которые напрямую конфликтуют с кодом, который они комментируют. У вас есть // Retruns a random number x; min < x < max но функция возвращает min <= x < max Любой, кто читает этот код, не поймет ваших намерений.

  • По возможности используйте краткую форму.

    • Использовать for of зацикливается for циклы, если вам не нужен индекс или если производительность очень важна.
    • Используйте сокращенное обозначение свойств для определения литералов объекта values = {maxWidth: maxWidth, maxHeight: maxHeight}; возможно values = {maxWidth, maxHeight};
    • Используйте стрелочные функции для простых однострочных функций.
    • Вам редко нужно ссылаться window поскольку это объект по умолчанию. Например window.innerWidth такой же как innerWidth. Вы не добавляете окно при доступе к другим window свойства как window.document так почему же избирательно делать это с другими?
    • Используйте назначение деструктуры при извлечении свойств из объекта или массивов. НАПРИМЕР const {maxWidth, maxHeight} = getMaxDimension(div_selection)

Переписать

Это переписывает только части вашего кода, так как ваш код слишком сложен для того, что он делает, пример внизу ответа не использует какой-либо ваш код,

Функция getRandomNumber, getChar, getMaxDimension были переписаны и переименованы.

const randNum = (min, max) => Math.random() * (max - min) + min;
function getMaxSize(elements) {
    var width = 0, height = 0;
    for (const el of elements) {
        width = Math.max(width, el.offsetWidth);
        height = Math.max(height, el.offsetWidth);
    }
    return {widtyh, height};
}
function intToBase(n, digits = "abcdefghijklmnopqrstuvwxyz") {
    const base = digits.length, result = [];
    do {
        result.push(digits[(n |= 0) % base]);
        n = n / base;
    } while (n > 0);
    return result.reverse().join("");
}

Монтажные коробки

Это обычная проблема в информатике, и ее трудно решить в зависимости от ограничений.

Предполагаю, что данные элементы могут уместиться по площади.

Это означает, что площадь подходящих элементов меньше, чем площадь для размещения, и что самый длинный край подходящих элементов меньше, чем самая длинная соответствующая ось соответствующей области.

Это как минимум гарантирует, что можно разместить один элемент. Алгоритм будет делать в среднем 50 попыток на ящик, если он не может найти позицию.

Некоторые заметки

  • Вы можете использовать getBoundingClientRect чтобы получить границы элемента.

  • Предмет Bounds определяет ограничивающие рамки и предоставляет функцию для проверки перекрытия, положения и, наконец, размещения элемента, если это необходимо.

  • Элементы сначала добавляются в массив placing. Затем каждый элемент проверяется на перекрытие с элементами в массиве. fitted. Если перекрытий не обнаружено, элемент перемещается в fitted и удален из placing

  • Когда у вас есть элементы, нет необходимости каждый раз запрашивать DOM, когда вам нужно знать, где он находится. Элементы, которые вы храните в массиве div_selection являются ссылками на элементы, поэтому любые внесенные вами изменения будут доступны как сохраненная ссылка.

  • В примере на коробку выделяется 50 попыток. Если ящик не использует все попытки, они становятся доступными для использования другими.

  • Только если коробку можно поставить, ее цвет меняется на красный. Ящики, которые нельзя переместить, остаются в верхнем левом углу и окрашиваются в черный цвет.

;(() => {
"use strict";
const TRIES_PER_BOX = 50;
const randUint = range => Math.random() * range | 0;
const placing  = [...document.querySelectorAll(".random")].map(el => Bounds(el, 5));
const fitted = [];
const areaToFit = Bounds();
var maxTries = TRIES_PER_BOX * placing.length;
while (placing.length && maxTries > 0) {
    let i = 0;
    while (i < placing.length) {
        const box = placing[i];
        box.moveTo(randUint(areaToFit.w - box.w), randUint(areaToFit.h - box.h));
        if (fitted.every(placed => !placed.overlaps(box))) {
            fitted.push(placing.splice(i--, 1)[0].placeElement());
        } else { maxTries-- }
        i++;
    }
} 
function Bounds(el, pad = 0) {   
    const box = el?.getBoundingClientRect() ?? {
        left: 0, top: 0, 
        right: innerWidth, bottom: innerHeight, 
        width: innerWidth, height: innerHeight
    };
    return {
        l: box.left - pad, 
        t: box.top - pad, 
        r: box.right + pad, 
        b: box.bottom + pad,
        w: box.width + pad * 2,
        h: box.height + pad * 2,
        overlaps(bounds) { 
            return !(
                this.l > bounds.r || 
                this.r < bounds.l || 
                this.t > bounds.b || 
                this.b < bounds.t
            ); 
        },
        moveTo(x, y) {
            this.r = (this.l = x) + this.w;
            this.b = (this.t = y) + this.h;
            return this;
        },
        placeElement() {
            if (el) {
                el.style.top = (this.t + pad) + "px";
                el.style.left = (this.l + pad) + "px";
                el.classList.add("placed");
            }
            return this;
        }
    };
}
})();
.random {
  position: absolute;
  margin: 2;
  border: 1px solid black;
  font-size: xx-large;
  top: 0px;
  left: 0pc;
  
}
.placed {
  color: red;
  border: 1px solid red;
}
<div class="random">Div 1</div>
<div class="random">Div 2</div>
<div class="random">Div 3</div>
<div class="random">Div 4</div>
<div class="random">Div 5</div>
<div class="random">Div 6</div>
<div class="random">Div 7</div>
<div class="random">Div 8</div>
<div class="random">Div 9</div>
<div class="random">Div 10</div>
<div class="random">Div 11</div>
<div class="random">Div 12</div>

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *