AI Равномерное размещение блоков разных размеров

AI

Редактор
Регистрация
23 Август 2023
Сообщения
3 641
Лучшие ответы
0
Реакции
0
Баллы
243
Offline
#1
Как‑то на одном проекте понадобилось красиво равномерно разместить небольшие блоки‑виджеты в контейнере на странице. Сложность в том, что эти блоки различаются, как по высоте, так и по ширине. При чём нужно учесть адаптивность вёрстки и динамическое изменение содержимого, как контейнера, так и самих элементов — виджетов. Собственно мои изыскания по этой теме и вылились в разработку собственного решения и эту статью, которые, я надеюсь, будут полезны читателям.

Опишем подробно условия/пожелания задачи:


  1. как можно меньшие пустоты между элементами и ими и границами контейнера;


  2. равномерное расположение пустот;


  3. по возможности, вообще обойтись без JavaScript кода, либо сделать его участие минимальным;


  4. адаптивность под разные размеры экранов и влияние других нод на странице вокруг контейнера;


  5. поддержка динамической адаптивности под изменения (размеры, содержимое) страницы, контейнера и элементов, а так же создания/добавления/удаления таких контейнеров/элементов что называется «на лету».

Интерактивную демонстрацию всех приведённых в статье примеров можно посмотреть здесь.

"Дедовский" метод "float: left"


float: left

.container_float_left{
overflow: auto;
> *{
float: left;
}
&::after{
clear: both;
}
}

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

По условиям/пожеланиям получаем:


  1. величина пустот: если разница между блоками не большая, то +/- подходит, иначе - нет;


  2. равномерность пустот: нет, равномерности тут не наблюдаем;


  3. без JS: подходит;


  4. адаптивность: подходит;


  5. динамическая адаптивность: подходит.
Метод "flex-flow: row wrap"


flex-flow: row wrap

.container_flex_row_wrap{
display: flex;
flex-flow: row wrap;
justify-content: space-evenly;
}

Блоки распределяются так же по строкам, как и в предыдущем методе, но, благодаря "justify-content: space-evenly", пустые расстояния между соседними элементами в строке равные.

По условиям/пожеланиям получаем:


  1. величина пустот: если разница между блоками не большая, то +/- подходит, иначе - нет;


  2. равномерность пустот: частично, соблюдается только между соседними элементами в строке;


  3. без JS: подходит;


  4. адаптивность: подходит;


  5. динамическая адаптивность: подходит.
Метод "column-count: 4"


column-count: 4

.container_columns{
column-count: 4;
column-gap: 0;
> *{
display: inline-block;
}
}

На первый взгляд, смотрится, как то, что нужно. Минимальные пустоты, так как распределение блоков идёт по столбцам, а не по строкам. Правда, равномерности в промежутках между столбцами уже нет. В минусы можно отнести то, что количество столбцов нужно прописывать вручную, так что адаптивность можно обеспечить разве что правилами @media для каждого случая размера экрана. О поддержке динамических изменений говорить не приходится.

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

По условиям/пожеланиям получаем:


  1. величина пустот: подходит;


  2. равномерность пустот: частично, соблюдается только между блоками в столбцах;


  3. без JS: подходит;


  4. адаптивность: подходит с условием использования правил @media для каждого случая размера экрана;


  5. динамическая адаптивность: нет.
Мой метод "flex-flow: column wrap" + js-скрипт


flex-flow: column wrap + Flex-Size-Fix

В основе лежит режим разметки "flex" со свойством "flex-flow: column wrap". Дело в том, что без ограничения контейнера по высоте мы получаем просто один столбец со всеми вложенными элементами. Однако, установив точную высоту, мы получаем как-раз то, что нужно, что наблюдаем на иллюстрационном изображении. Как не трудно догадаться, скрипт занимается как раз определением этой высоты блока, а так же следит за изменениями страницы, запуская это переопределение по необходимости.

Суть в том, чтобы установив примерную (заведомо меньшую реальной) высоту контейнера, постепенно в цикле по небольшим шагам увеличивать оную (высоту) до тех пока не будет устранено переполнение. По итогу это и будет искомая оптимальная величина. Наличие переполнения по высоте определяется так: .scrollHeight > .scrollHeight. По ширине: .scrollWidth > .offsetWidth.

Фрагмент кода с непосредственными вычислением и установкой высоты контейнера:


/* node - блок-контейнер */
const step = 10; // шаг подбора высоты

let
square = 0, // общая площадь всех внутренних блоков
max_height = 0; // максимальна высота блока

/* обход всех дочерних елементов с заполнением переменных, объявленных выше */
[...node.childNodes].forEach(child => {
if(child.nodeName === '#text') return;
square += child.offsetHeight * child.offsetWidth;
if(child.offsetHeight > max_height) max_height = child.offsetHeight;
});

/* вычисляем стартовую высоту */
let start_height = square / node.offsetWidth;
if(start_height < max_height) start_height = max_height;

/* устанавливаем стартовую высоту */
if(!isNaN(start_height)) node.style.height = start_height + 'px';

/* цикл увеличения высоты контейнера "node" проводит итерации до тех пор
, пока переполнение по высоте (.scrollHeight > .scrollHeight) и ширине (.scrollWidth > .offsetWidth) не будет устранено */
let savety = 1000;
while((node.scrollHeight > node.offsetHeight || node.scrollWidth > node.offsetWidth) && savety){
node.style.height = parseInt(node.style.height) + step + 'px';
savety--;
}

Примечание. В принципе можно и не заморачиваться и начинать высоту с ноля. Дело в облегчении работы браузера, хотя на сколько именно в среднем оно благотворно сказывается на производительности трудно сказать, и уж точно конечный пользователь не заметит разницы.

Остальной код не буду здесь приводить, ибо банальность с обходом DOM дерева с поиском целевых нод и отслеживание за изменениями страницы и участвующих узлов с помощью "обзёрверов", без каких-то подводных камней. Хотя, нужно заметить не очевидную особенность: при наличие прокрутки страницы и динамическом изменении целевой ноды может произойти резкий неприятный сдвиг прокрученной области (как-будто без причины). Фиксится просто:


  1. находим родительский HTML-элемент с прокруткой;


  2. запоминаем текущее положение скролла;


  3. проводим непосредственную установку новой высоты для целевого блока;


  4. возвращаем положение текущей прокрутки с помощью ранее сохранённой координаты.

Буквально 4 строки кода:

const
get_scroll_parent = node => node.parentNode?.scrollTop ? node.parentNode : node.parentNode ? get_scroll_parent(node.parentNode) : null,
scroll_parent = get_scroll_parent(node), // находим родителя с прокруткой, если он есть
current_scroll = scroll_parent ? scroll_parent.scrollTop : null; // запоминаем текущее положение скролла
...
/* Подгоняем высоту блока */
...
/* исправляем прокрутку, если требуется */
if(current_scroll && current_scroll !== scroll_parent.scrollTop) scroll_parent.scrollTo({top: current_scroll, behavior: 'instant'});

Так как вмешательство скрипта минимально у нас имеется арсенал нативных CSS-свойств для кастомизации.

Однако, отсутствует возможность пользоваться только следующими свойствами:


  • display;


  • flex-flow;


  • flex-direction;


  • flex-wrap;


  • flex-shrink;


  • flex-grow.

Как пользоваться:


  • Скачиваем скрипт и подключаем к своей странице либо копируем весь код из файла и размещаем в теге "script" внутри блока "head" либо "body" нашего html.


  • Активируем целевые блоки-контейнеры с помощью атрибута "data-flex-size-fix".
Подключение скрипта (на всякий случай):


<script language="JavaScript" src="./flex_size_fix.js"></script>
Инициализация целевых блоков-контейнеров с помощью атрибута "data-flex-size-fix":


...
<div data-flex-size-fix>
...
</div>
...
По условиям/пожеланиям получаем:


  1. величина пустот: подходит;


  2. равномерность пустот: подходит;


  3. без JS: нет, но минимальное вмешательство;


  4. адаптивность: подходит;


  5. динамическая адаптивность: подходит.

Примечание. Ломал голову о звучном названии скрипта. Пришёл к выводу, что тот набор слов через тире (Flex-Size-Fix) в принципе отражает всю суть.

P.S. Стоит так же упомянуть о известной подключаемой библиотеке, решающей как раз данную задачу, но, к сожалению, совсем вылетело из головы её название и нагуглить не смог. Там внутренние блоки размещались и позиционировались абсолютно. Если кто-то знает, пожалуйста напишите в комментариях.

P.P.S. На случай, если кому-то важно, статья и код писались без использования ИИ.

P.P.P.S. Телеграмм-канала у меня нет, так что подписываться некуда, извините.
 
Яндекс.Метрика Рейтинг@Mail.ru
Сверху Снизу