Раскладка изображений мозайкой

Задача разложить изображения в ряд мозайкой как это делают на https://www.freepik.com

Особенности:

  • Изображения всегда заполняют всю ширину экрана
  • Изображения выстраиваются в линии, при этом высота каждой линии отличается
  • Высота, ширина изображений выбирается не случайным образом, а максимально оптимально
  • Flex, а не абсолютное позиционирование
  • Изначально известен путь до изображения и его размеры (ширина, высота)
  • Исходные изображения или квадратные или прямоугольные, ширина всех всегда одинаковая (как на фрипике - 626px), а высота может отличаться.

Библиотеки вроде masonry (https://masonry.desandro.com), isotope (https://isotope.metafizzy.co) слишком монструозные, используют абсолютное позиционирование и не решают задачу всё равно.

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

Вот пример с добавленными изображениями и выведенными размерами в атрибуты для удобства. Нужно задать оптимально ширину и высоту каждого элемента .image

.container {
  padding: 0 10px;
}

.row {
  margin: 0 -5px;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;  
}

.image {
    margin: 0 5px 10px 5px;
}

.image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
<div class="container">
 <div class="row">
  <div class="image" data-w="626" data-h="312">
    <img src="https://image.freepik.com/free-vector/enjoy-summer-3d-realistic-background-with-clouds-daisies-grass-leaves-product-podium_87521-3206.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="521">
    <img src="https://image.freepik.com/free-vector/linear-flat-wedding-monograms_52683-64319.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="442">
    <img src="https://image.freepik.com/free-vector/flat-car-poster-with-photo-horizontal_52683-64510.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="417">
    <img src="https://image.freepik.com/free-vector/gradient-grainy-gradient-shapes_23-2148975080.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="286">
    <img src="https://image.freepik.com/free-photo/city-tornado-doomsday-scene-illustration_456031-22.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="417">
    <img src="https://image.freepik.com/free-vector/gradient-grainy-gradient-texture_23-2148974472.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="417">
    <img src="https://image.freepik.com/free-vector/hand-drawn-blackboard-coffee-collection_79603-1654.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="468">
    <img src="https://image.freepik.com/free-photo/person-putting-medical-mask-earth_23-2148984685.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="626">
    <img src="https://image.freepik.com/free-psd/3d-space-rocket-with-smoke_23-2148938939.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="417">
    <img src="https://image.freepik.com/free-vector/gradient-grainy-gradient-shape-set_23-2148971570.jpg" alt="">
  </div>
 </div>
</div>


Ответы (2 шт):

Автор решения: Leonid

Здесь пример как можно сделать по два элемента в ряд, например (подгонка под малое окно просмотра). А так должен быть алгоритм, который рассматривает несколько изображений в ряд в зависимости от ширины контейнера, учитывая пределы возможного уменьшения изображений. Если нет - на одно изображение в ряд меньше.

const MARGINS = 60; // Margins, paddings, полоса прокрутки - на глаз
let image_arr = [...document.querySelectorAll('.image')]; // массив всех контейнеров изображений (боксов)
let obj_arr = image_arr.map(image => { // копия массива, но с более удобными для работы свойствами
  return {w: image.getAttribute('data-w'), h: image.getAttribute('data-h')};
}); 
let width = document.querySelector('.container').getBoundingClientRect().width - MARGINS; // Ширина общего контейнера для расчетов

let counter = 0;


changeSize = () => {
  let images = obj_arr.slice(counter, counter+2); // Берем по два бокса

  // Здесь берем меньшую по высоте картинку и изменяем вторую до этой же высоты, а ширину по коэффициенту для сохранения соотношения
  let lowest = images.indexOf(images.sort((a,b) => a - b)[0]); 
  let k1 = images[lowest].h/images[1-lowest].h;
  images[1-lowest].h *= k1;
  images[1-lowest].w *= k1;
  
  // Здесь складываем полученную ширину двух картинок и подгоняем под ширину контейнера
  let imagesW = images.reduce((acc,cur) => acc+= +cur.w, 0);
  let k2 = width/imagesW;
  images[lowest].w *= k2;
  images[lowest].h *= k2;
  images[1-lowest].w *= k2;
  images[1-lowest].h *= k2;

  counter += 2;
}

// Перебираем массив попарно
for (let i = 0; i < image_arr.length - 1; i+=2){
  changeSize();
}

// Назначаем размеры боксам соответственно рабочему массиву объектов.
image_arr.forEach((image,i) => {
    image.style.width = obj_arr[i].w + 'px';
    image.style.height = obj_arr[i].h + 'px';
})
.container {
  padding: 0 10px;
}

.row {
  margin: 0 -5px;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;  
}

.image {
    margin: 0 5px 10px 5px;
}

.image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
<div class="container">
 <div class="row">
  <div class="image" data-w="626" data-h="312">
    <img src="https://image.freepik.com/free-vector/enjoy-summer-3d-realistic-background-with-clouds-daisies-grass-leaves-product-podium_87521-3206.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="521">
    <img src="https://image.freepik.com/free-vector/linear-flat-wedding-monograms_52683-64319.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="442">
    <img src="https://image.freepik.com/free-vector/flat-car-poster-with-photo-horizontal_52683-64510.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="417">
    <img src="https://image.freepik.com/free-vector/gradient-grainy-gradient-shapes_23-2148975080.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="286">
    <img src="https://image.freepik.com/free-photo/city-tornado-doomsday-scene-illustration_456031-22.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="417">
    <img src="https://image.freepik.com/free-vector/gradient-grainy-gradient-texture_23-2148974472.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="417">
    <img src="https://image.freepik.com/free-vector/hand-drawn-blackboard-coffee-collection_79603-1654.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="468">
    <img src="https://image.freepik.com/free-photo/person-putting-medical-mask-earth_23-2148984685.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="626">
    <img src="https://image.freepik.com/free-psd/3d-space-rocket-with-smoke_23-2148938939.jpg" alt="">
  </div>
  <div class="image" data-w="626" data-h="417">
    <img src="https://image.freepik.com/free-vector/gradient-grainy-gradient-shape-set_23-2148971570.jpg" alt="">
  </div>
 </div>
</div>

→ Ссылка
Автор решения: Давид Манжула

На freepik.com используется opensource-библиотека JavaScript-flexImages от разработчиков Pixabay. Она легковесная - весит всего 2кб.

Там же можно ознакомится с алгоритмом работы - меньше сотни строк кода (:

→ Ссылка