I've an application with a scrollable element that contains a list of items.
I'd like to optimize the rendering for jank free scrolling, with some tricks that are described in http://aerotwist.com/blog/on-translate3d-and-layer-creation-hacks/ for exemple
Just would like to know: where should I force the browser to create layers? Am I supposed to create a rendering layer around each of my list items?
I also would like to know why the browser isn't able to do this on his own, because when an element is scrollable, it makes sens to me that we will move the content of this element up and down without changing the rendering of the inner content right? So why doesn't the browser creates a layer for the inner content of any scrollable element?
By the way, is this layer creation hack consistent across browsers?
Edit:
I've noted that it is now possible to indicate to the browser that some changes will happen.
I could use for exemple: will-change: scroll-position; according to this article
However, I still don't understand why the browser needs this, because if we set overflow-y: auto;
or overflow-y: scroll;, it seems obvious that the scroll position is expected to change.
If you are scrolling list of elements that are 'static' (they don't change their relative position, size, opacity etc. while you scroll) there isn't much that you can do to improve the scrolling performance. That's because your scrollable content is already promoted to a separate layer. You can easily test it yourself:
open this demo (it doesn't use translate3d nor will-change),
enable 'Show composited layer borders' in the DevTools
and observe the result.
Orange border around the scrollable content indicates that it is on a separate layer.
If you are experiencing janky scrolling then try to narrow your issue down (e.g. using Timeline in the DevTools). translateZ(0) is not a silver bullet for all performance problems.
Related
I am new to Web Development and React (and fairly amateur at CSS), so there must be some fundamental issue with my understanding of how flex works.
With reference to this codesandbox, the website that I am designing (for upskilling myself) seems to have (for the lack of a better word) some kind of 'shunt' in the Home Page. To elaborate, keep a close watch on the navbar on top. As soon as you click on 'About', the whole page seems to move a little to the right. Only the home page is being affected in this manner.
I have tried figuring out the issue on my own using the Browser's handy 'inspect tools'. Came to the following conclusions.
It seems that the Home page has the width of 1519px whereas all other pages have the size 1536px. I am unable to understand why there is a difference of 15px at all.
Trying to isolate the issue (by removing all elements and adding them back one-by-one to see which one is erroneous), I realised this 'shunt' is being caused whenever I place a flex element inside another flex element. Thanks to other stackoverflow answers, I found that the dimensions of a flex element depend upon the parent (which in turn is another flex element with ambiguous sizing properties).
Issue
Even when I resolve the above mentioned problem, there still remains the "latest-post-card" element which impacts the Home Page orientation. Further, when I remove the height property for the "latest-post-card" everything falls right into place, perfectly, just the way I want it to appear, save for the fact that the height of this card is variable. For the sake of consistency, I would like this card's height to be fixed (as I will keep updating the contents of this card dynamically through the backend).
Requesting fellow developers to shed light on the issue here; would appreciate if anyone can point out what the root of the problem is and how I can remedy it.
What you're referring to as a "shunt" is caused by the other pages taking up less height and therefore not needing the scrollbar. When the scrollbar disappears, the page basically expands by the width of the scrollbar and therefore the content jumps slightly to the right.
On many modern browsers (for example both Safari and Chrome on MacOS and iOS), it's possible to scroll past the actual html page to explore what lies beyond it (for example, on this very page, there's a white field below the dark footer on the bottom). I've been trying to figure out the general principle behind how to modify these areas.
Specifying a background color of the body element in CSS makes it possible to choose a unified look for everything that's outside the actual page, but this doesn't work if one wants different things to show up at different places (for example, on this very page, it would be natural for the footer color to extend to the left and to the right only below its own border, but not above where the obvious choice would be white).
So, how should one approach modifying the content outside of the actual page (that is, content that's not really existing/being shown on certain browsers)?
for example, on this very page, there's a white field below the dark footer on the bottom
Are you talking about what e.g. Safari on iOS does when you scroll beyond the page? Because most browsers don't do what you describe.
What Safari on iOS does is indeed using some heuristics like background-color of the <body> and <html> tags (and possibly other elements). This behavior is not standardized, since traditionally browsers would never scroll the page further than to the bottom.
It cannot really be customized that much, but there's an approach.
In almost every scenario imaginable, you won't be able to view anything below the end of the <body> element (except for the example #mb21 pointed out). So in theory, any background style applied to the body will be visible to the very bottom of the viewport. Generally speaking, a background colour won't extend outside of its container.
So I am making this horizontal scroll site which has a ton of images. I planned on using svgs for the entire site, but with only 20-30 svg images of medium to high complexity used in the page, and chrome already seems to be showing som jank and high paint times for scroll (and firefox is even worse, though safari seems to do a lot better).
Scroll timeline
View the site (scroll works on mac only, windows users can use arrow keys)
My question is, if I were to use pngs instead of svgs, would it reduce the paint times and hence the jank? Why is the browser struggling with only around 20 odd svg images?
As was my doubt, the problem turned out to be something completely different. Browsers are more than capable of handling multiple vector images. But what they aren't good at (and understandably so) is at redrawing those images very often.
Problem
My long horizontal scroll site was quite wide (30,000px). I had a background-color property applied to one of lower z-index'ed div's to represent the sky throughout the scroll site. I didn't want the sky to stretch the entire 30,000px since it essentially didn't change much, and hence gave it viewport width and height, with:
position:fixed;
Not a very smart move. Turns out that this property was causing my document layer to be repainted on every frame. Initially I though it was normal for browsers to do so on scroll, since Robby Leonardi's site, which I used as reference also repainted every frame.
Solution
Thanks to this article by one of the chrome dev tools developers, I set aside conventional wisdom, and make the sky layer
position:absolute;
and stretched it the entire site width, and boom! The paint rectangles were gone. And the scroll performance was smoother than butter.
Other solutions I tried
Hiding elements not near the viewport to make painting lighter, as suggested by #philipp, but didn't yeild any appreciable difference. Also, it felt super-hacky, and it wasn't targeting the root cause of the problem.
I tried modularizing my site into scenes and using translateZ(0) hack on each scene so that only the smaller scenes get repainted instead of the document. This actually helped quite a bit, and scroll was decent. Then,
I gave all the svg images their own layer by using translateZ(0). I started getting FPS of around 60 now, but again, this wasn't the right way of doing things.
I once had a similar thing. The SVG was 10 or more times as wide as the one shown above, it contained ~20k elements and was about 3MB in size. The only Thing what brought back performance (since it was a jump and run game) was an algorithm which was able to find all elements whose bounding box overlapped the viewport. With this I could use display: none; to hide everything what is invisible.
That reduced the amout of visible elements to ~150 per frame and the game ran again fluently.
I used a balanced binary tree (avl tree) and a one dimensional range query, since the height of the viewport was always the same as the image.
Good luck!
[EDIT]
Forgot to leave something like an anwer. From my Experience are large/huge SVG Graphics a bottleneck in rendering, especially if there is a lot of scripting happening. If you do not need any Interactivity with the elements from the Graphic, so nothing than a large Background Image, I would recommend to use a Tile map, based on PNG images, that is the standard way in Jump'n'Run games with huges »worlds«, so you can gain perfomance in two Points:
Rendering is faster,
You can »lazy ajax load« tiles, depending on visibility, to prevent users to download the »whole world« at startup.
Additinally you could use something like PIXI.js to render with WebGL, what will push the performance drastically and with it comes support for Tilemaps and Spritesheets.
If you insist on the advantages of Vector Grafics (Scaling, Interactivity) than you need to find a way to hide as much elements as possible to keep the frame rate high.
I am experiencing slowness with an animation in my website.
After some investigation I found out (via the DevTools timeline tab) that the problem is that the entire page is being re-painted instead of just the animated div.
I checked the "Show composited layer borders" option, and found out that sometimes the animated div is in another render layer.
But I can't find a consistent behavior:
When the div is not in another layer - the animation is slow.
When the div is in another layer, sometimes the animation is fast and sometimes it is slow, depending on the presence of other elements in the page (a div with position:fixed, a marquee, etc). These other elements appear to be totally unrelated to the animated div in the DOM tree but obviously have an effect on the rendering of the page during the animation.
I found a few articles (1, 2, 3, 4, 5) that suggest possible ways to "force" Chrome to render an element in another render layer but most of them are old (things might have changed).
Also, they generally don't address how elements can affect each other with regard to the render layers.
How does Chrome decide which element to put in which layer?
How can I find out what was decided in my case? (i.e. debug the render layers)
How can different elements affect each other with regard to the render layer?
How can an animation of an element that is in another layer, cause a re-paint of the whole page? (at some cases this happens)
How can I ensure a fast render of my animation? i.e - force the element into another layer and make sure the animation doesn't cause a re-paint of the entire page.
And lastly - how can I stay on top of changes to the browser's rendering algorithm so that these problems don't return in the future?
OK so I finally found a solution to my problem.
This SO answer explains how to enable the "Layers panel" in chrome DevTools. That panel allows you to see in real time (even during animations) which elements are in which layers on the page.
Also, each layer has properties that tell you why chrome decided to make a layer out of it.
Using this tool I was able to determine that one of my elements which is an overlay of the whole page (to mask the page when there's a modal div) sometimes gets its own layer and sometimes not.
The reason it got a layer only when some other elements like marquee were present on the page was that chrome detected that "the element may overlap other composited elements".
When these "other composited elements" are not there, this overlay element does not get it's own layer. And when I open the modal there's also an animation on the opacity of the overlay div. And since it was not in a separate layer - it caused the entire page to re-paint itself in each frame (this is sometimes referred to as a "paint storm").
I solved the problem by making sure the overlay div will always get its own layer, by adding -webkit-backface-visibility: hidden to the style of this div.
My content wrapper will be centered in the body. the problem i have is that i have a certain div that will be partially reaching into the body and out of the wrapper using z-index. this works fine when using a certain solution. if an user is maximizing its desktop solution the frame of the content wrapper wont be visible anymore. even then the certain div is no more visible. so is there a way to limit the max. zoom?
here is an example: link
when you will zoom in you can see that the red div is from a certain point no more visible. this i would like to avoid if possible. thanks a lot.
Various browsers allow you to define viewport metatags that define the allowable zoom limits (for example Web Kit-based browsers). If you know what your target browsers are, you may want to investigate this.