CSS Flex grid with overflow in X axis - html

I am trying to create a grid of tiles, which sounds easy enough.
The problem I run into is that I want to have each row overflow in the X axis and each tile to expand on hover.
When the tile is expanded on hover it should only push aside its siblings, not the row above/below.
I have made a version that works, but the code is not exactly elegant and the only reason in works is using negative bottom-margins to push the lines lines together as well as setting the aspect ratio on the background image. The way I understand it this happens because Flex is trying to fit all items into it's container. I also don't quite understand how setting the aspect ratio of the image can override flexbox's need to fit everything inside the frame.
I've made a code pen of it demonstrating the code, but I would love for someone to help me coming up with a better solution since this is quite messy!
If this could be achieved in HTML and CSS only that would be great.
So here's the requirements I want to have:
several rows with 11 tiles that overflows in the X direction without creating scrollbars so that the user can horizontal scroll on each line
when hovering over a tile it should push the items in the same row to the side, but not the line above/below
when hovering the tile size should increase by X%, but the content (text) should not increase in size
the box-shadow should not be cut off by the line below when hovering
Link to codepen
HTML:
<div class="grid">
<div class="row">
<div class="tile">
<div class="text">
<p>Text</p>
</div>
<img class="background-image" src="">
</div>
</div>
</div>
CSS
grid {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
/* firefox */
scrollbar-width: none;
overflow-x: auto;
}
/* chrome */
.grid::-webkit-scrollbar {
display: none;
}
.row {
position: relative;
display: flex;
align-items: center;
gap: 1vw;
height: 12vw;
margin-bottom: -4vw;
overflow-y: hidden;
/* FireFox */
scrollbar-width: none;
}
/* chrome */
.row::-webkit-scrollbar {
display: none;
}
.tile {
position: relative;
height: 6vw;
width: 10vw;
transition: 350ms all;
}
.tile:hover {
height: 8vw;
width: 13vw;
z-index: 2;
box-shadow: 1vw 0.5vw 1vw #00000075;
}
.text {
position: absolute;
bottom: 0;
font-size: 1.5vw;
height: 30%;
transition: all 250ms;
}
.tile:hover .text {
font-size: 2.5vw;
height: 100%;
z-index: 3;
}
.background-image {
object-fit: cover;
object-position: 50% 10%;
aspect-ratio: 1.62/1;
height: 100%;
}

Related

Shrink a row of images to fit container height and maintain aspect ratios

I'd like to know how to shrink a row of images so that they all fit within a div with an unspecified height. The images should never scale up beyond their native height, and they must maintain their aspect ratio. Also, I'd like the height of the containing div to be limited to the native height of the tallest image. The image tags have no height or width attributes.
Here's a fiddle with what I have so far. I approached this using flexbox and object-fit: scale-down. The row of images in question are gray and are in the div with the green background. They currently do not scale at all, but they are at least centered vertically and horizontally how I'd like them to be. Here are before and after images of the effect I'd like to achieve (sorry for switching the green background to yellow in the images). Additional details below the code snippet, but that about sums up the basic question.
body {
font-family: arial;
font-size: 26px;
text-align: center;
color: black;
background-color: white;
}
.smallhint {
font-size: 16px;
color: #8c8c8c;
}
img {
padding: 0;
margin: 0;
font-size: 0;
display: block;
object-fit: scale-down;
min-height: 0;
}
.flex-column {
display: flex;
flex-direction: column;
padding: 0px;
margin: 0px;
height: 90vh;
flex-grow: 0;
min-width: 0;
min-height: 0;
}
.flex-row {
display: flex;
flex-direction: row;
flex: 0 1.5 auto;
justify-content: center;
align-items: center;
background-color: green;
}
.context {
display: flex;
flex-direction: column;
max-height: 100%;
background-color: blue;
}
.primary {
position: relative;
z-index: 0;
right: 0;
bottom: 0;
padding: 0;
font-size: 0;
min-height: 0;
align-items: end;
background-color: orange;
}
.primary img {
margin-left: auto;
margin-right: auto;
border-style: solid;
border-width: 3px;
border-color: black;
height: calc(100% - 2*3px);
}
.mask {
position: absolute;
z-index: 1;
top: 0;
right: 0;
width: 100%;
height: 100%;
font-size: 0;
}
.nonimage {
padding-top: 5px;
display: inline;
background-color: pink;
}
<div class="flex-column">
<div class="primary">
<img src="https://via.placeholder.com/200">
<div class="mask">
<img src="https://via.placeholder.com/200/FF000">
</div>
</div>
<div class="flex-row">
<div class="context">
<img src="https://via.placeholder.com/75x150">
</div>
<div class="context">
<img src="https://via.placeholder.com/150x75">
</div>
</div>
<div class="nonimage">
<div class="smallhint">Some Text<br>Other Text</div>
</div>
</div>
I'm working on a (fixed-height) interface styled with CSS and will likely be asking a series of questions. I'm not great at CSS, so I'm open to approaches that are very different from my failed attempt!
At the top is a single centered image ("primary image"), below that are two other images ("secondary images") in a row, and below that is some text. Eventually, I'd like both sets of images to be responsive to changes in the height and width of the browser. However, I'd like to preferentially scale down the secondary images more than the primary image when the browser is too short to contain everything at native dimensions. For this, it seemed like flexbox containers with various flex-grow values would work here; it seems to work with the primary image somewhat, but the secondary images refuse to scale.
Also, I'm aware that, even if my current approach worked, the object-fit: scale-down strategy would leave behind some unwanted "padding" that will result in visual space between the secondary images. I have a feeling a very different approach may be required to get the effect that I want in the end, since I want the images to sit adjacent to each other without extra space around them. Furthermore, there also seems to be an issue with the container itself when the browser becomes very thin, since a scrollbar appears but it should always be 90vh.
Thank you all for the input!
Add a min-height: 0; rule for .flex-row. I guess that means it was pretty close to working when I asked the question.
This solution retains the issue I mention in my question about the additional "padding" created around images when object-fit: scale-down (or cover) is used. So, that means I'll be asking another question about that topic!

How to smoothly animate hiding of sidebar which effects size of other areas

I have a screen with 3 horizontally aligned main areas:
The sidebar (1) can be collapsed/hidden (to the left). When hidden, the freed-up space shall be given to the visualization (3) whilst the menus width (2) stays the same.
ATM I'm aligning the content like this:
Parent container of (1), (2) & (3): display: flex
(1): width: 260px;
(2): width: 293px;
(3): flex: 1;
When collapsed, I simply set width: 0 for the sidebar.
I know that I could simply animate/transition the width change or go about it by changing and animating/transition the left property or play with margins etc. but all those solutions I can think of trigger the browsers layout step (changing width, left, margin, ...) which I'd like to prevent since that leads to poor animation performance as explained here.
Ideally I'd like to stay with CSS transitions of properties which only trigger the browsers compose step like translate etc.
Unfortunately I can't think of a way to only use those "good" CSS properties and also meet my goal of redistributing the freed-up space from the hidden sidebar to the visualization (3).
Is it even possible to hide the sidebar animated to the left without triggering a re-layout but still redistribute the freed-up space? If not, how can this still be done performantly?
I imagine that this is a pretty common use case in web development, so links to according literature, blogs etc. are welcome as well!
I found a lot of examples on the web regarding animated hiding of sidebars but they either animate the width property or don't redistribute the freed up space to the remaining visible content (e.g. sidebars which are simply shown "above" the main content etc.), so none of the examples I found so far actually meet my described goals...
Please check the following HTML,JS,CSS snippet. You could change the actual widths of the elements to exactly match the ones you need.
Manipulating the width of the sidebar is not a problem. Performance is, as explained in the link you provided. On the finishing lines of this article it is written:
Performance matters to users. Web developers need to build apps that
react quickly and render smoothly. Google performance guru Paul Lewis
is here to help you destroy jank and create web apps that maintain 60
frames per second performance. You'll leave this course with the tools
you need to profile apps and identify the causes of jank. You'll
explore the browser's rendering pipeline and uncover patterns that
make it easy to build performant apps.
So you could re-create the logic of the below example by using Javascript requestAnimationFrame as shown here.
const side = document.querySelector('.side');
const sideToggle = document.querySelector('.sideToggle');
const main = document.querySelector('.main');
sideToggle.addEventListener('click', () => {
if (!sideToggle.classList.contains('active')) {
sideToggle.classList.add('active');
} else {
sideToggle.classList.remove('active');
}
if (!main.classList.contains('full')) {
main.classList.add('full');
} else {
main.classList.remove('full');
}
if (!side.classList.contains('hidden')) {
side.classList.add('hidden');
} else {
side.classList.remove('hidden');
}
});
.container {
display: inline-flex;
flex-direction: row;
align-items: stretch;
align-content: space-evenly;
justify-content: space-evenly;
width: 100%;
}
.side {
display: flex;
flex-direction: column;
align-items: stretch;
align-content: space-evenly;
justify-content: space-evenly;
background: #000;
padding: 0;
width: 20%;
height: 100vh;
transition: width 1s linear;
}
.side a {
color: #fff;
text-decoration: none;
line-height: 1;
height: 1.5rem;
padding: 1rem;
}
.side a:hover {
color: #000;
background: #fff;
}
.side.hidden {
width: 0;
transition: width 1s linear;
}
.sideToggle {
background: #000;
color: #fff;
width: 2rem;
height: 2rem;
position: fixed;
right: .75rem;
bottom: .75rem;
border-radius: 50%;
text-align: center;
cursor: pointer;
z-index: 1001;
}
.sideToggle:after {
content: "\2630";
font-size: 1.25rem;
vertical-align: top;
}
.sideToggle.active:after {
content: "\00D7";
vertical-align: top;
font-size: 1.75rem;
}
.main {
background: red;
width: 80%;
height: 100vh;
transition: width 1s linear;
display: inline-flex;
flex-direction: row;
align-items: stretch;
align-content: space-evenly;
justify-content: space-evenly;
width: 100%;
color: #fff;
}
.main.full {
width: 100%;
transition: width 1s linear;
}
.left {
width: 15rem;
padding: 1rem;
}
.right {
width: calc(100% - 15rem);
background: indigo;
padding: 1rem;
}
<div class="container">
<div class="side">
Home
Page 1
Page 2
Page 3
</div>
<span class="sideToggle active"></span>
<div class="main">
<div class="left">
this width is ment to be static
</div>
<div class="right">
this width is ment to be dynamic
</div>
</div>
</div>
This is another possible way by using mostly translateX and without changing the width of the sidebar.
const side = document.querySelector('.side');
const sideToggle = document.querySelector('.sideToggle');
const main = document.querySelector('.main');
sideToggle.addEventListener('click', () => {
if (!sideToggle.classList.contains('active')) {
sideToggle.classList.add('active');
} else {
sideToggle.classList.remove('active');
}
if (!main.classList.contains('full')) {
main.classList.add('full');
} else {
main.classList.remove('full');
}
if (!side.classList.contains('hidden')) {
side.classList.add('hidden');
} else {
side.classList.remove('hidden');
}
});
.container {
display: inline-flex;
flex-direction: row;
align-items: stretch;
align-content: space-evenly;
justify-content: space-evenly;
width: 100%;
}
.side {
display: flex;
flex-direction: column;
align-items: stretch;
align-content: space-evenly;
justify-content: space-evenly;
background: #000;
padding: 0;
width: 7rem;
transform: translateX(0);
height: 100vh;
transition: transform 1s linear, z-index 1s linear;
z-index: 9999;
position: fixed;
left: 0;
top: 0;
will-change: transform, z-index;
}
.side a {
color: #fff;
text-decoration: none;
line-height: 1;
height: 1.5rem;
padding: 1rem;
}
.side a:hover {
color: #000;
background: #fff;
}
.side.hidden {
transform: translateX(-100%);
transition: transform 1s linear, z-index 1s linear;
z-index: -1;
}
.sideToggle {
background: #000;
color: #fff;
width: 2rem;
height: 2rem;
position: fixed;
right: .75rem;
bottom: .75rem;
border-radius: 50%;
text-align: center;
cursor: pointer;
z-index: 1001;
}
.sideToggle:after {
content: "\2630";
font-size: 1.25rem;
vertical-align: top;
}
.sideToggle.active:after {
content: "\00D7";
vertical-align: top;
font-size: 1.75rem;
}
.main {
background: red;
width: calc(100% - 7rem);
height: 100vh;
transition: transform 1s linear, width 1s linear;
display: inline-flex;
flex-direction: row;
align-items: stretch;
align-content: space-evenly;
justify-content: space-evenly;
color: #fff;
left: 0;
top: 0;
transform: translateX(7rem);
position: absolute;
will-change: transform, width;
}
.main.full {
transform: translateX(0);
width: 100%;
transition: transform 1s linear, width 1s linear;
}
.main .left,
.main.full .left {
flex-grow: 1;
flex-shrink: 1;
width: 15rem;
padding: 1rem;
}
.right {
flex-grow: 2;
flex-shrink: 2;
width: calc(100% - 15rem);
background: indigo;
padding: 1rem;
}
<div class="container">
<div class="side">
Home
Page 1
Page 2
Page 3
</div>
<span class="sideToggle active"></span>
<div class="main">
<div class="left">
this width is ment to be static
</div>
<div class="right">
this width is ment to be dynamic
</div>
</div>
</div>
Althought the solution of user2560539 is nice, it did not suffice my requirements because with a lot of elements in the content area, the manipulation of the sidebar width leads definitely to a performance problem.
The issue is that as soon as you start transforming geometrical or positional properties (width, height, margin, padding, top, left, bottom, right, etc.) the browser starts to recalculate the layout again and again for every frame during the animation. You can see this in the performance tab of Chrome or Firefox as "Layout Shift". This is super laggy as soon as your DOM includes more than a few nodes.
Transformations (translations, rotations, skews, etc.) are much faster because the browser does not need to calculate all values regarding geometry and position over and over again. The browser calculates everything on a per pixel basis.
So I came up with another solution. What I did is to use the fast
transform: scaleX(1.1); //e.g. 1920px / 1745px
CSS property to enlarge the content area. The factor 1.1 is calculated by the width of the full content area (no sidebar visible) divided by the compressed content area (sidebar visible).
With this in mind you can use a simple translation animation to translate the sidebar and to scale the content area. Here is a codepen [slightly stolen from user2560539, thank you :) ]:
https://codepen.io/enne87/pen/MWBaOrp
Of course the content is distorted since scaleX does not only scale the content wrapper, but also all its child elements. If this is a problem for you, you can add a CSS class to the distorted elements which holds the inverse of the scaleX operation:
.make-thinner-initally {
transform: scaleX(0.9088); //e.g. 1745px / 1920px
}
Add this class to the elements as soon as the animation begins and remove it after the animation ends.
By the way, Google Calendar uses a very similar approach when you expand / collapse the left side bar.

Element with border-radius shows artifacts after transition in Microsoft Edge

I have an element with border-radius applied that appears to leave a sort of trail (visual bug) behind when returning to its normal width after being shrunk to accommodate other enlarged elements. This seems to only happen when the border-radius property is used and the glitch level is proportional to the value of border-radius.
Basically, there are two elements inside a container with display: flex. The second element increases in width on hover and so the other element needs to shrink in order to not overflow. When I stop hovering, on the second element, the first one returns to its normal width, but it leaves a strange visual trail of its edge (pun unintended).
Before hover:
During hover:
After hover (the bug):
#container {
display: flex;
align-items: center;
width: 50%;
margin: 0 auto;
}
#reduce {
background: #eee;
width: 100%;
height: 50px;
border-radius: 30px;
}
#hoverexpand {
transition: all 0.5s ease;
width: 20%;
}
#hoverexpand:hover {
width: 50%;
}
<div id="container">
<div id="reduce">
</div>
<div id="hoverexpand">
<span>Hover this</span>
</div>
</div>
Again, this only happens on Microsoft Edge and I'm baffled as to what might be causing it. Is this a known bug? Is there any workaround?
There is a workaround. You can force Edge to repaint the affected element by promoting it to a composite layer with translateZ.
You only have to set the following rule to your #reduce element:
transform: translateZ(0);
Here is the working example:
#container {
display: flex;
align-items: center;
width: 50%;
margin: 0 auto;
}
#reduce {
flex: 2 0;
background: #eee;
height: 50px;
border-radius: 30px;
transform: translateZ(0);
}
#hoverexpand {
flex: 1 0;
transition: flex 0.5s ease;
}
#hoverexpand:hover {
flex: 2 0;
}
<div id="container">
<div id="reduce">
</div>
<div id="hoverexpand">
<span>Hover this</span>
</div>
</div>

Positioning divs on top of an image that's utilizing the flex CSS property

For a web application, I'm to position an animated emoji along with some text in a div. These elements are to remain separated in a fully responsive way. Behold:
I'm using flex to accomplish this. That ensures that even if the screen size becomes extremely small, separation is still kept by stacking these one on top of the other.
To accomplish it, the whole outer div is wrapped in:
.act{
display: flex;
flex-wrap: wrap;
background-color: #E1F5FE;
padding: 10px;
border-radius: 10px;
align-items: center;
}
Next, the animated image inside the div is wrapped in:
.anim {
flex: 1 1;
min-width: 64px;
text-align: center;
}
.anim > img {
width: 100%;
height: auto;
max-width: 50px;
}
Lastly, the text along with the image is wrapped in:
.txt {
flex: 1 1 180px;
text-align: center;
}
Did you notice the tear drops on the emoji? Those are separate from the image, and are to be animated in html5.
I can't figure out how to ensure those tear drops stay precisely around the eyes of the emoji. I have tried using a z-index alongwith position:absolute (e.g. see the following):
<div class="anim">
<div class="tear" style="z-index:2;position:absolute;margin-top: 30px;margin-left: 110px;"></div>
<div class="tear" style="z-index:2;position:absolute;margin-top: 30px;margin-left: 84px;"></div>
<img src="sad.png">
</div>
This isn't responsive at all.
Moreover, If I try usingposition:relative, that makes it impossible to overlap the tear shape over the emoji, regardless of what z-index I set.
Please help me fix this situation. Ideally, I want to stick to using flex because otherwise, it's perfect for my needs.
Note: Answers to a similar SO question don't help since I've already included what they're suggesting.
To accomplish that you need a wrapper around the image and text, that take the size of the image.
Here is a sample code, where I added an extra wrapper, image, around the anim, and then made the anim display as inline block.
Here the image wrapper become the flex item instead, and will allow the anim to behave and be sized as the image, and create the boundaries you need to be able to place the eyes at a fixed position on top the image.
Stack snippet
.act {
display: flex;
flex-wrap: wrap;
background-color: #E1F5FE;
padding: 10px;
border-radius: 10px;
align-items: center;
}
.image {
flex: 1 1;
min-width: 64px;
text-align: center;
}
.anim {
display: inline-block;
position: relative;
}
.anim>img {
width: 100%;
max-width: 50px;
}
.txt {
flex: 1 1 180px;
text-align: center;
}
.tear {
position:absolute;
top: 10px;
left: 30px;
width: 10px;
height: 10px;
background: blue;
}
.tear:first-child {
left: 10px;"
}
<div class="act">
<div class="image">
<div class="anim">
<div class="tear"></div>
<div class="tear"></div>
<img src="http://placehold.it/150">
</div>
</div>
<div class="txt">
Some text
</div>
</div>

How to style a square div dynamically based on the height?

I already read this Q&A and the blog posts that it links to. The problem here is that the referred-to methods only work if you have a set width and are setting the height dynamically. But how can you do it if you need the opposite--the height is known but the width is not?
I have a visual element, specified in CSS, that I need to appear consistently in multiple contexts. (For the purposes of this exercise it may as well be a red square.) The contexts have set heights--e.g. a nav with height x, a list-item with height y, etc. The width of the element, therefore, needs to be based on the height -- not the other way around.
I tried reversing the method mentioned in the linked articles (to use height instead of width, and padding-right instead of padding-top), but it doesn't work -- the element winds up with no width, and thus the red square (.my-box .content) doesn't appear:
HTML:
<div class="outer">
<div class="my-box">
<div class="content">
</div>
</div>
<div class="more-stuff">
Some more stuff goes here
</div>
</div>
CSS:
.outer {
background-color: white;
height: 72px;
padding: 12px;
display: flex;
flex-direction: row;
}
.my-box {
height: 100%;
position: relative;
}
.my-box::before {
content: "";
display: block;
padding-left: 100%;
}
.content {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: red;
}
.more-stuff {
flex: 1 1 auto;
margin-left: 12px;
}
JSFiddle for the above
Does anyone know if this is possible without JS or hardcoding the dimensions every time? Googling finds nothing that works for dynamic height--even with quotation marks. :(
JS is the only reliable option in your case.
In CSS box model (and HTML in general) width and height are not symmetric.
In general HTML has "endless tape" layout model - width is fixed (by view/window) but height is not known upfront - needs to be calculated.
CSS does layout in following steps:
do horizontal layout inside left and right bounds. At the end this will give you content height.
If height:auto (by default) set element height computed at step #1.
Do vertical alignment if needed (table-cell and flexbox elements).
Mathematically speaking height = F(width,content) and F here is a step function - different input width values may give you same output height value. Step function has no determined inverse function - there is no such an inverse function F' that will allow you to calculate width = F'(height,content).
(My pardon for the math on pure CSS subject, but I don't know how to explain it otherwise.)
A little bit a hack, but still a solution.
We create a box with ratio 1:1, set the width and rotate box transform: rotateZ(90deg).
*,
*:before,
*:after {
box-sizing: inherit;
}
html {
box-sizing: border-box;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
body {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
background-color: #F72F4E;
overflow: hidden;
}
.Box {
width: 50vmin; /* or 400px for ex. */
position: relative;
background-color: rgba(0,0,0,.2);
overflow: hidden;
position: relative;
-webkit-transform: rotateZ(90deg);
transform: rotateZ(90deg);
}
.Box:before {
content: "";
display: block;
height: 0;
padding-top: 100%;
}
.Box__content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 12px;
-webkit-transform: rotateZ(-90deg);
transform: rotateZ(-90deg);
}
<div class="Box">
<div class="Box__content">Box</div>
</div>
P.S.: SVG can be useful in that case, i hope.