High render usage with animations on mouse move - css

I found some interesting behavior when testing the performance of some CSS animations. For example, I can animate these 1,000 elements and on my PC they are barely using any resources.
It's honestly impressive that it preforms that well, but if I'm moving the mouse, it's a completely different story.
The only difference is that I am constantly moving my mouse. I want to know why this happens, and if there is anything I can do to improve it. None of these elements have any mouse interaction, no JavaScript listeners, no CSS hover effects or anything like that. I am confused why moving the mouse would then have such a major effect. This high resource usage also happens even when the elements aren't visible and are scrolled past. The window is focused for all benchmarks.
The animation that I am talking about are using transforms and opacity. When using other properties like width, height, margin I get high resource usage no matter what.
Here are several things that I have tried to change this with no noticeable difference in performance.
pointer-events: none, unsurprisingly no effect. I didn't think it worked like that.
will-change
display: inline-block vs position: absolute layout
transform vs opacity animations
translate3d vs translate
Elements outside of the viewport (scrolled past)
I'm guessing this has to do with a GPU and as soon as the mouse moves, it requires the CPU to do something. This would explain the lack of difference on properties that require to CPU to calculate, and properties that can use the GPU are effected so dramatically.
I'm noticing this using Brave and Edge for sure. I don't under the Firefox tool enough to confidently say the same for them, although it appears to happen as well.
How can I make this always run as in the first case? I am never using any mouse interaction, so these elements could careless what it happening with the mouse from my perspective.
Here is a code snippet that should be able to produce the effect. I also have here on a GitHub pages site.
https://jack-stoller.github.io/so_demo.html
<style>
body {
padding-bottom: 150vw;
}
div {
position: absolute;
height: 50px;
width: 50px;
pointer-events: none;
will-change: transform;
background-color: red;
animation: Animation 2s ease infinite;
}
#keyframes Animation {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
</style>
<body></body>
<script>
const size = 50 + 8 + 8;
const count = Math.floor(window.innerWidth / size);
[...Array(1000)].forEach((_, i) => {
let div = document.createElement('div');
div.style.top = Math.floor(i / count) * size + 'px';
div.style.left = Math.floor(i % count) * size + 'px';
document.body.appendChild(div);
});
</script>

Related

CSS: Combination of Visibility and Opacity

In my project, I have a button that I want to display and hide under certain conditions. When the button is hidden, it should still take up space (meaning display: none; is not what I want) but it should not be visible and the user should not be able to interact with it. Changing the visibility does everything I want, but then it appears and disappears instantly, and I would like to have a smooth transition. When using the opacity, I can set the transition duration, but then the cursor still changes to a pointer when hovering over the button. By accident, I discovered that I can simply combine these two things to get the desired result: When I click on the button in the example, it fades out slowly, but once it is gone I cannot interact with it anymore. However, I don't understand why it works that way, since the visibility changes instantly, and I also don't know if it works like this in all major browsers. Could somebody answer these two things for me, please?
function hide() {
document.querySelector("button").style.visibility = "hidden";
document.querySelector("button").style.opacity = "0";
}
button {
transition-duration: 1s;
}
<button onclick="hide()">This is a test.</button>
You can use opacity and pointer-events. Also please don't use javascript for css stuff. In my example I add a class instead.
const button = document.querySelector('.js-disable')
button.addEventListener('click', function() {
button.classList.add('button--is-disabled')
})
.button {
background-color: #ededed;
padding: .5rem 1rem;
border: none;
}
.button--is-disabled {
opacity: 0;
pointer-events: none;
transition: opacity 2s;
}
<button class="button js-disable">Click me</button>
The question was basically: why does this work when visibility is changed instantly.
This is in fact not correct. visibility is interpolated and as you have the transition set on everything, the visibility will transition.
Obviously it can not have continuous values, but it 'chooses' a value of visible or hidden, which are the two endpoints you have specified, in steps. The place(s) at which it changes are determined by the timing function.
So, your code works absolutely fine. The element fades away with the opacity and at some point (near the end in the case of the ease timing function) it will switch from visible to hidden.
See for example MDN:
Visibility values are interpolated between visible and not-visible.

unexpected use of low performance transition property (height)

.sampleClass {
transition: height 1s cubic-bezier(0.19, 01, 0.1, 0.01);
}
I am trying to use the transition property like above but when I run the lint checks it throws the following error and similar errors when using width or all
126:15 ⚠ Unexpected use of plugin/no-low-performance-animation-properties
low performance
transition property
(height).
Some ways of transitioning encourage the system to use the GPU where possible - though this is a complex subject so difficult to lay down hard and fast rules for all cases.
In this particular instance you may like to try transition on a scaleY rather than a height and see if that is more acceptable. This specific idea is discussed in https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/ where they give this example -
<div id="a"></div>
<div id="b"></div>
<style>
#a, #b {
will-change: transform;
}
#a {
width: 100px;
height: 100px;
}
#b {
width: 10px;
height: 10px;
transform: scale(10);
}
</style>
the second (scale) version being 100 times smaller than the first (separated out). Of course you will want to use scaleY for your height example.
It's worth reading the article and picking up a few tips on what may be best in a particular instance. One thing that is stated is that CSS animations use the GPU, I don't know enough to recommend definite ways forward, try it and lint will tell you I guess.
That's telling you that a transition on the "height" property is a really expensive operation from a performance standpoint.
Animating height, width, or child content that impacts the size of an element, forces the browser to re-calculate positions of all elements and repaint the page. If you're trying to do that calculation for each frame of animation it's likely to get janky.
This is a bit old, but the main concepts still apply HTML5Rocks High-Performance Animations.
The goal is to animate properties that can be done with minimal layout changes. Typically this means changing things outside the DOM flow (scale(), translate(), etc)

Css scale text still blurry after searching on StackOverflow

URL: https://www.royalsmushicafe.dk/
I have issues with the left side menu text looking blurry on mouseover. It's as if it's blurry during the animation and turns crisp again only after the animation is over. In Safari it stays blurry.
I'm using Transform: scale(1.2) and -webkit-font-smoothing: subpixel-antialiased;, but have tried quite a lot of suggested solutions.
I've been browsing StackOverflow and Google without luck, with suggestions like using transform perspective(1px), scale3d, translate3d( 0, 0, 0), backface-visibility: hidden even filter: blur(0) and whatnot – nothing has resulted in the expected behaviour of a crisp text scaling on mouseover :(
Any help would be much appreciated
I've just had almost the exact same problem, and found all the same hack ideas for perspective(1px), backface-visibility: hidden, and so on, with no success. Chrome and Firefox are fine, but scaled-up text blurs horribly in Safari. For anyone else experiencing this, the band-aid solution is to scale down instead of up.
In my case, I have a label that moves and changes scale when the input has content:
label {
will-change: transform, color;
transform: translate3d(0,0,0);
transform-origin: top left;
height: 20px;
}
input:placeholder-shown:not(:focus) + label {
transform: translate3d(0,.6rem,0) scale3d(1.5,1.5,1);
}
Idea here is that the label has the same styles if the input is focused or has content, and is bigger if the input is empty and unfocused (hence the funky pseudoclass selector.)
The problem is that using transform like this triggers GPU compositing on the <label>. When this happens, the composited layer is rendered like a bitmap in the GPU, at the dimensions calculated during CSS layout (20px here). Then, the GPU layer is scaled up (in this case by 1.5x, so it's now 30px high, and blurry).
Chrome and Firefox seem to re-render the layer at final scale which un-blurs it. Safari does not, probably to save memory since the composited layer is dimensionally smaller (20px high instead of 30px).
To make it work better, I opted to scale down:
label {
will-change: transform, color;
/* reverse the scaling ratio */
transform: translate3d(0,0,0) scale3d(0.67, 0.67, 1);
transform-origin: top left;
height: 20px;
}
input:placeholder-shown:not(:focus) + label {
/* reset to scale of 1 */
transform: translate3d(0,.6rem,0) scale3d(1,1,1);
}
The scaled-down text is a tiny bit blurry, but much less noticeably. I might try different -webkit-font-smoothing values, although I don't hold out a lot of hope because of the way GPU rendering works. Scaling ratios that resolve to a clean, integer pixel font size will probably work better, too.
Would backface-visibility: hidden; help? I recall having similar problems and it did help.

Smooth out rapid javascript-driven position changes with CSS transitions?

I think this is a long shot, but worth asking. I've got something similar to the left menu in many mobile apps (e.g. Facebook), that opens smoothly (in my case, using CSS transitions) when you press the 'open' button, but I've also implemented a drag-out feature where you can swipe from the left of the screen to drag the menu out. In this scenario, it's driven by javascript and is too rapid (seemingly) for the CSS transitions to know what to do. It gets confused, stutters, goes backwards etc. So I've simply shut off the transition while the user is dragging the menu out. This works very well on iOS where ti's perfectly smooth, and while Android and Blackberry both try their best, it would be great if it could be smoother.
Here is my CSS for the open/closed menu states
#view_wrap {
position: absolute;
top: 0px;
right: -100%;
left: 100%;
bottom: 0px;
-webkit-transition: all .2s 0s;
-webkit-transform: translate(-100%);
background-color: #fff;
}
#menu.open + #view_wrap {
-webkit-transform: translate(-3.125em);
box-shadow: 0px 0px .3125em rgba(0,0,0,.5);
overflow: hidden;
}
#view_wrap.animating {
-webkit-transition: none;
}
and the drag functionality just changes the translate value with each touchmove
Do you know of any tricks to get such rapid changes to be applied more smoothly with or without CSS transition?
I managed to get this to work, not entirely sure it will work in your situation but I have a slideshow which slides images horizontally both through next & back buttons and through dragging with a finger.
I was in a similar situation to you in that the sliding with the buttons was powered by 3d transitions so very smooth on any device that supported it however the dragging was done with just plane old javascript and stuttered a lot.
I have significantly more code than this but just extracted the bits to do with the transition for clarity.
On the element being dragged I began by adding a transition with a small duration on the touchstart event using jquery, 100ms seemed to work nicely for me.
// inside the touchstart event
$('#my-element').on('touchstart', function(e) {
$(this).css('transition','100ms');
});
While the touchmove event was firing, where I was previously doing a calculation to work out the left hand position and setting the left position of the element, I replaced this by applying a 3d transition and updating the X coordinate as the user moved their finger.
NOTE: translate3d apparently is 'hardware accelerated' on iOS devises where 'translate' isn't so its better to do a 3d translate with Y & Z set to 0.
// Inside the touchmove event, -10px will change as the user drags
$('#my-element').on('touchmove', function(e) {
var newLeft = /** A calculation of the new left hand position **/
$(this).css('transform','translate3d('+newLeft +'px,0px,0px);
});
Finally, on the touchend event I removed the CSS transition and re-set the left hand position of the element to the correct position.
// Inside the touchend event
$('#my-element').on('touchend', function(e) {
var endLeft = /** A calculation of the new left hand position **/
$('#sliding-element').css('transition','');
$('#sliding-element').css('transform','');
$('#sliding-element').css('left', endLeft /** Whatever the new left hand position is **/);
});
Hope this helps

Fixed element disappears in Chrome

When scrolling on a website I've built, using the CSS property position: fixed works as expected to keep a navigation bar at the very top of the page.
In Chrome, however, if you use the links in the navigation bar it sometimes disappears. Usually, the item you've clicked on is still visible, but not always. Sometimes the entire thing disappears. Moving the mouse around brings back part of the element, and scrolling with the scroll wheel or arrow keys just one click brings the element back. You can see it happening (intermittently) on https://nikeplusphp.charanj.it - you might have to click on a few of the navigation the links a few times to see it happen.
I've also tried playing with the z-index and the visibility/display type but with no luck.
I came across this question but the fix didn't work for me at all. Seems to be a webkit issue as IE and Firefox work just fine.
Is this a known issue or is there a fix to keep fixed elements visible?
Update:
Only effects elements that have top: 0;, I tried bottom: 0; and that works as expected.
Add -webkit-transform: translateZ(0) to the position: fixed element. This forces Chrome to use hardware acceleration to continuously paint the fixed element and avoid this bizarre behavior.
I created a Chrome bug for this https://bugs.chromium.org/p/chromium/issues/detail?id=288747. Please star it so this can get some attention.
This fixes it for me:
html, body {height:100%;overflow:auto}
I was having the same issue with Chrome, it seems to be a bug that occurs when there is too much going on inside the page, I was able to fix it by adding the following transform code to the fixed position element, (transform: translateZ(0);-webkit-transform: translateZ(0);) that forces the browser to use hardware acceleration to access the device’s graphical processing unit (GPU) to make pixels fly. Web applications, on the other hand, run in the context of the browser, which lets the software do most (if not all) of the rendering, resulting in less horsepower for transitions. But the Web has been catching up, and most browser vendors now provide graphical hardware acceleration by means of particular CSS rules.
Using -webkit-transform: translate3d(0,0,0); will kick the GPU into action for the CSS transitions, making them smoother (higher FPS).
Note: translate3d(0,0,0) does nothing in terms of what you see. it moves the object by 0px in x,y and z axis. It's only a technique to force the hardware acceleration.
#element {
position: fixed;
background: white;
border-bottom: 2px solid #eaeaea;
width: 100%;
left: 0;
top: 0;
z-index: 9994;
height: 80px;
/* MAGIC HAPPENS HERE */
transform: translateZ(0);
-webkit-transform: translateZ(0);
}
The options above were not working for me until I mixed two of the solutions provided.
By adding the following to the fixed element, it worked. Basically z-index was also needed for me:
-webkit-transform: translateZ(0);
z-index: 1000;
This is a webkit issue that has yet to be resolved, oddly making the jump with JavaScript, rather than using the # url value, doesn't cause the problem. To overcome the issue, I supplied a JavaScript version that takes the anchor value and finds the absolute position of the element with that ID and jump to that:
var elements = document.getElementsByTagName('a');
for(var i = 1; i < elements.length; i++) {
elements[i].onclick = function() {
var hash = this.hash.substr(1),
elementTop = document.getElementById(hash).offsetTop;
window.scrollTo(0, elementTop + 125);
window.location.hash = '';
return false;
}
}
I could refine this further and make it is that only it only looks for links beginning with a #, rather than ever a tag it finds.
If it don't work after adding
-webkit-transform: translateZ(0)
than also add
user-scalable=no
on viewport meta
source here
it worked for me
I encountered the same issue in a different case. It was because of usage of same id in multiple place. For example i used #full 2 divs.
It seems that mozilla and I.E. supports usage of same id in multiple cases. But chrome doesnot. It reacted with fixed element disappering in my case.
Just removing the id's solved the problem.
None of them worked for me except calling the modal via javascript
Click me to open MyModal
<script>
function show_modal()
{
MyModal.style.display = 'block';
}
</script>
other than this, none of the solutions above solved my problem.
This Worked for me . Add Overflow property to your top most container which can be Div or Form etc.
div, form
{
overflow:visible;
}
The same issue happened to me. For the main page of the website. I made a fixed header and Used an image for the front poster. Everything worked fine. But the moment I changed the opacity of the poster image my header with position: fixed; got disappeared. It was present there in the chrome developer tools. But was totally transparent on the website.
I tried every solution from StackOverflow, W3shools, Geeke4geeks. But if one thing was fixed another thing got messed up.
So I just opened photoshop and edited the image manually. And then posted it on my website.
It worked.
But still, it won't be effective for divs under the fixed elements.
If none of the answers above worked for you, make sure you aren't a dummy like me and have overflow: hidden; set on the fixed element :(
What if none of above worked at all? simple case of sticky header + mobile side menu pushing content.
I try to find a way to avoid fixed element (sticky header) being translated but in this case nothing is a good alternative.
So as there is no workaround on scope so far there is a JS alternative that I opted for to recalculate absolute position of fixed element. See here: https://stackoverflow.com/a/21487975/2012407
In my case, I just added min-height: 100vh; to fixed element, may be that will be useful for somebody

Resources