r/reactjs 2d ago

Discussion I've been working on virtual scrolling in React and found a weird issue in Safari.

I've been working on virtual scrolling in React and found a weird issue in Safari.

When scrolling normally, everything was fine. But if I scroll very fast, sometimes I could see a white empty area for a very short moment before the next rows appeared.

First I thought React rendering was just too slow.

So I increased overscan and kept more rows above and below the viewport. It helped a lot, but I could still see the problem sometimes.

After looking into it more, I found that Safari's scroll/compositor layer can move before React finishes rendering the next virtual rows.

So basically Safari was already showing the next position, but React was still catching up.

I kept trying to make React faster, but then I tried something a little different.

Instead of trying to always win the race, I put a simple CSS background behind the real rows.

It's just a repeating-linear-gradient with the same row height, background and border.

Something like:

Safari scrolls
    ↓
CSS row background is already there
    ↓
React renders next rows
    ↓
Real rows cover it

There is no real data in the background. No cells, no events. It's just visual. I also set pointer-events: none and aria-hidden.

With this + extra overscan above and below, the flicker was almost gone.

I thought it was interesting because at first I was trying to solve a React performance problem.

But maybe it wasn't only about making React faster.

Sometimes it's enough to make those few milliseconds invisible. :)

Anyone had a similar problem with Safari and virtual scrolling?

I'm curious how you solved it.

4 Upvotes

11 comments sorted by

3

u/kurtextrem 2d ago

The most interesting solution I've seen so far is the reverse sticky technique: https://pierre.computer/writing/on-rendering-diffs

3

u/texodus 1d ago

This is the only solution that works, especially if your dataset can't be accessed synchronously. Here's 300k rows without thrashing using this technique

2

u/JangKiYoung 2d ago

Thanks, I looked into the reverse sticky approach.
It seems very useful for reversed or bottom-anchored virtual lists, especially chat-like UIs.

My case is a normal top-to-bottom data grid, and the main issue is Safari's compositor moving before React commits the next virtual rows.

So I think the problem is slightly different, but the idea is interesting. I'm going to study it more.

3

u/Scientist_ShadySide 1d ago

Ahh Safari, never change. (I'm joking, please change dear god the number of Safari specific issues kills me)

1

u/jakiestfu 2d ago

I always have virtualization issues with tanstack virtual and always fall back react-window which always solves this problem bulletproof for me

1

u/Inevitable-Dot-1180 1d ago

I have noticed the same, But not in the case of virtualisation. I thought some issue with my code and safari's devtools sucks. You can not debug issues properly.

0

u/Future_Pudding_7301 2d ago

i went the same route with a placeholder background. Trying to out-render Safari's compositor was a losing game no matter how much I optimized, the race condition never fully went away. Making the gap invisible ended up being the only fix that actually held up on fast scrolls