126

Using the CSS flex box model, how can I force an image to maintain its aspect ratio?

JS Fiddle: http://jsfiddle.net/xLc2Le0k/2/

Notice that the images stretch or shrink in order to fill the width of the container. That's fine, but can we also have it stretch or shrink the height to maintain the image proportions?

HTML

<div class="slider">
    <img src="http://www.placebacon.net/400/300" alt="Bacn">
    <img src="http://www.placebacon.net/400/300" alt="Bacn">
    <img src="http://www.placebacon.net/400/300" alt="Bacn">
    <img src="http://www.placebacon.net/400/300" alt="Bacn">
</div>

CSS

.slider {
    display: flex;
}
.slider img {
    margin: 0 5px;
}
Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
coderMe
  • 2,099
  • 3
  • 18
  • 20
  • 1
    As far as I know, the best solution is to use a `
    ` with the CSS `background-image: url(...);background-size:contain; background-repeat:no-repeat`
    – Blazemonger Oct 16 '15 at 14:44
  • 1
    There are some interesting leads here, but what about when the images aren't already the same ratio? Adding and 'extra' div, is the last thing we need to worry about. Why not use a test case like this instead: https://jsfiddle.net/sheriffderek/999Lg9qv/ – sheriffderek Aug 05 '16 at 17:31

9 Answers9

87

To keep images from stretching in either axis inside a flex parent I have found a couple of solutions.

You can try using object-fit on the image which, e.g.

object-fit: contain;

http://jsfiddle.net/ykw3sfjd/

Or you can add flex-specfic rules which may work better in some cases.

align-self: center;
flex: 0 0 auto;
Matty Balaam
  • 1,259
  • 8
  • 13
  • 4
    object-fit could be a good solution were it not [missing support in IE and Edge](http://caniuse.com/#search=object-fit) even up to Edge 14 – daniels Jul 04 '16 at 06:54
  • 1
    Check out this [object-fit fallback for Edge an IE](https://medium.com/@primozcigler/neat-trick-for-css-object-fit-fallback-on-edge-and-other-browsers-afbc53bbb2c3). – Andrei Telteu Aug 10 '16 at 09:58
  • I've been using that fallback myself, works very well. – Matty Balaam Aug 10 '16 at 14:41
  • 1
    @daniels Edge now has object-fit in development: https://developer.microsoft.com/en-us/microsoft-edge/platform/status/objectfitandobjectposition/ – AMDG May 15 '17 at 16:34
  • 1
    `object-fit: contain;` did the trick for me, when I only had the problem in chrome, ff was fine. – Benjamin Peter May 19 '17 at 16:21
  • `object-fit:contain` and `align-self: center; flex: 0 0 auto;` works better than setting img width with : `width: 100%;` because image with and height are still in ratio. – Moff452 Apr 09 '19 at 13:22
70

For img tags if you define one side then other side is resized to keep aspect ratio and by default images expand to their original size.

Using this fact if you wrap each img tag into div tag and set its width to 100% of parent div then height will be according to aspect ratio as you wanted.

http://jsfiddle.net/0o5tpbxg/

* {
    margin: 0;
    padding: 0;
}
.slider {
    display: flex;
}
.slider .slide img {
    width: 100%;
}
Samuel Liew
  • 76,741
  • 107
  • 159
  • 260
Muhammad Umer
  • 17,263
  • 19
  • 97
  • 168
  • 2
    I know there are two answers that suggest using divs, but I am marking this answer correct because it explains **why** the image heights are different than I expected. In fact, this is normal & correct behavior, and is unlikely to be "fixed", since it is not a bug. – coderMe Jun 20 '15 at 14:17
  • 20
    But this answer doesn't talk anything about flexbox, because images do behave differently within flexbox container. Setting one side will not proportionally resize the other. Wrapping them in non flexbox container helps but that adds unnecessary markup losing semanticity. – Robert Koritnik Feb 17 '16 at 12:41
  • 3
    It doesn't look like you need a wrapping div to make this work: http://jsfiddle.net/xLc2Le0k/120/ – Rick Strahl Mar 18 '16 at 21:37
  • 4
    But, what about this? http://jsfiddle.net/xLc2Le0k/15/ I think that this solution is a fluke that only works because the images are all the same ratio. side note: there isn't any 'semanticity' lost by placing an image in a div for positioning / especially when dealing with responsive images. – sheriffderek Aug 05 '16 at 17:35
  • @RickStrahl Your solution is actually better than that in the answer. – Mikhail Batcer Feb 03 '17 at 13:36
  • 1
    Seriously the cleanest solution and it works with picture! – shaneonabike Oct 09 '19 at 13:10
68

No need to add a containing div.

The default for the css "align-items" property is "stretch" which is what is causing your images to be stretched to its full original height. Setting the css "align-items" property to "flex-start" fixes your issue.

.slider {
    display: flex;
    align-items: flex-start;  /* ADD THIS! */
}
Marvin Herbold
  • 761
  • 5
  • 4
36

Most of images with intrinsic dimensions, that is a natural size, like a jpeg image. If the specified size defines one of both the width and the height, the missing value is determined using the intrinsic ratio... - see MDN.

But that doesn't work as expected if the images that are being set as direct flex items with the current Flexible Box Layout Module Level 1, as far as I know.

See these discussions and bug reports might be related:

  • Flexbugs #14 - Chrome/Flexbox Intrinsic Sizing not implemented correctly.
  • Firefox Bug 972595 - Flex containers should use "flex-basis" instead of "width" for computing intrinsic widths of flex items
  • Chromium Issue 249112 - In Flexbox, allow intrinsic aspect ratios to inform the main-size calculation.

As a workaround, you could wrap each <img> with a <div> or a <span>, or so.

jsFiddle

.slider {
  display: flex;
}

.slider>div {
  min-width: 0; /* why? see below. */
}

.slider>div>img {
  max-width: 100%;
  height: auto;
}
<div class="slider">
  <div><img src="https://picsum.photos/400/300?image=0" /></div>
  <div><img src="https://picsum.photos/400/300?image=1" /></div>
  <div><img src="https://picsum.photos/400/300?image=2" /></div>
  <div><img src="https://picsum.photos/400/300?image=3" /></div>
</div>

4.5 Implied Minimum Size of Flex Items

To provide a more reasonable default minimum size for flex items, this specification introduces a new auto value as the initial value of the min-width and min-height properties defined in CSS 2.1.


Alternatively, you can use CSS table layout instead, which you'll get similar results as flexbox, it will work on more browsers, even for IE8.

jsFiddle

.slider {
  display: table;
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
}

.slider>div {
  display: table-cell;
  vertical-align: top;
}

.slider>div>img {
  max-width: 100%;
  height: auto;
}
<div class="slider">
  <div><img src="https://picsum.photos/400/300?image=0" /></div>
  <div><img src="https://picsum.photos/400/300?image=1" /></div>
  <div><img src="https://picsum.photos/400/300?image=2" /></div>
  <div><img src="https://picsum.photos/400/300?image=3" /></div>
</div>
Stickers
  • 75,527
  • 23
  • 147
  • 186
  • The first snippet needs `.slider > div{min-width:0}` on new browsers that support `min-width: auto`. – Oriol Jun 12 '15 at 13:22
  • 3
    The flexbox spec introduced a new `auto` value as the default value for `min-width` and `min-height` (previously it was `0`). Chrome does not implement it yet, so your first snippet works. But new browsers need it in order to allow the flex items to shrink smaller than the default width of the images. – Oriol Jun 12 '15 at 20:54
  • 3
    +1 this should be accepted answer because it talks about images in flexbox model which is **the root problem** in this case... – Robert Koritnik Feb 17 '16 at 12:43
  • Table is great if your layout doesn't change at various break-points, but that is unlikely these days. – sheriffderek Aug 05 '16 at 17:37
  • 1
    What about when the images aren't all the same ratio? https://jsfiddle.net/sheriffderek/5u7y21we/ – sheriffderek Aug 05 '16 at 17:40
6

I have been playing around flexbox lately and i came to solution for this through experimentation and the following reasoning. However, in reality I'm not sure if this is exactly what happens.

If real width is affected by flex system. So after width of elements hit max width of parent they extra width set in css is ignored. Then it's safe to set width to 100%.

Since height of img tag is derived from image itself then setting height to 0% could do something. (this is where i am unclear as to what...but it made sense to me that it should fix it)

DEMO

(remember saw it here first!)

.slider {
    display: flex;
}
.slider img {
    height: 0%;
    width: 100%;
    margin: 0 5px;
}

Works only in chrome yet

Muhammad Umer
  • 17,263
  • 19
  • 97
  • 168
  • Interesting, but it seems a bug. According to [the spec](http://www.w3.org/TR/CSS21/visudet.html#x12), "*The percentage is calculated with respect to the height of the generated box's containing block. If the height of the containing block is not specified explicitly (i.e., it depends on content height), and this element is not absolutely positioned, the value computes to `auto`*". That's what happens on Firefox. – Oriol Jun 12 '15 at 13:31
  • http://jsfiddle.net/xLc2Le0k/8/ explain the difference in ff and chrome. for this – Muhammad Umer Jun 12 '15 at 16:05
  • width seems to be doing in ff what height is doing in chrome. – Muhammad Umer Jun 12 '15 at 16:06
  • It is interesting, but I'm afraid that X-browser-only solutions are not really solutions. Your commented fiddle, here: http://jsfiddle.net/xLc2Le0k/8/ is absolutely **fascinating**, though, in FF. This seems to show that FF is making the image height "proportionate" to the height of the image if it were truly 100%. In other words, the img doesn't "know" about the container's display: flex. This explains why, in FF, if you set the width of the img to 100%, the image is taller than if you set the width to auto. – coderMe Jun 20 '15 at 14:11
  • This is kinda close... but only works in Chrome: http://jsfiddle.net/sheriffderek/xLc2Le0k/270/ – sheriffderek Aug 05 '16 at 17:49
4

This behavior is expected. flex container will stretch all its children by default. Image have no exception. (ie, parent will have align-items: stretch property )

To keep the aspect ratio we've two solutions:

  1. Either replace default align-items: stretch property to align-items: flex-start or align-self: center etc, http://jsfiddle.net/e394Lqnt/3/

or

  1. Set align property exclusively to the child itself: like, align-self: center or align-self: flex-start etc. http://jsfiddle.net/e394Lqnt/2/
Asim K T
  • 16,864
  • 10
  • 77
  • 99
0

Declare where display: flex; was given Element.
align-items: center;

ZungTa
  • 310
  • 3
  • 9
-1

Actually I found out that the container of the image tag need to have a height in order to keep the ratio of the image. for example>

.container{display:flex; height:100%;} img{width:100%;height:auto;}

then in your html your container will wrap the tag image

<div class="container"><img src="image.jpg" alt="image" /></div>

this work for IE and other browsers

-1

I just had the same issue. Use the flex align to center your elements. You need to use align-items: center instead of align-content: center. This will maintain the aspect ratio, while align-content will not keep the aspect ratio.

Clinton Green
  • 9,697
  • 21
  • 68
  • 103