具有持续长宽比的自适应CSS网格

时间:2018-07-29 06:23:27

标签: html css sass responsive-design css-grid

我的目标是创建一个响应网格,其中包含数量未知的项目,其长宽比保持在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网格有更好的解决方案? (也许使用javascript)

4 个答案:

答案 0 :(得分:4)

您可以利用以下事实:百分比填充基于宽度。

This CSS-tricks article很好地解释了这个想法:

  

...如果您的元素宽为500px,顶部填充为100%,   顶部填充为500px。

     

那不是500px×500px的完美正方形吗?是的!方面   比率!

     

如果我们将元素的高度强制为零(高度:0;)并且不   有任何边界。然后填充将成为盒子模型的唯一组成部分   影响高度,我们将得到正方形。

     

现在想象一下,我们使用56.25%而不是100%的顶部填充。那个会发生   成为完美的16:9比例! (9/16 = 0.5625)。

因此,为了使各列保持宽高比:

1)根据您的建议设置列宽:

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

2)在项目中添加一个伪元素,以保持16:9的宽高比:

.item:before {
  content: "";
  display: block;
  height: 0;
  width: 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 Demo(调整大小以查看效果)

答案 1 :(得分:2)

也许我听不懂这个问题,但是当您想按屏幕宽度缩放项目时,如何计划将项目的宽高比保持为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>

答案 2 :(得分:1)

对于视频布局,我需要使用完全相同的东西,但是我无法使用其他答案,因为我需要受宽度的限制。基本上,我的用例是一个具有一定大小,未知物品计数和固定纵横比的容器。不幸的是,这不能在纯CSS中完成,它需要一些JS。我找不到一个好的bin打包算法,所以我自己写了一个(允许它可以模仿现有的)。

基本上,我所做的是获取最大的一组行,并找到具有最佳比率的拟合。然后,我发现保留宽高比的最佳项目边界,然后将其设置为CSS网格的自动调整高度和宽度。结果非常不错。

这是一个完整的示例,展示了如何将其与CSS自定义属性一起使用。第一个JS函数是完成最佳尺寸工作的主要函数。添加和删​​除项目,调整浏览器大小以观看其重置为最佳使用空间(或者您可以看到this CodePen version)。

// 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.log('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>

答案 3 :(得分:1)

CSS 进化:纵横比属性

我们现在可以使用 aspect-ratio CSS4 属性 (Can I Use ?) 轻松管理纵横比,无需填充和伪元素技巧。结合 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