Адаптивная сетка CSS с постоянным соотношением сторон

Моя цель - создать адаптивную сетку с неизвестным количеством элементов с соотношением сторон 16: 9. Сейчас это выглядит так:

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, 160px);
    grid-template-rows: 1fr;
    grid-gap: 20px;
 }
.item {
    height: 90px;
    background: grey;
}
<div class = "grid">
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
</div>

Проблема в том, что элементы не масштабируются с размером экрана, что приводит к появлению поля на правильном сайте. Но когда сетка адаптируется к размеру экрана, например, с помощью grid-template-columns: repeat(auto-fit, minmax(160p, 1fr)) и удаляется height: 90px;, соотношение сторон не сохраняется.

Может быть, есть лучшее решение без css grid? (Возможно, используя javascript)

Примерно так: codepen.io/danield770/pen/bjYvOj?

Danield 29.07.2018 10:13

Это именно то, что я искал! Если вы ответите на вопрос, я поставлю вам галочку в качестве решения :) Спасибо!

Florian Ludewig 29.07.2018 10:16
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Введение в CSS
Введение в CSS
CSS является неотъемлемой частью трех основных составляющих front-end веб-разработки.
Как выровнять Div по центру?
Как выровнять Div по центру?
Чтобы выровнять элемент <div>по горизонтали и вертикали с помощью CSS, можно использовать комбинацию свойств и значений CSS. Вот несколько методов,...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Travel Booking Angular Template один из лучших Travel & Tour booking template in the world. 30+ валидированных HTML5 страниц, которые помогут...
14
2
14 160
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Возможно, я не могу понять вопрос, но как вы планируете сохранить соотношение сторон элемента 16: 9, при этом желая масштабировать элементы по ширине экрана?

Если вас устраивают элементы с увеличенной шириной между размерами экрана, это может сработать:

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    grid-template-rows: 1fr;
    grid-gap: 20px;
 }
.item {
    height: 90px;
    background: grey;
}
<div class = "grid">
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
        <div class = "item"></div>
</div>

Ваше решение действительно хорошее, но проблема в том, что соотношение 16: 9 не сохраняется. Они не должны «растягиваться» по оси абсцисс. Есть ли способ масштабировать их по обеим осям?

Florian Ludewig 29.07.2018 09:58

Когда ширина экрана увеличивается, они либо растягиваются (что разрушит 16: 9, мое решение), либо оставляют поля (между или в конце, ваше решение). Нет третьей возможности, имо. Какую ты хочешь?

dasfdsa 29.07.2018 10:04

Взгляните на эту страницу: ссылка на сайт (мне нужен похожий вид, но без указания 10 разной ширины экрана @media)

Florian Ludewig 29.07.2018 10:09

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

dasfdsa 29.07.2018 10:14

Но на странице, которую я отправил вам, элементы не растягиваются (они одинаково масштабируются с обеих сторон)

Florian Ludewig 29.07.2018 10:18
Не реагирует, как запрошено OP. имеет фиксированные размеры.
vsync 27.10.2020 13:50
Ответ принят как подходящий

Вы можете воспользоваться тем фактом, что процентное заполнение зависит от ширины.

Эта статья о CSS-хитростях хорошо объясняет идею:

...if you had an element that is 500px wide, and padding-top of 100%, the padding-top would be 500px.

Isn't that a perfect square, 500px × 500px? Yes, it is! An aspect ratio!

If we force the height of the element to zero (height: 0;) and don't have any borders. Then padding will be the only part of the box model affecting the height, and we'll have our square.

Now imagine instead of 100% top padding, we used 56.25%. That happens to be a perfect 16:9 ratio! (9 / 16 = 0.5625).

Итак, чтобы столбцы сохранили соотношение сторон:

  1. Установите ширину столбцов, как вы предложили:

grid-template-columns: repeat(auto-fit, minmax(160px, 1fr))

  1. Добавьте к элементам псевдоэлемент, чтобы сохранить соотношение сторон 16: 9:

    .item: before { содержание: ""; дисплей: блок; высота: 0; ширина: 0; padding-bottom: calc (9/16 * 100%); }

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  grid-template-rows: 1fr;
  grid-gap: 20px;
}
.item {
  background: grey;
  display: flex;
  justify-content: center;
}
.item:before {
  content: "";
  display: block;
  height: 0;
  width: 0;
  padding-bottom: calc(9/16 * 100%);
}
<div class = "grid">
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
  <div class = "item"></div>
</div>

Демо Codepen (измените размер, чтобы увидеть эффект)

Я столкнулся с небольшой проблемой ... Элементы становятся действительно огромными, если в сетке всего 1 или 2 элемента ... Есть ли способ предотвратить такое поведение?

Florian Ludewig 29.07.2018 11:24

да, попробуйте заменить auto-fit на auto-fill - см. мой пост здесь в пункте 2, я объяснил разницу

Danield 29.07.2018 11:32

Ох, как я мог не подумать об этом ... Огромное спасибо за помощь!

Florian Ludewig 29.07.2018 11:59

Когда я вставляю много текста в первый элемент, весь ряд элементов растягивается внизу. Разве нельзя СОХРАНИТЬ соотношение сторон 16: 9?

Malachi 24.01.2019 15:29

Мне любопытно, как этого добиться с изображениями. Не с фоновыми изображениями, а с изображениями, использующими свойство объектно-подходящего css.

romeplow 14.12.2019 05:32

По моему опыту, это не работает с фиксированной высотой.

Chad Retz 02.05.2020 08:31

Мне нужно было то же самое для макетов видео, но я не мог использовать другие ответы, потому что мне нужно было ограничиваться шириной а также height. В основном моим вариантом использования был контейнер определенного размера, неизвестного количества элементов и фиксированного соотношения сторон элементов. К сожалению, это невозможно сделать в чистом CSS, для этого нужен JS. Мне не удалось найти хороший алгоритм упаковки контейнеров, поэтому я написал его сам (при условии, что он может имитировать существующие).

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

Вот полный пример, показывающий, как использовать его с чем-то вроде настраиваемых свойств CSS. Первая JS-функция является основной, которая определяет наилучший размер. Добавляйте и удаляйте элементы, изменяйте размер браузера, чтобы увидеть, как он сбрасывается для оптимального использования пространства (или вы можете увидеть эта версия CodePen).

// Get the best item bounds to fit in the container. Param object must have
// width, height, itemCount, aspectRatio, maxRows, and minGap. The itemCount
// must be greater than 0. Result is single object with rowCount, colCount,
// itemWidth, and itemHeight.
function getBestItemBounds(config) {
  const actualRatio = config.width / config.height
  // Just make up theoretical sizes, we just care about ratio
  const theoreticalHeight = 100
  const theoreticalWidth = theoreticalHeight * config.aspectRatio
  // Go over each row count find the row and col count with the closest
  // ratio.
  let best
  for (let rowCount = 1; rowCount <= config.maxRows; rowCount++) {
    // Row count can't be higher than item count
    if (rowCount > config.itemCount) continue
    const colCount = Math.ceil(config.itemCount / rowCount)
    // Get the width/height ratio
    const ratio = (theoreticalWidth * colCount) / (theoreticalHeight * rowCount)
    if (!best || Math.abs(ratio - actualRatio) < Math.abs(best.ratio - actualRatio)) {
      best = { rowCount, colCount, ratio }
    }
  }
  // Build item height and width. If the best ratio is less than the actual ratio,
  // it's the height that determines the width, otherwise vice versa.
  const result = { rowCount: best.rowCount, colCount: best.colCount }
  if (best.ratio < actualRatio) {
    result.itemHeight = (config.height - (config.minGap * best.rowCount)) / best.rowCount
    result.itemWidth = result.itemHeight * config.aspectRatio
  } else {
    result.itemWidth = (config.width - (config.minGap * best.colCount)) / best.colCount
    result.itemHeight = result.itemWidth / config.aspectRatio
  }
  return result
}

// Change the item size via CSS property
function resetContainerItems() {
  const itemCount = document.querySelectorAll('.item').length
  if (!itemCount) return
  const container = document.getElementById('container')
  const rect = container.getBoundingClientRect()
  // Get best item bounds and apply property
  const { itemWidth, itemHeight } = getBestItemBounds({
    width: rect.width,
    height: rect.height,
    itemCount,
    aspectRatio: 16 / 9,
    maxRows: 5,
    minGap: 5
  })
  console.info('Item changes', itemWidth, itemHeight)
  container.style.setProperty('--item-width', itemWidth + 'px')
  container.style.setProperty('--item-height', itemHeight + 'px')
}

// Element resize support
const resObs = new ResizeObserver(() => resetContainerItems())
resObs.observe(document.getElementById('container'))

// Add item support
let counter = 0
document.getElementById('add').onclick = () => {
  const elem = document.createElement('div')
  elem.className = 'item'
  const button = document.createElement('button')
  button.innerText = 'Delete Item #' + (++counter)
  button.onclick = () => {
    document.getElementById('container').removeChild(elem)
    resetContainerItems()
  }
  elem.appendChild(button)
  document.getElementById('container').appendChild(elem)
  resetContainerItems()
}
#container {
  display: inline-grid;
  grid-template-columns: repeat(auto-fit, var(--item-width));
  grid-template-rows: repeat(auto-fit, var(--item-height));
  place-content: space-evenly;
  width: 90vw;
  height: 90vh;
  background-color: green;
}

.item {
  background-color: blue;
  display: flex;
  align-items: center;
  justify-content: center;
}
<!--
Demonstrates how to use CSS grid and add a dynamic number of
items to a container and have the space best used while
preserving a desired aspect ratio.

Add/remove items and resize browser to see what happens.
-->
<button id = "add">Add Item</button><br />
<div id = "container"></div>

Это здорово, я повсюду искал что-то подобное, спасибо за публикацию!

martin 16.09.2020 23:24

Если у вас 2 div в первой строке и 1 div во второй строке, нашли ли вы способ центрировать 1 div во второй строке?

martin 17.09.2020 00:23

CSS Evolution: свойство соотношения сторон

Теперь мы можем использовать свойство CSS4 aspect-ratio (Могу ли я использовать ?), чтобы легко управлять соотношением сторон без отступов и трюков с псевдоэлементами. В сочетании с object-fit мы получаем очень интересный рендеринг.

Вот фотографии в различных пропорциях, которые мне нужно отрендерить в формате 16/9:

section {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /* Play with min-value */
}

img {
  background-color: gainsboro; /* To visualize empty space */
  aspect-ratio: 16/9; 
  /*
    "contain" to see full original image with eventual empty space
    "cover" to fill empty space with truncating
    "fill" to stretch
  */
  object-fit: contain;
  width: 100%;
}
<section>
  <img src = "https://placeimg.com/640/360/architecture">
  <img src = "https://placeimg.com/640/360/tech">
  <img src = "https://placeimg.com/360/360/animals">
  <img src = "https://placeimg.com/640/360/people">
  <img src = "https://placeimg.com/420/180/architecture">
  <img src = "https://placeimg.com/640/360/animals">
  <img src = "https://placeimg.com/640/360/nature">
</section>

Детская площадка: https://codepen.io/JCH77/pen/JjbajYZ

Другие вопросы по теме