I have a nice little table with 4 elements which contain images.
The images are usually uploaded by users, so I don't have exact control over the image size.
What I am trying to do is create a 2 x 2 layout which resizes to fit the users screen and each box in the layout is given a 16:9 aspect ratio.
This works really well adjusting the window width, but if the user adjusts the height, the elements overflow rather than adjusting in height to fit the users screen.
You can see example here, and if you adjust your screen the horizontal width behavior is what I'm looking for, but adjusting vertically hides the images on smaller window sizes.
https://codepen.io/anon/pen/zaXEOL
.outer-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
max-height: 70vh;
max-width: 70vw;
overflow: hidden;
}
.holder {
border: 1px solid red;
max-width: 46%;
max-height: 46%;
margin: 1%;
display: flex;
align-items: center;
justify-content: center;
height: 0;
padding-top: 26%;
width: 100%;
position: relative;
}
img {
object-fit: contain;
max-height: 100%;
top: 0;
max-width: 100%;
width: 100%;
height: 100%
<div class="outer-grid">
<div class="holder">
<img src="http://fillmurray.com/200/300"/>
</div>
<div class="holder">
<img src="http://fillmurray.com/300/300"/>
</div>
<div class="holder">
<img src="http://fillmurray.com/300/200"/>
</div>
<div class="holder">
<img src="http://fillmurray.com/250/300"/>
</div>
</div>
The css I am using is fairly simple
.outer-grid {
display: grid;
grid-template-columns: repeat(2,1fr);
grid-template-rows: repeat(2,1fr);
grid-gap: 1rem;
max-height: 70vh;
max-width: 70vw;
overflow: hidden;
}
.holder {
border: 1px solid red;
max-width: 100%;
max-height: 100%;
display: flex;
align-items: center;
justify-content: center;
height: 0;
padding-top: 56%;
position: relative;
}
img {
position: absolute;
display: block;
top: 0;
max-height: 100%;
max-width: 100%;
}
First, note that your layout doesn't work at all in Firefox and Edge. This is because the percentage padding trick you are using, when applied in a grid container, doesn't work in all browsers. Here is a detailed explanation: Percentage padding / margin on grid item ignored in Firefox
Second, percentage padding re-sizes images based on their width. That's the whole trick.
From the spec:
ยง 8.4 Padding properties: padding-top, padding-right,
padding-bottom, padding-left, and
padding
<percentage>
The percentage is calculated with respect to the width of the
generated box's containing block, even for padding-top and
padding-bottom.
The images can re-size on the horizontal axis because percentage padding is associated with width. They can't re-size on the vertical axis because percentage padding has no association with height.
Related
I'm trying to have a div centered in the screen. This div should have a specific width and height when it fits in the available window space, but it should shrink to fit when the available window space is not enough, but also, maintaining its original aspect ratio.
I've been looking at lots of examples that work for a decreasing width, but none that work for both width and height changes in the window size.
Here's my current CSS:
* {
border: 0;
padding: 0;
margin: 0;
}
.stage_wrapper {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: gray;
}
.stage {
width: 960px;
height: 540px;
max-width: 90%;
max-height: 90%;
display: flex;
justify-content: center;
align-items: center;
background: chocolate;
object-fit: contain; /* I know this is for images, it's an example of what I'm looking for */
}
And, my current HTML:
<div class="stage_wrapper">
<div class="stage">
<p>Some text</p>
</div>
</div>
This is showing a centered div that has a fixed width of 960px and a fixed height of 540px. It should never be bigger than that.
Then, if I change the size of my window to have a smaller width or height than that, the div element is successfuly shrinking - except it is not maintaining the original aspect ratio, and that is what I'm looking for. I want it to respond to changes in both width and height.
Is this possible at all?
The aspect-ratio ref property has a good support now so you can use the below:
body {
height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
background: gray;
}
.stage {
--r: 960 / 540;
aspect-ratio: var(--r);
width:min(90%, min(960px, 90vh*(var(--r))));
display: flex;
justify-content: center;
align-items: center;
background:
/* this gradient is a proof that the ratio is maintained since the angle is fixed */
linear-gradient(30deg,red 50%,transparent 50%),
chocolate;
}
<div class="stage">
<p>Some text</p>
</div>
Old answer
Here is an idea using viewport unit and clamp(). It's a kind of if else to test if the width of the screen is bigger or smaller than the height (considering the ratio) and based on the result we do the calculation.
In the code below with have two variables cv and ch and only one of them will be equal to 1
If it's cv then the width is bigger so we set the height to cv and the width will be based on that height so logically cv/ratio
If it's ch then the height is bigger so we set the width to cv and the height will be based on that width so logically ch/ratio
In the clamp() I am using 1vh/1vw that I multiple by 90 which is equivalent to your 90% to have 90vh/90vw
body {
height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
background: gray;
}
.stage {
--r: calc(960 / 540);
--cv: clamp(0px,(100vw - 100vh*var(--r))*10000,1vh);
--ch: clamp(0px,(100vh*var(--r) - 100vw)*10000,1vw);
height: calc((var(--cv) + var(--ch)/var(--r)) * 90 );
width: calc((var(--ch) + var(--cv)*var(--r)) * 90 );
max-width: 960px;
max-height: 540px; /* OR calc(960px/var(--r)) */
display: flex;
justify-content: center;
align-items: center;
background:
/* this gradient is a proof that the ratio is maintained since the angle is fixed */
linear-gradient(30deg,red 50%,transparent 50%),
chocolate;
}
<div class="stage">
<p>Some text</p>
</div>
In theory the clamp can be simplified to:
--cv: clamp(0px,(1vw - 1vh*var(--r)),1vh);
--ch: clamp(0px,(1vh*var(--r) - 1vw),1vw);
But to avoid any rounding issue and to not fall into values like 0.x I consider a big value to make sure it will always be clamped to 1 if positive
UPDATE
It seems there is a bug with Firefox so here is another version of the same code:
body {
height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
background: gray;
}
.stage {
--r: calc(960 / 540);
--cv: clamp(0px,(100vw - 100vh*var(--r))*100,90vh);
--ch: clamp(0px,(100vh*var(--r) - 100vw)*100,90vw);
height: calc((var(--cv) + var(--ch)/var(--r)) );
width: calc((var(--ch) + var(--cv)*var(--r)) );
max-width: 960px;
max-height: 540px; /* OR calc(960px/var(--r)) */
display: flex;
justify-content: center;
align-items: center;
background:
/* this gradient is a proof that the ratio is maintained since the angle is fixed */
linear-gradient(30deg,red 50%,transparent 50%),
chocolate;
}
<div class="stage">
<p>Some text</p>
</div>
Any solution using vw & vh assumes your container is the viewport. But what if you need an element that letterboxes to fit any container?
In other words, you want the behavior of object-fit: contain for an element that's not an <img> or <video>.
You need a container that centers both vertically and horizontally:
#container {
display: flex;
align-items: center;
justify-content: center;
}
and a contained object with fixed aspect-ratio that stretches either its width or height to 100%:
#object {
aspect-ratio: 16/9;
overflow: hidden;
[dimension]: 100%;
}
where [dimension] is width if the container is tall, and height otherwise.
If you don't know ahead of time whether it's going to be tall or not, a little javascript is needed to check for tallness and decide which dimension to stretch.
function update() {
const isTall = container.clientWidth / container.clientHeight < aspectRatio;
object.style.width = isTall ? '100%' : 'auto';
object.style.height = isTall ? 'auto' : '100%';
}
new ResizeObserver(update).observe(container);
I don't think this dimension switching can be accomplished in pure CSS without javascript.
Here's a demo.
You can set your height and width like:
height: 80vw;
width: 80vw;
Then, you can set the max height and width like:
max-height: 100vh;
max-width: 100vh;
Or if you have other things like headers in the way then:
max-height: calc(100vh - 32px);
max-width: calc(100vh - 32px);
The 32px is how many pixels(or other measurements) it takes up
Use this CSS maybe:
.stage_wrapper {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: gray;
}
.stage {
width: 50vw;
height: 40vw;
max-width: 90%;
max-height: 90%;
display: flex;
justify-content: center;
align-items: center;
background: chocolate;
object-fit: contain; /* I know this is for images, it's an example of what I'm looking for */
}
You can adjust the code if you want to to be bigger or smaller, but this will let your div have a fixed aspect ratio.
For the desktop landing page, I want this composition of images to maintain it's height in relation to the browser height (75vh), otherwise this landing collage will become too horizontal/skinny as someone shrinks the width of their browser.
As it changes proportion with the page getting wider or narrower, I want the images inside to be cropped, not stretched.
I figured out a way to crop them, but only if I set an actual px height to the images. I don't want that. I want the height to be in proportion to the browser height. If I make the divs they're in a ratio of the height of the browser, then the images just stretch inside to fit the div.
<div class="landing">
<div class="landing-side"><img src="landing-1.jpg" alt=""></div>
<div class="landing-side"><img src="landing-2.jpg" alt=""></div>
<div class="landing-center"><img src="landing-3.jpg" alt=""></div>
<div class="landing-side"><img src="landing-4.jpg" alt=""></div>
<div class="landing-side"><img src="landing-5.jpg" alt=""></div>
</div>
.landing {
position: relative;
width: 100%;
height: auto;
display: flex;
}
.landing img {
width: 100%;
height: auto;
display: block;
}
.landing-center,
.landing-side {
height: 75vh;
display: flex;
flex: 1 1 auto;
overflow: hidden;
justify-content: center;
}
.landing-center {width: 40%;}
.landing-side {width: 15%;}
Here is how I did it before - at a certain browser width, the height would be fixed in px, then the side images would start getting cropped*. This is OK, but then I'd have to do make it hop to various specific image heights at different widths. Whereas I want the height of the whole larger container of images to change as the browser changes proportions.
.landing {
position: relative;
width: 100%;
display: flex;
}
.landing img {
width: 100%;
height: auto;
display: block;
}
.landing-center,
.landing-side {
display: flex;
flex: 1 1 auto;
overflow: hidden;
justify-content: center;
}
#media screen and (max-width: 1536px) {
.landing-center {flex-shrink: 0;}
.landing-center img,
.landing-side img {
width: auto;
height: 400px;
}
}
*(Note: I'd prefer that only the side images get cropped in the first code example as well, but that may complicate things too much for this one question.)
To croping an image, you need to use overflow: hidden;
just like:
.croped-content {
position: relative;
width: 25vw;
height: 70vw;
border: 2px solid red;
overflow: hidden;
}
.croped-content img {
min-width: 100%;
min-height: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
<div class="croped-content">
<img src="https://cdn.pixabay.com/photo/2017/10/06/07/29/guava-2822182_1280.png" alt="guava">
</div>
All I had to do is put the vh here, instead of the px. This works now. I got all mixed up trying too many ways to make this work that I missed the fact that I had put the height attribute to the container instead of the image.
.landing-center {flex-shrink: 0;}
.landing-center img,
.landing-side img {
width: auto;
**height: 75vh;**
}
I have an image which I want to display undistorted (it should scale, but not get distorted). However, when the container has a greater width-to-height aspect ratio than the image, then I want the left and right border of the image be aligned with the container and the top and bottom of the image be cropped, and when the aspect ratio is smaller than that of the image, then I want the top and bottom to be aligned with the top and bottom of the container and the left and right side of the image be cropped. So the image should always fill the container and be cropped and centered depending on container aspect ratio.
like this (red showing the aspect ratio of the container)
How can I do that with CSS?
I did try
.fill {
display: flex;
justify-content: center;
align-items: center;
overflow: hidden
}
.fill img {
flex-shrink: 0;
min-width: 100%;
min-height: 100%
}
from How can I fill a div with an image while keeping it proportional? but this just sticks the image into the top left corner of my window
Solution 1
CSS The object-fit Property
The CSS object-fit property is used to specify how an or should be resized to fit its container.
.img-outer {
width: 200px;
height: 200px;
overflow: hidden;
background: #000;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 18px !important;
}
.img-outer img {
object-fit: cover;
max-width: calc(100% + 50px);
min-height: 100%;
min-width: calc(100% + 50px);
max-height: calc(100% + 50px);
width: auto;
height: auto;
}
<div class="img-outer">
<img src="https://ichef.bbci.co.uk/news/976/cpsprodpb/12A9B/production/_111434467_gettyimages-1143489763.jpg">
</div>
<div class="img-outer">
<img src="https://images.unsplash.com/photo-1560115246-30778a6578be?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8OHx8Y3V0ZSUyMGNhdHxlbnwwfHwwfHw%3D&w=1000&q=80">
</div>
Solution 2
This should do it:
img {
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
}
I have a parent with a flex child :
.card {
background-color: white;
max-width: 80vw;
height: auto;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
position: fixed;
bottom: 10;
overflow: hidden;
border-radius: 32.5vw;
z-index: 100001;
}
.menusC {
width: auto;
height: auto;
margin: auto;
display: inline-flex;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
justify-content: center;
background-color: red;
}
<div class="card">
<div class="menusC">
<div class="menuBC">
......
</div>
</div>
</div>
As you can see my flex is inline-flex which means the flex get the size of its items.
But the parent card insist to have it's own width (takes the max otherwise 100%).
How would i make the card - be exactly at the width of the flex - menusC ?
I have not seen what it looks like when the code is executed, but why has the parent got a fixed position with left and right set to 0?
This is telling it to go all the way from left to right.
Maybe change position: fixed to something else, or remove the left or right properties if fixed position is needed.
I have a flexbox-based layout with two panels (top and bottom) occupying 1/3 and 2/3 of the viewport, respectively. (Actually there are more panels, but I've distilled it to the minimal example).
The top panel is also a flex container, because I want its children to flow top to bottom and be vertically centered when there is room. When there is more stuff in top panel than would fit in it, I want it to be scrollable, hence overflow: auto.
The problem: the contents of top shrink to its size, even with flex-shrink: 0, and the scrollbar never pops up.
Observe how the content is shrunk in the following demo, even though it has an explicitly specified height:
html, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.main {
display: flex;
height: 100%;
flex-direction: column;
}
.top {
display: flex;
flex-direction: column;
flex-basis: 33%;
border-bottom: 1px solid #ccc;
overflow: auto;
justify-content: center;
padding: 20px;
}
.bottom {
overflow: auto;
flex-basis: 67%;
}
.content {
height: 500px;
background-color: #eee;
}
<div class="main">
<div class="top">
<div class="content">Content</div>
</div>
<div class="bottom"></div>
</div>
The questions:
How to fix this while preserving the layout requirements? Disabling display: flex for the top panel gives the desired effect in the demo. I could position contents of top in a flexboxless way, but I lose the benefits of flex layout and the automatic vertical centering.
Why does this happen? References to CSS spec would be welcome.
You wrote:
The problem: the contents of top shrink to its size, even with flex-shrink: 0, and the scrollbar never pops up.
Actually, the solution is flex-shrink: 0. So the question becomes, where did you apply it?
It wouldn't work if you applied it to top โ a flex item in the primary container with flex-basis: 33% (i.e., height: 33%, in this case) โ because the length of top is a percentage. As such, it will shrink / expand naturally as percentage lengths are relative to the parent container.
You need to apply flex-shrink: 0 to .content โ a flex item in the nested container with a fixed height (height: 500px / flex-basis: 500px).
So this will work:
.content {
height: 500px;
flex-shrink: 0;
}
or this:
.content {
flex-basis: 500px;
flex-shrink: 0;
}
or, better yet, this:
.content {
flex: 0 0 500px; /* don't grow, don't shrink, stay fixed at 500px */
}
From the spec:
7.2. Components of
Flexibility
Authors are encouraged to control flexibility using the flex shorthand
rather than with its longhand properties directly, as the shorthand
correctly resets any unspecified components to accommodate common
uses.
body {
margin: 0;
}
.main {
display: flex;
flex-direction: column;
height: 100vh;
}
.top {
display: flex;
flex-direction: column;
flex-basis: 33%;
border-bottom: 1px solid #ccc;
overflow: auto;
justify-content: center;
padding: 20px;
}
.bottom {
overflow: auto;
flex-basis: 67%;
}
.content {
flex: 0 0 500px;
background-color: #eee;
}
<div class="main">
<div class="top">
<div class="content">Content</div>
</div>
<div class="bottom"></div>
</div>
Then you have a second problem, which is that the upper section of the top element gets cut off and is inaccessible via scroll. This is caused by justify-content: center on the container.
This is a known issue. It is solved by using flex auto margins.
So instead of this:
.top {
display: flex;
flex-direction: column;
flex-basis: 33%;
border-bottom: 1px solid #ccc;
overflow: auto;
/* justify-content: center; <--- REMOVE */
padding: 20px;
}
do this:
.content {
flex: 0 0 500px;
margin: auto 0; /* top & bottom auto margins */
background-color: #eee;
}
body {
margin: 0;
}
.main {
display: flex;
flex-direction: column;
height: 100vh;
}
.top {
display: flex;
flex-direction: column;
flex-basis: 33%;
border-bottom: 1px solid #ccc;
overflow: auto;
/* justify-content: center; USE AUTO MARGINS ON FLEX ITEM INSTEAD */
padding: 20px;
}
.bottom {
overflow: auto;
flex-basis: 67%;
}
.content {
flex: 0 0 500px;
margin: auto 0;
background-color: #eee;
}
<div class="main">
<div class="top">
<div class="content">Content</div>
</div>
<div class="bottom"></div>
</div>
Here's a complete explanation:
Can't scroll to top of flex item that is overflowing container
The scrollbar appears when there are enough .content element shrinked to their very minimal height (one line height in this case).
That's not really how things work with flex. height is not strictly respected. If you still want to work with height, you can fix this by setting a min-height to .content according to the minimum height you want for them.
Or you can instead set flex on .content (and get rid of height):
css
flex: 100px 1 0;
Which will set a minimum height (flex-basis) of 100px, flex-grow at 1 so that it takes all the available space, and flex-shrink at 0 so that the element is always at least 100px tall.