30

I have a 2 column layout, the right hand column is a scrollable result lists with a max of 200 item results (basically just a ul with overflow-y: scroll; set)

What I am finding is that the right hand column is causing some jank (which is especially noticeable on low end hardware) when you scroll.

In the chrome timeline i can see some major "Update Layer tree" while I scroll the column. Is there any way I can figure out why "Update Layer tree" is so lengthy and what CSS properties are affecting speed the most? When I click on it - just gives me info on how long it ran for and nothing else.

I have some CSS in each of the li's that isn't performing very well (eg. filters, transforms, box-shadows etc) - If i remove each SASS file one by one, it improves the scrolling performance (and eventually removes jank once i remove all the CSS), but obviously its hard to pin point which css properties are not performing doing this.

I wonder if im missing something in the chrome timeline that can be helpful in this regard?

I have tried to use the will-change CSS property to promote the scroll to a different layer / force hardware acceleration - but this doesn't make much difference.

Also there is no javascript events executing while I scroll.

Limiting to less than 200 items isn't an option.

I have setup an example of this (I know its a longer list of items, but this is just for demo purposes): https://github.com/jooj123/jank/blob/master/scroll.html

What really interesting is that if i remove the overflow-y: scroll; (in .map-search__results in the example above) the scrolling becomes very smooth and jank disappears - why does this have so much effect?

Here is the chrome timeline (with basically just long "Update Layer tree" sections): enter image description here

Fix:

Paul's answer about will-change: transform is spot on, in particular this tidbit helped:

"Add will-change: transform;. Once added, the "repaints on scroll" rect is gone."

BUT just be careful as it wont always apply depending on your code base, check the scroll performance issues indicator to make sure "repaints on scroll" is off, for me I had to also disable a -webkit-clip-path property (in my actual code base - not in the demo provided) that was set in one of the child divs, see:

enter image description here

Marty
  • 2,965
  • 4
  • 30
  • 45
  • Possible lead: http://stackoverflow.com/questions/25724126/chrome-devtools-timeline-update-layer-tree-event I'd advise to post code relevant to those ~200 elements if you want advices on optimisation – kuzyn Sep 03 '16 at 06:33
  • I have seen that post, doesnt really give me any ideas on how to diagnose the issue. Im not using javascript to set style as i scroll and there is no layer invaidation i can see in the chrome timeline – Marty Sep 03 '16 at 07:41
  • 1
    putting the overflow on a wrapper div has worked better for me in the past than directly onto the ul – Carol McKay Sep 06 '16 at 09:29
  • Have you had a look at this post? http://stackoverflow.com/questions/12969228/chrome-slow-scrolling-with-fixed-position-elements – kuzyn Sep 07 '16 at 14:23
  • @carol-mckay thanks for the suggestion, but this didnt make a huge overall difference – Marty Sep 08 '16 at 08:23

2 Answers2

36

I tried out your test page and took a look.

First, with scrolling perf issues I like flipping on a few of the checkboxes in the Rendering pane:

enter image description here

Instantly we can see this div called out as "repaints on scroll". You can then verify that in the experimental Layers panel:

enter image description here

(I right-clicked in the tree and selected "Show internal layers" btw)

Now large "update layer tree" costs are usually caused by LOTS of layers or a few layers that are very large in dimensions. In this case we really have neither. But we do have a scrolling layer that's not getting composited scrolling.

Composited scrolling == fast scrolling == usually the default. But in cases like this we fall back to "main thread scrolling" which is really slow and sucky. On the plus side, crbug.com/381840 is just about to be fixed which should fix this test case automatically. Yay! But we can work around it for now.

The workaround

You did mention you tried will-change and it didn't have an effect, but trying it myself has worked out great. It belongs on the element that has overflow set, in this case its the .map-search__results selector. Add will-change: transform;. Once added, the "repaints on scroll" rect is gone. And measuring with timeline afterwards and it's MUCH better.

Here's a before/after view:

enter image description here link to viewer

Good luck!

Timo Tijhof
  • 10,032
  • 6
  • 34
  • 48
Paul Irish
  • 47,354
  • 22
  • 98
  • 132
  • just so you know, in my actual code (not the demo provided) `will-change: transform` was not working, this was due to `-webkit-clip-path: inset(100%);` being set (by a autoprefixer plugin) in one of the child divs - which seems to have some effect on `will-change`. Once i removed that as well as adding `will-change` the scroll performance issues indicator disappeared and the performance increase was massive !! – Marty Sep 08 '16 at 02:13
  • Aw, Paul. Thanks for the clinic & rawgit reference; very useful! – kuzyn Sep 08 '16 at 16:17
  • I'm having a similar situation in scrolling a chat history. `will-change: transform` removes the `repaint on scroll` though I still have a considerable jank ( 27fps ). One thing to notice is the `Painting` takes more time than `Rendering` now. Also, when I switch between chats, there's nothing painted ( but visible in DOM tree), and if I scroll and click and drag the area things appear( if `will-change: tranform` is added). Any thoughts? – Arjun S Kumar Nov 10 '16 at 10:14
  • But what should we do with `position: fixed` children? Any container with `will-change: transform` applied positions them related to its content, not the viewport. – raina77ow Mar 06 '17 at 20:40
  • Wow, this did wonders for an app I'm developing, thank you. – Matt Cosentino Jun 15 '20 at 21:16
4

If your issue is noticeably visible to you while scrolling you should be able to, with both the timeline and styles tabs open in your developer tools, uncheck styles that apply to your elements one by one in the styles tab and see how it effects the scrolling. This way you can test each style rule independently instead of entire CSS files. And if you're using sourcemaps in your CSS you should easily be able to find the offending rule in your actual CSS files and make adjustments.

If you're unfamiliar with using the styles tab: with Chrome developer tools open, use the inspector tool (top left corner of the developer pane) to select your items you suspect might be giving you issues. All styles associated or overridden for the selected element will be listed in the styles tab. You can then turn them off one by one. You can use the inspector to drill down as deep as you wish through nested items as well.

UPDATE:

I pulled down the code after your edit and looked at it pretty closely. I noticed that the overflow you have was on your ul item. Personally I've never used overflow on a ul (typically preferring a container like div for that), so out of curiosity I removed overflow-y:scroll from your ul and put it on the ul's containing div .right-content (and set it's height to 100%), and the janky scrolling is gone.

As for the why, I can only speculate, but I believe it has to do with the number of items you are scrolling. When your overflow is on the UL, you are directly scrolling ALL of those child li items. When it's on the containing div, you are actually scrolling ONLY the ul, so I would believe that this process has much less math to determine when calculating positioning of the moving items. 1 div positioning to adjust vs. hundreds of li's to adjust.

paint comparison

If you compare the timelines in the images above you'll see the difference. The one on top is the original version with overlflow-y:scroll on the ul. You can hover over all those little lines in the update tree process and see that there are hundreds of items being painted. These are your li items. In the second timeline is the version where I've removed the overflow from the ul and applied it to the containing div. You can see in this timeline the update tree process has only one item to paint which is the ul. If you look at the dimensions of the items you'll see the difference in size as well.

Robert Wade
  • 4,918
  • 1
  • 16
  • 35
  • I have updated my question. Ill give you an upvote but I dont think it an overly quick way of solving the issue (and im aware of the inspector) - also it doesnt answer why `overflow-y: scroll` is so slow - if i remove that all jank disappears – Marty Sep 07 '16 at 11:49
  • 1
    Did a little further research with the timeline and did find out that if you look at the update tree process you can see all the items that are being painted. You can hover over them and see the difference. I added an image and description to the answer outlining this. – Robert Wade Sep 07 '16 at 19:11
  • This made a few ms difference in frames, but the main issue is the "Update Layer tree" – Marty Sep 08 '16 at 03:16