具有持久纵横比的响应式 CSS 网格

Posted

技术标签:

【中文标题】具有持久纵横比的响应式 CSS 网格【英文标题】:Responsive CSS Grid with persistent aspect ratio 【发布时间】:2019-01-05 18:00:24 【问题描述】:

我的目标是创建一个包含未知数量项目的响应式网格,使其纵横比保持在 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

【问题讨论】:

类似这样的:codepen.io/danield770/pen/bjYvOj ? 这正是我想要的!如果您回答问题,我会勾选您的作为解决方案:) 谢谢! 【参考方案1】:

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

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

...如果您有一个 500px 宽且 padding-top 为 100% 的元素, padding-top 为 500px。

那不是一个完美的正方形,500px × 500px?是的!一个方面 比例!

如果我们将元素的高度强制为零(高度:0;)并且不要 有任何边界。那么填充将是盒子模型的唯一部分 影响高度,我们就会有我们的正方形。

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

所以为了让列保持纵横比:

    按照您的建议设置列宽:

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

    为项目添加伪元素以保持 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 个项目,项目会变得非常大......有没有办法防止这种行为? 是的,尝试将 auto-fit 更改为 auto-fill - 请参阅我在第 2 点中的帖子 here 我解释了差异 哦,我怎么没想到这个...非常感谢您的帮助! 当我在第一个项目中放入大量文本时,整行项目在底部伸展。没有办法保持 16:9 的比例吗? 我很好奇您将如何使用图像来实现这一点。不是背景图片,而是使用 object-fit css 属性的图片。【参考方案2】:

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

【讨论】:

这个!只是这个!哦,你是救生员!【参考方案3】:

对于视频布局,我需要完全相同的东西,但我无法使用其他答案,因为我需要以宽度 高度为界。基本上,我的用例是一个具有一定大小、未知项目数和项目固定纵横比的容器。不幸的是,这不能在纯 CSS 中完成,它需要一些 JS。我找不到一个好的装箱算法,所以我自己写了一个(当然它可能会模仿现有的)。

基本上,我所做的是获取最大的一组行并找到最佳比例的拟合。然后,我找到了保留纵横比的最佳项目边界,然后将其设置为 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>

【讨论】:

这太好了,我一直在寻找这样的东西,谢谢你发帖! 如果第一行有 2 个 div,第二行有 1 个 div,你有没有办法让第二行的 1 个 div 居中?【参考方案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 的比例。它们不应沿 x 轴“拉伸”。有没有办法在两个轴上缩放它们? 当屏幕宽度变大时,它们要么会拉伸(这将破坏 16:9,我的解决方案),要么会留下边距(介于两者之间或最后,您的解决方案)。没有第三种可能性,imo。你要哪一个? 看看这个页面:link(我想要类似的外观,但没有指定 10 种不同的@media 屏幕宽度) 我的解决方案正是这样做的。试试here。如果您增加屏幕宽度,项目将暂时伸展,但当屏幕变得如此宽以至于新项目可以容纳时,所有项目将恢复到 160 像素宽度,并且新项目 (160 像素) 将加入。 但是在页面上,我发送给您的项目不会拉伸(它们在两侧均等缩放)

以上是关于具有持久纵横比的响应式 CSS 网格的主要内容,如果未能解决你的问题,请参考以下文章

使具有不同纵横比的响应式图像具有相同的高度

仅使用 CSS 的响应式视频 iframe(保持纵横比)?

对于具有不同纵横比的响应式图像,有啥方法可以最小化 Cumulative Layout Shift?

固定缩略图比例大小和保持纵横比?

Bootstrap Thumbnail & Caption 类的响应式 CSS

响应式图像拉伸 - 使用基于 y 轴的网格?