Нужно уточнить только одно: какую именно «логику раскладки» вы хотите? Чаще всего задают одно из двух правил:
- Правило A (самое популярное): «горизонтальные» картинки занимают 2 колонки, «вертикальные» — 1 колонку; всего в ряду 3 колонки (т.е. 2+1 или 1+1+1 и т.д.).
- Правило B: в узком экране — 2 колонки, в широком — 3 колонки (и ориентация влияет на то, сколько колонок занимает картинка).
Ниже дам два рабочих варианта реализации в React — короткий и надёжный через CSS Grid (проще) и более детерминированный (упаковка в строки) через JS + flex (если вам важен точный контроль, чтобы ряды всегда полностью заполнялись).
1) Вариант через CSS Grid (просто и быстро)
- Идея: узнаём соотношение сторон у каждой картинки (onLoad: naturalWidth / naturalHeight), назначаем span = 2 для landscape и span = 1 для portrait, рендерим в grid с grid-column: span N.
Пример:
JSX (React):
```
function Gallery({ images }) {
const [items, setItems] = React.useState(
images.map(src => ({ src, span: 1 }))
);
const onLoad = (index, e) => {
const ar = e.target.naturalWidth / e.target.naturalHeight;
const span = ar > 1.0 ? 2 : 1; // порог можно менять
setItems(prev => {
const copy = [...prev];
copy[index] = { ...copy[index], span };
return copy;
});
};
return (
<div className="grid">
{items.map((it, i) => (
<div key={i} className="item" style={{ gridColumn: `span ${it.span}` }}>
<img src={it.src} onLoad={(e) => onLoad(i, e)} alt="" />
</div>
))}
</div>
);
}
```
CSS:
```
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 колонки */
gap: 8px;
align-items: stretch;
}
.item img {
width: 100%;
height: 100%;
object-fit: cover; /* или contain, в зависимости от желаемого кадрирования */
display: block;
}
```
Пояснения:
- Горизонтальные картинки займут 2 из 3 колонок (span:2), вертикальные — 1.
- На мобильном можно менять сетку через медиазапросы (например repeat(2,1fr)) и соответственно подстраивать порог/логики.
- Этот вариант максимально прост и использует нативный поток grid. Минус: ряды могут быть «неплотными» (оставаться пустые ячейки), но визуально обычно выглядит нормально.
2) Вариант с «жёсткой» упаковкой (greedy packing) — если нужно, чтобы каждая строка всегда заполнялась комбинациями span=2/1
- Идея: заранее группируем элементы в строки (row) так, чтобы суммарные span в строке были равны колонкам (например 3). Алгоритм — жадный: добавляем элемент в текущую строку, если суммарный span <= cols, иначе стартуем новую строку.
Пример:
JS:
```
function buildRows(items, cols = 3) {
const rows = [];
let cur = [];
let sum = 0;
for (const it of items) {
const span = it.span || 1;
if (sum + span <= cols) {
cur.push(it);
sum += span;
} else {
// если элемент сам больше cols (маловероятно), можно принудительно положить в новую строку
rows.push({ items: cur, sum });
cur = [it];
sum = span;
}
}
if (cur.length) rows.push({ items: cur, sum });
return rows;
}
```
Рендер (React):
```
function PackedGallery({ images }) {
const [items, setItems] = React.useState(
images.map(src => ({ src, span: 1 }))
);
const rows = buildRows(items, 3);
const onLoad = (index, e) => {
const ar = e.target.naturalWidth / e.target.naturalHeight;
const span = ar > 1.0 ? 2 : 1;
setItems(prev => {
const copy = [...prev];
copy[index] = { ...copy[index], span };
return copy;
});
};
return (
<div className="packed">
{rows.map((row, ri) => (
<div className="row" key={ri}>
{row.items.map((it, i) => {
const span = it.span || 1;
const widthPercent = (span / 3) * 100;
return (
<div className="cell" key={i} style={{ flex: `0 0 ${widthPercent}%` }}>
<img src={it.src} onLoad={(e) => onLoad(/* index mapping needed */)} />
</div>
);
})}
</div>
))}
</div>
);
}
```
CSS:
```
.row {
display: flex;
gap: 8px;
}
.cell img { width: 100%; height: auto; display: block; }
```
Пояснения и тонкости:
- В варианте выше надо сопоставлять индекс картинки при onLoad — можно хранить индекс в item или замыкание.
- Если суммарные span в строке меньше cols (например остался 1 из 3), можно:
- растянуть колонку(ы) — увеличить width остальных элементов в строке (пропорционально), либо
- оставить пустое пространство.
- Для избежания «скачков» layout (CLS) лучше задавать width/height или атрибуты size для <img> заранее (или заранее знать aspect ratio и задавать контейнеру aspect-ratio), тогда grid/rows не перескакивают при загрузке.
Дополнительные советы
- Порог aspect-ratio можно варьировать (напр., >1.2 считать горизонтальной) если хотите отделять слегка квадратные изображения.
- Для сложных красивых шаблонов можно использовать библиотеки Masonry / CSS masonry (experimental) / Packery, но нативный grid + span/packing покрывают 95% задач.
- Для адаптивности используйте медиазапросы: на <600px сделайте grid-template-columns: repeat(2,1fr) и пересчитайте span (landscape = 2 -> займёт всю строку).
Если хотите, я могу:
- показать полный рабочий пример с корректным сопоставлением индексов onLoad -> items (с pasteable кодом),
- или реализовать именно ту логику, которую вы имели в виду (опишите правило: кто сколько колонок занимает и как должно выглядеть поведение при остатках).