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

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>

Related

CSS Flex grid with overflow in X axis

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%;
}

How to animate width on parent element with display:block/none on child elem

I have two children in one parent element where first child is always visible but second one is visible on hover only. Since I have fit-content set for parent's width when I hover the parent elem expands. What I need is a smooth transition for this expansion. I do all this in React. Thanks
Check context please:
.parent{
display: flex;
justify-content: space-evenly;
background-color: #f31d84;
width: fit-content;
}
.icon{
margin: 5px;
}
.text{
display: none;
margin: 5px;
}
.parent:hover .text{
display: block;
}
<div>
<div class='parent'>
<div class='icon'>
icon
</div>
<div class='text'>
texttexttexttext
</div>
</div>
</div>
I've managed to find a solution for my problem. The code:
<div>
<div class='parent'>
<div class='icon'>
icon
</div>
<div className='text-wrapper'>
<div class='text'>
texttexttexttext
</div>
</div>
</div>
</div>
.parent{
display: flex;
justify-content: space-evenly;
background-color: #f31d84;
width: fit-content;
}
.icon{
margin: 5px;
}
.text{
opacity: 0;
max-width: 0;
margin: 5px;
transition: 5s ease;
}
.text-wrapper{
width: auto;
}
.parent:hover .text{
opacity: 1;
max-width: 1000px;
}
You can't animate what is not a value. I mean that going from display: none to display: block can't be animate because they are not values.
It's a wrong implementation. Your parent element width in this example doesn't change on hover. If u want to animate things, a good practice is set width 0 to the child element and when parent element gets hovered div with the class text gets 50% of parent's width. you can achieve this through css only and react way. If u are interested let me know to edit this answer.
Transitioning from display: none to display: block will not work. Indeed, there is only a finite number of CSS properties that can be animated and display is not one (see: Animatable CSS properties on MDN).
The common way to do such a thing is to transition from width: 0 to width: 50% (or whatever value you want your block to be, just remember that auto will not work).
.parent {
display: flex;
justify-content: space-evenly;
background-color: #f31d84;
width: fit-content;
}
.icon {
margin: 5px;
}
.text {
display: block;
margin: 5px;
width: 0;
overflow: hidden;
transition: all .2s ease;
}
.parent:hover .text {
width: 50%;
transition: all .2s ease;
}
<div>
<div class='parent'>
<div class='icon'>
icon
</div>
<div class='text'>
texttexttexttext
</div>
</div>
</div>

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.

Reduce bounds of expanded area for hover

Consider this contrived example. I have two divs, one of which grows to twice the size when you hover over it. When you move your mouse out of the edge of the expanded box, it returns to normal size.
Notably, it only returns to normal size once your mouse leaves the bounds of the expanded box. What I would like is for the box to go back to its normal size when your mouse leaves the bounds of the original size of the box.
I've been doing some reading of other questions and I think maybe pseudo elements are the way forward, but I can't figure it out. There are examples of maintaining the bounding box size when the element shrinks, but not when it expands.
.left {
width:300px; float:left;
}
.right {
width:300px; float:right; background-color: red;
}
.right:hover {
transform: scale(2);
}
<br>
</br>
<div style="width:800px">
<div class="left">foo</div>
<div class="right">bar</div>
</div>
Add pointer-events: none; to the div you want to transform. Apply the :hover effect to a parent div wrapping the transforming div. Example:
html, body {
height: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
.box {
height: 100px;
width: 100px;
background: tomato;
pointer-events: none;
}
.parentBox:hover .box {
transform: scale(2);
}
<div class="parentBox">
<div class="box"></div>
</div>
You can also make the box react to events by putting the event handlers on the box's parent, example on click:
html, body {
height: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
.box {
height: 100px;
width: 100px;
background: tomato;
pointer-events: none;
}
.parentBox:hover .box {
transform: scale(2);
}
<div class="parentBox" onclick="alert('You clicked the box!');">
<div class="box"></div>
</div>

Gap on the right side when transitioning width

I've a vertical full screen menu, with three columns. On hover, the actual columns width grow, the others shrink. The only problem, that there is a little gap on the right side when the cursor is moved fast between columns.
The code:
.service-navigation {
height: 100%;
width: 100%;
}
.service-navigation:hover .service-navigation-element {
width: 30%;
}
.service-navigation:hover .service-navigation-element:hover {
width: 40%;
}
.service-navigation-element {
width: 33.333333%;
height: 100%;
transition: .6s ease;
float: left;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
text-align: center;
box-sizing: content-box;
overflow: hidden;
}
How can I remove that gap, and why is it there?
How can I remove that gap,...
One solution could be to use flex. It will make things much easier and smooth. Just change the flex-basis on hover.
Example 1:
* { box-sizing: border-box; padding: 0; margin: 0; }
body, html { height: 100vh; width: 100vw; }
.service-navigation { height: 100%; width: 100%; display: flex; }
.service-navigation-element { flex: 1 1 33%; transition: .6s ease; }
.service-navigation-element:hover { flex-basis: 50%; }
.service-navigation-element:nth-child(1) { background-color: red; }
.service-navigation-element:nth-child(2) { background-color: green; }
.service-navigation-element:nth-child(3) { background-color: blue; }
<div class="service-navigation">
<div class="service-navigation-element">1</div>
<div class="service-navigation-element">2</div>
<div class="service-navigation-element">3</div>
</div>
Another solution could be to use display: table on the container and display: table-cell on the children.
Example 2:
* { box-sizing: border-box; padding: 0; margin: 0; }
body, html { height: 100vh; width: 100vw; }
.service-navigation { height: 100%; width: 100%; display: table; }
.service-navigation-element { display: table-cell; width: 10%; transition: .6s ease; }
.service-navigation-element:hover { width: 20%; }
.service-navigation-element:nth-child(1) { background-color: red; }
.service-navigation-element:nth-child(2) { background-color: green; }
.service-navigation-element:nth-child(3) { background-color: blue; }
<div class="service-navigation">
<div class="service-navigation-element">1</div>
<div class="service-navigation-element">2</div>
<div class="service-navigation-element">3</div>
</div>
However, there is a problem with this second approach. You have to provide an initial width to the children (width:auto will not fire transtions). Also, it is not possible to precisely control the width of the children once hovered. The table-cell would re-calculate the widths on its own. I would recommend the first approach of using flex.
and why is it there?
I think that it is because of these two rules:
.service-navigation:hover .service-navigation-element {
width: 30%;
}
.service-navigation:hover .service-navigation-element:hover {
width: 40%;
}
When you hover on the container, all the children start reducing to 30% width from 33.3333%. When that happens there will be extra space on the right (30 * 3 != 100). Meanwhile, the child on which hover is fired starts increasing its width to 40%. When you do it slowly, there is enough time for both the transitions to complete smoothly. However, when you hover quickly then one transition is not yet complete when another starts and it abruptly ends, causing flicker.
The gap results from the animate-style.
The default transition-timing-function is ease-in-out, wich can be described in a cubic bezier curve. possible solutions may be to set transition-timing-function: linear or adding a transition-delay.
Also the animation-duration is calculated everytime you enter an element. So leaving an element while its at full width and then quickly reentering it will cause the animation from 90% width to 100% width to be the full .6s long