使用CSS保持div的纵横比

1432

我想创建一个响应式的 div,能够随着窗口宽度的变化而改变其宽度/高度。

有没有CSS规则可以允许高度根据宽度变化,同时保持其纵横比

我知道我可以通过JavaScript实现这一点,但我更喜欢仅使用CSS。

div keeping aspect ratio according to width of window


2
这里提出的所有CSS解决方案都有局限性。目前没有通用的解决方案可以不使用JavaScript。@Web_Designer有一个很好的解决方案,但它只基于宽度保持比例。如果您想为宽度和高度都很重要的容器保持该比例,它将无法正常工作。使用vw和vh也会遇到类似的问题,因为您必须锁定其中之一。对于滚动内容可能不是问题,但对于其他依赖于有限高度的应用程序,您将需要JavaScript。 - xtempore
4
CSS的新属性“aspect ratio”现已推出。https://web.dev/aspect-ratio/ - AdamKniec
37个回答

1561

只需创建一个带有padding-bottom百分比值的包装器<div>,例如:

.demoWrapper {
  padding: 10px;
  background: white;
  box-sizing: border-box;
  resize: horizontal;
  border: 1px dashed;
  overflow: auto;
  max-width: 100%;
  height: calc(100vh - 16px);
}

div {
  width: 100%;
  padding-bottom: 75%;
  background: gold; /** <-- For the demo **/
}
<div class="demoWrapper">
  <div></div>
</div>

这将导致一个高度等于其容器宽度的75%的 <div> 元素 (一个4:3的宽高比)。

这依赖于padding 的工作原理:

百分比是相对于生成的框的包含块的宽度计算的[...] (参考来源:w3.org,强调为本人添加)

其他宽高比和100%宽度的 padding-bottom 值:

aspect ratio  | padding-bottom value
--------------|----------------------
    16:9      |       56.25%
    4:3       |       75%
    3:2       |       66.66%
    8:5       |       62.5%

在 div 中放置内容:

为了保持 div 的宽高比并防止其内容被拉伸,您需要添加一个绝对定位的子元素,并使用以下方式将其拉伸到包装器的边缘:

div.stretchy-wrapper {
  position: relative;
}

div.stretchy-wrapper > div {
  position: absolute;
  top: 0; bottom: 0; left: 0; right: 0;
}

这里是一个演示,另外一个更详细的演示


33
@schellmax,这是因为padding百分比是相对于当前元素的宽度计算,而height百分比是相对于父元素的高度计算。此外,绝对定位是相对于元素的外部容器计算的,其中包括填充区域。要了解更多信息,请搜索“CSS Box Model”。 - Anson Kao
17
这似乎不能嵌套使用。它在第一层级可以工作,但是当尝试在维持纵横比的div内部做相同的事情时,padding-bottom百分比似乎会应用到父元素的宽度上。这里有一个例子,其中.stretchy-wrap.onethird的25% padding-bottom实际上是父元素宽度的25%。有人可以解释一下吗? - Misterparker
1
如果您无法使用2021年的aspect-ratio,请使用以下替代方案:https://codepen.io/bagermen/pen/rNzOxMQ - user2434435

687

有几种方法可以指定一个像div这样的元素的固定纵横比,以下是其中的两种:

1. 使用 aspect-ratio CSS 属性

div {
  aspect-ratio: 1 / 1;
  width: 50%;
  background: teal;
}
<div>aspect-ratio: 1 / 1;</div>

这是最简单和灵活的解决方案。它直接为元素指定了一个固定的宽高比(或者高宽比)。这意味着您还可以根据元素的高度指定宽高比。
它不依赖于父元素的宽度(像填充技术一样)或视口大小(像下面的vw单位技术),而是依赖于元素自身的宽度或高度。有别于其他解决方法,这就是它如此强大的原因。在 MDN 上获取更多信息

这是一种现代属性(2021年)。所有现代浏览器都支持它,请参见 caniuse 以获取精确的浏览器支持情况

以下是几个具有不同宽高比的示例:

.ar-1-1  {aspect-ratio: 1 / 1;}
.ar-3-2  {aspect-ratio: 3 / 2;}
.ar-4-3  {aspect-ratio: 4 / 3;}
.ar-16-9 {aspect-ratio: 16 / 9;}
.ar-2-3  {aspect-ratio: 2 / 3;}
.ar-3-4  {aspect-ratio: 3 / 4;}
.ar-9-16 {aspect-ratio: 9 / 16;}


/** For the demo : **/
body {
  display:flex;
  flex-wrap:wrap;
  align-items:flex-start;
}
div {
  background: teal;
  width: 23%;
  margin:1%;
  padding:20px 0;
  box-sizing: border-box;
  color:#fff;
  text-align:center;
}
<div class="ar-1-1">aspect-ratio: 1 / 1;</div>
<div class="ar-3-2">aspect-ratio: 3 / 2;</div>
<div class="ar-4-3">aspect-ratio: 4 / 3;</div>
<div class="ar-16-9">aspect-ratio: 16 / 9;</div>
<div class="ar-2-3">aspect-ratio: 2 / 3;</div>
<div class="ar-3-4">aspect-ratio: 3 / 4;</div>
<div class="ar-9-16">aspect-ratio: 9 / 16;</div>

2. 使用 vw 单位:

您可以使用 vw 单位来设置元素的宽度和高度。这样可以保持元素的纵横比,基于视口宽度

vw:视口宽度的1/100。[MDN]

或者,您也可以使用 vh 来设置视口高度,甚至使用 vmin/vmax 来使用视口尺寸中较小/较大的一个(讨论看这里)。

Example: 1:1 aspect ratio

div {
  width: 20vw;
  height: 20vw;
  background: gold;
}
<div></div>

对于其他的纵横比,您可以使用以下表格来计算元素宽度所对应的高度值:

aspect ratio  |  multiply width by
-----------------------------------
     1:1      |         1
     1:3      |         3
     4:3      |        0.75
    16:9      |       0.5625

示例:4x4个正方形div的网格

body {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}
div {
  width: 23vw;
  height: 23vw;
  margin: 0.5vw auto;
  background: gold;
}
<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>

这里有一个带有演示的Fiddle,以及一个创建响应式正方形网格并使其垂直和水平居中的解决方案。


vh / vw单位在IE9+浏览器中受支持。更多信息请参见canIuse


虽然aspect-ratio确实名副其实,但我发现它是通过扩展文本周围的框而不是重新排列文本来实现的。结果是底部有很多空白空间,而不是紧密贴合内容。有没有解决方法?理想情况下,它也应该是弹性的,以防止达到硬性的最大宽度或最大高度。(拉伸比裁剪更好)。 - Brian White

153
使用CSS属性aspect-ratio
<div class='demo'></div>

.demo {
  background: black;
  width: 500px;
  aspect-ratio: 4/3;
}

注意:当您仅使用宽度属性并将其值设置为100%时,该宽度将被计算,以便不填充水平空间。当您想保留100%的宽度时,必须同时添加宽度和高度,并将每个值都设置为100%。这样它才能正常工作(至少在最新版本的Chrome中可以正确工作)。 - Kevin Glier
Overwolf仍然使用不支持aspect-ratio的Chromium版本。相反,您可以使用类似于height: calc(100vw * 4 / 3);的东西。 - Slion
这似乎是在更详细地重复2020年的解决方案 https://dev59.com/u3I_5IYBdhLWcg3wK_zE#61825062 - TylerH
这些属性可以节省十几行代码。 - undefined

68

在 CSS 实现 aspect-ratio 之前,这个答案就已经写好了。但是今天你应该使用它。


原始答案

我偶然发现了一个我认为是聪明的解决方案,使用<svg>display:grid

display:grid元素允许您使用相同的grid-area占用两个(或多个)子元素的相同空间。
这意味着它们都是流内容,重叠在一起,而且默认情况下较高的元素设置比例

其中一个将是负责设置比例的<svg>。另一个则是实际内容。如果实际内容很短,从未填满整个比例(并且您只想将其居中在具有此比例的空间中),请将其居中(请参见下面的第一个可运行片段)。

<div class="ratio">
  <svg viewBox="0 0 1 1"></svg>
  <div>
    I'm square
  </div>
</div>

.ratio {
  display: grid;
}
.ratio > * {
  grid-area: 1/1;
}

<svg>的比例设置为您想要的任何值:

  • <svg viewBox="0 0 4 3"></svg>
  • <svg viewBox="0 0 16 9"></svg>

.ratio {
  display: grid;
}
.ratio > * {
  grid-area: 1/1;
}

/* below code NOT needed for setting the ratio 
 * I just wanted to mark it visually
 * and center contents
 */
.ratio div {
  border: 1px solid red;
  display: flex;
  align-items: center;
  justify-content: center;
}
<div class="ratio">
  <svg viewBox="0 0 7 1"></svg>
  <div>
    Fixed ratio 7:1
  </div>
</div>


如果您需要一个解决方案,其中内容元素具有更多的内容,并且您希望将其限制在具有所需比率的可滚动区域中,请在父级上设置position:relative,并在内容上设置position:absolute; height:100%; overflow-y: auto;,允许流内容元素(<svg>)设置大小,从而实现比例。

.ratio {
  display: grid;
  position: relative;
}
.ratio > * {
  grid-area: 1/1;
}


.ratio > div {
  height: 100%;
  overflow-y: auto;
  position: absolute;
  
  /* the rest is not needed */
  border: 1px solid red;
  padding: 0 1rem;
}
<div class="ratio">
  <svg viewBox="0 0 7 2"></svg>
  <div>
    <h1>Fixed ratio 7:2</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. A scelerisque purus semper eget. Sem nulla pharetra diam sit amet nisl suscipit adipiscing bibendum. A cras semper auctor neque vitae tempus quam pellentesque nec. Morbi enim nunc faucibus a pellentesque sit amet porttitor. Arcu odio ut sem nulla. Sed viverra ipsum nunc aliquet bibendum enim facilisis gravida neque. Cras tincidunt lobortis feugiat vivamus at augue eget. Laoreet sit amet cursus sit amet. Amet nulla facilisi morbi tempus iaculis urna id volutpat. Leo in vitae turpis massa sed elementum tempus egestas sed. Egestas integer eget aliquet nibh. Dolor sit amet consectetur adipiscing elit.

<p>Ut aliquam purus sit amet. Eget magna fermentum iaculis eu non diam phasellus vestibulum. Diam in arcu cursus euismod quis viverra nibh. Nullam vehicula ipsum a arcu cursus vitae congue. Vel orci porta non pulvinar neque laoreet suspendisse. At tellus at urna condimentum mattis pellentesque. Tristique senectus et netus et malesuada. Vel pretium lectus quam id leo in. Interdum velit euismod in pellentesque. Velit euismod in pellentesque massa placerat duis. Vitae suscipit tellus mauris a diam maecenas sed enim.

<p>Mauris a diam maecenas sed enim ut sem. In hendrerit gravida rutrum quisque. Amet dictum sit amet justo donec enim diam. Diam vulputate ut pharetra sit amet aliquam id. Urna porttitor rhoncus dolor purus non enim praesent. Purus in massa tempor nec feugiat nisl pretium. Sagittis vitae et leo duis ut. Facilisi nullam vehicula ipsum a arcu cursus vitae congue mauris. Volutpat odio facilisis mauris sit amet massa vitae tortor condimentum. Aliquam purus sit amet luctus venenatis lectus magna. Sit amet purus gravida quis blandit turpis. Enim eu turpis egestas pretium aenean. Consequat mauris nunc congue nisi. Nunc sed id semper risus in hendrerit gravida rutrum. Ante metus dictum at tempor. Blandit massa enim nec dui nunc mattis enim ut.
  </div>
</div>


正如@emjay在下面的评论中指出的那样,只要它被正确编码,svg比例可以放置在父元素的伪元素之一中:

.three-squares {
  display: grid;
  border: 1px solid red;
}

.three-squares > *, .three-squares:before {
  grid-area: 1/1;
}

.three-squares:before {
  content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 1'%3E%3C/svg%3E");
  line-height: 0;
}
<div class="three-squares">  
  <div>I'm 3:1</div>
</div>

当在伪元素内使用时,<svg> 成为一个 替换元素,默认情况下,它位于可变高度的基线上(在 Chrome 中为 4px,在 Firefox 中为 3.5px)。基线的高度根据 line-height 而变化,这就是为什么我们需要在伪元素上设置 line-height: 0 来获得准确的比例。更多细节请参见 此处


我个人更喜欢将<svg>放置在标记中的版本,因为我可以使用单个类(.ratio)处理各种比例的容器(而不是为每个可能需要的比例都有一个类)。

29

我找到了一种使用CSS的方法,但是你必须小心,因为它可能会根据你自己网站的流程而改变。我这样做是为了在我的网站中将具有恒定纵横比的视频嵌入到流动宽度部分中。

假设你有一个类似于以下嵌入式视频:

<object>
     <param ... /><param ... />...
     <embed src="..." ...</embed>
</object>
您可以将此所有内容放入具有“video”类的div中。该视频类可能是您网站中的流体元素,因此它本身没有直接的高度限制,但是当您调整浏览器大小时,它会根据网站的流动而改变其宽度。这可能是您尝试嵌入视频并同时保持特定纵横比的元素。
为了实现这一点,在“video”类div中嵌入对象之前先放置一张图片。
重要的是,图像具有您希望保持的正确纵横比。另外,请确保图像的��小至少与基于您的布局预计的最小视频(或任何维护A.R.的东西)相同。这将避免在百分比调整大小时出现任何分辨率问题。例如,如果您想保持3:2的宽高比,请不要仅使用3px乘2px的图像。它可能在某些情况下有效,但我没有测试过,并且最好避免使用。
您应该已经定义了此类流体元素的最小宽度。如果没有,最好这样做,以避免在浏览器窗口变得太小时切断元素或产生重叠。最小宽度应为600px左右(包括任何固定宽度列),因为屏幕分辨率不会更小,除非您处理面向手机的站点。
我使用了完全透明的png,但我认为如果您做得对,它并不重要。像这样:
<div class="video">
     <img class="maintainaspectratio" src="maintainaspectratio.png" />
     <object>
          <param ... /><param ... />...
          <embed src="..." ...</embed>
     </object>
</div>

现在,您应该能够添加类似于以下内容的CSS:

div.video { ...; position: relative; }
div.video img.maintainaspectratio { width: 100%; }
div.video object { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; }
div.video embed {width: 100%; height: 100%; }

在复制粘贴的嵌入代码中,确保也删除对象和嵌入标签中的任何显式高度或宽度声明。

它的工作方式取决于视频类元素和要维持特定宽高比的项目的位置属性。它利用了图像在调整大小时保持其适当宽高比的方式。它告诉视频类元素中的其他内容通过将其宽度/高度强制设置为动态图像调整的视频类元素的100%来充分利用动态图像提供的可用空间。

相当不错,是吧?

您可能需要根据自己的设计进行一些尝试才能使其正常工作,但对我来说,这实际上效果出奇的好。 总体概念已经在这里了。


20

Elliot启发了我这个解决方案 - 谢谢:

aspectratio.png是一个完全透明的PNG文件,大小为您喜欢的宽高比,例如我的情况是30x10像素。

HTML

<div class="eyecatcher">
  <img src="/img/aspectratio.png"/>
</div>

CSS3

.eyecatcher img {
  width: 100%;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  background-image: url(../img/autoresized-picture.jpg);
}

请注意:background-size 是一个 CSS3 的特性,可能无法在目标浏览器中正常工作。您可以检查互操作性(例如在caniuse.com上)。


18

正如 @web-tiki 已经展示如何使用 vh/vw,我也需要一种在屏幕中央对齐的方法,这里有一个针对 9:16 竖屏的代码片段。

.container {
  width: 100vw;
  height: calc(100vw * 16 / 9);
  transform: translateY(calc((100vw * 16 / 9 - 100vh) / -2));
}

translateY将保持此中心在屏幕上。calc(100vw * 16 / 9)是9/16的预期高度。(100vw * 16 / 9 - 100vh)是溢出高度,因此将溢出高度/2往上拉将使其保持居中在屏幕上。

对于横向布局并保持16:9,您可以使用

.container {
  width: 100vw;
  height: calc(100vw * 9 / 16);
  transform: translateY(calc((100vw * 9 / 16 - 100vh) / -2));
}

比例9/16很容易更改,不需要预定义100:56.25100:75。如果您想先确保高度,则应该交换宽度和高度,例如,在9:16肖像模式下使用height:100vh;width: calc(100vh * 9 / 16)

如果您希望适应不同的屏幕尺寸,您可能会对以下内容感兴趣:

  • background-size cover/contain
    • 上述样式类似于包含,取决于宽高比。
  • object-fit
    • img/video标签的cover/contain
  • @media (orientation: portrait)/@media (orientation: landscape)
    • 面向“portrait” / “landscape”的媒体查询以更改比例。

17

正如在 w3schools.com 上所述,并在这个被接受的答案中有所强调,使用百分比值的 padding 值 (重点在我):

指定包含元素宽度的百分比填充

因此,保持16:9纵横比的响应式 DIV 的正确示例如下:

CSS

.parent {
    position: relative;
    width: 100%;
}
.child {
    position: relative;
    padding-bottom: calc(100% * 9 / 16);
}
.child > div {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

HTML

<div class="parent">
    <div class="child">
        <div>Aspect is kept when resizing</div>
    </div>
</div>

在 JSFiddle 上的演示


16
为了补充Web_Designer的答案,<div>元素的高度(完全由底部填充区域组成)将是其包含元素宽度的75%。这是一个很好的总结:http://mattsnider.com/css-using-percent-for-margin-and-padding/。我不确定为什么会这样,但事实就是如此。
如果您希望
元素的宽度不是100%,您需要另一个包装
元素,并在该元素上设置宽度:
div.ar-outer{
    width: 60%; /* container; whatever width you want */
    margin: 0 auto; /* centered if you like */
}
div.ar {
    width:100%; /* 100% of width of container */
    padding-bottom: 75%; /* 75% of width of container */
    position:relative;
}
div.ar-inner {
    position: absolute;
    top: 0; bottom: 0; left: 0; right: 0;
}

最近我使用了类似于Elliot的图像技巧,使我能够使用CSS媒体查询根据设备分辨率提供不同的logo文件,但仍然按比例缩放,就像<img>自然地做的一样(我将logo设置为具有正确宽高比的透明.png背景图)。但Web_Designer的解决方案可以节省一个http请求。


很好的解决方案。在我的情况下,当将图像放置到DOM中时,我知道“自然”图像大小,并且我需要具有适当高度的图像占位符,以防止浏览器加载图像后进一步滚动。在.ar-outer中,我有像素的图像宽度,在.ar中,我有从实际图像纵横比计算出来的padding-bottom。而不是ar-inner,我有图像本身(<img>)。但是,我必须将宽度设置为100%,而不是设置顶部,底部,左侧和右侧。当我将maxWidth设置为ar-outer时,如果父级较小,则图像会很好地缩放,但不会缩放到比其自然大小更大的尺寸。 - Mi-La

16

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接