В настоящее время я работаю над проектом, для которого я хочу, чтобы различные ссылки (элементы 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 ответ
Стиль
Некоторые моменты стайлинга
Имена. У тебя плохое именование
- Соглашение об именах JavaScript:
camelCase
избегать использованияsnake_case
- Используйте контекст функции, чтобы придать дополнительный смысл. Например, у вас есть
maxWidth
иmaxHeight
в функции, называемойgetMaxDimension
. Поскольку они единственные переменные (начиная сi
) объявлен и как таковой может быть простоwidth
иheight
getRandomNumber
может бытьrandomNumber
илиrandomNum
илиrandNum
arr
Избегайте именования переменных после их типа.arr
может бытьelements
. Также всегда старайтесь называть массивы или объекты, подобные массивам, множественным числом.
- Соглашение об именах JavaScript:
Не повторяйте код. Две петли в
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>