Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LayoutTree: replace transform with position relative #2780

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

ethan-james
Copy link
Collaborator

Fixes #2709

Replacing transform: translateX(${1.5 - indent}em) with position: relative; left: ${1.5 - indent}em fixes the drifting cursor. The animation seems pretty smooth to me, although the caret jitters slightly as it animates, at least on the iPad.

I tried to enable hardware acceleration using transform: translateZ(0) or will-change: left but I didn't notice any difference visually or in the profiler.

I still need to remove the hideCaret configuration from Panda CSS if we are going to keep this as the solution.

@ethan-james ethan-james marked this pull request as draft January 22, 2025 00:37
@raineorshine
Copy link
Contributor

raineorshine commented Jan 22, 2025

Thanks! I was worried about losing the hardware acceleration, but I did some initial testing and it does appear to work smoothly. I even deployed it to staging and tested it on my personal thoughtspace which has a lot more thoughts, and didn't notice any slowdown. Of course, I'm on an iPhone 15 Pro so I'm not sure how it behaves on less powerful devices.

I'm okay with the tiny bit of caret jitter that occurs.

What else should we do to ensure this is safe for production? Do you think we should do any performance measurements to make a more empirical comparison?

@ethan-james
Copy link
Collaborator Author

I think we should make a list of more complex test cases, including this one which I've never figured out how to replicate: https://github.com/cybersemics/em/assets/750276/931e766d-291c-495b-83b7-bebde4625dcb. We should also decide whether we want to make the change on all platforms, or only use position: relative on iOS Safari and keep transform for the other browsers. I tested it in Chrome and I also can't see any difference between transform and position: relative.

As for performance, I used this as my guide: https://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/. The takeaway seems to be that simple animations won't look different, but they could potentially compound if different animations are playing simultaneously. It seems to me that for our purposes, the indent animation will be the only thing happening.

Regardless, I didn't see the excessive paint times described in the video, which is pretty old. Maybe browsers have evolved to handle animations more smoothly. Here are some screenshots from the Safari profiler:

First is the transform method:
Screenshot 2025-01-23 at 08 33 35

Next is position: relative by itself:
Screenshot 2025-01-23 at 08 41 40

Third is position: relative with transform: translateZ(0):
Screenshot 2025-01-23 at 08 43 42

Finally, position: relative with will-change: left:
Screenshot 2025-01-23 at 08 45 41

I certainly can't see that any method performs differently from any other method. I also tried drilling into the "Layout & Rendering" tab to check on the Composite & Paint steps. The interface in the profiler is pretty buggy, but all I could see resembled the following, where Paint times are inconsequential:

Screenshot 2025-01-23 at 09 41 41

Please let me know if you are aware of any better tools for measuring performance on an iOS device. My methods are pretty ad hoc, but I also feel like the evidence that I have been able to collect doesn't point to any necessary follow-up. I know that I have an older iPhone hiding somewhere. I'll see if I can test on that later.

@raineorshine
Copy link
Contributor

Thanks so much for the thorough analysis!

I think we should make a list of more complex test cases, including this one which I've never figured out how to replicate: cybersemics/em/assets/750276/931e766d-291c-495b-83b7-bebde4625dcb.

I think if we solve this with position: relative then it will handle all cases. If we need to revert to hiding the caret, then I'd be happy to compile a list of special cases.

We should also decide whether we want to make the change on all platforms, or only use position: relative on iOS Safari and keep transform for the other browsers. I tested it in Chrome and I also can't see any difference between transform and position: relative.

Yes, good question. If the difference is insignificant, than using the same approach will be cleaner and reduce complexity. But if there is even a small difference in performance, it would be in the best interest of all desktop and Android users (which will be a lot) to use transform. I would lean towards branching on platform to be safe.

As for performance, I used this as my guide: paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft. The takeaway seems to be that simple animations won't look different, but they could potentially compound if different animations are playing simultaneously. It seems to me that for our purposes, the indent animation will be the only thing happening.

When the cursor moves, some thoughts are collapsed and other thoughts are expanded, which triggers animations, loading new thoughts, recalculating heights and layout, re-rendering the tree, buffering new thoughts from the server, deallocating cached thoughts, etc. So we have a lot going on in the browser.

I know definitively that we have no spare performance budget when it comes to rendering and animating thoughts. There is already noticeable slowdown on the latest iPhone when rendering many thoughts. So I think the best mindset here is that we need to claw back any performance gains we can.

Regardless, I didn't see the excessive paint times described in the video, which is pretty old. Maybe browsers have evolved to handle animations more smoothly.

This is plausible, as it has been many years since the transform advice came out. Though, it seems pretty core to the browser's rendering engine, and I'm not sure if that would have changed. Regardless, empirical testing as you have done will be the best way to test this hypothesis.

I certainly can't see that any method performs differently from any other method.

One thing I am seeing is 22 layout events in the transform approach, which jumps to 45 in the position: relative approach. This would fit the warnings that position: relative triggers additional layout calculations. Though I concur that I don't see any major spike in CPU time. Thoughts?

@trevinhofmann Would love to get your eyes on these charts as well! It's challenging to interpret the results correctly.

I also tried drilling into the "Layout & Rendering" tab to check on the Composite & Paint steps. The interface in the profiler is pretty buggy, but all I could see resembled the following, where Paint times are inconsequential.

The performance differences might be swallowed up by the device if it's powerful enough. Does Safari have a way to throttle the CPU? I feel like that would give us a better idea of what happens when the CPU budget is used up and additional layout calculations occur. Chrome Dev Tools has the option to throttle CPU which I have found to be very handy for performance testing.

While drawing conclusions from a different platform is tenuous, I wonder if we might want to test this in Chrome Dev Tools with 5-10x CPU throttling to get a better sense of what happens on slower devices. Paul Irish's article and other sources I have read indicate that the difference between transform" and position: relative` layout calculations are not platform specific. Chrome on a slow device is probably a more closer to our desired test case than Safari on a fast device, despite platform differences.

Please let me know if you are aware of any better tools for measuring performance on an iOS device. My methods are pretty ad hoc, but I also feel like the evidence that I have been able to collect doesn't point to any necessary follow-up. I know that I have an older iPhone hiding somewhere. I'll see if I can test on that later.

I think the profilers in Chrome and Safari are the best tools available, and both provide very detailed information if you know how to use them. I'm not expert though, so I'm in the same boat when it comes to interpreting the results. Thanks for working through this.

in order to hopefully help browsers optimize the position transition
@ethan-james
Copy link
Collaborator Author

I've spent a lot of time playing with it, and there definitely does seem to be a difference in time spent in layout, which I haven't found any way to eliminate. I decided to add will-change: left, margin because I can see it drawing a new compositing layer when I add that, and anecdotally the animation feels a little smoother at low CPU. But I can't capture any meaningful difference in the profiler.

Here it is in Chrome with 10x CPU throttling using transform:

Screenshot 2025-01-28 at 14 59 33

and here it is using position: relative:

Screenshot 2025-01-28 at 14 58 21

At 5x throttling I can't tell that there's any visual difference between the animation methods, but at 10x throttling I can start to see it. The position: relative animation will stutter a bit, and then generally it will smooth out again when I click back and forth repeatedly.

I think it's worth mentioning that CPU throttling probably doesn't affect GPU very much, so we're looking at an emulated device with 10% of a modern CPU but 100% of a modern GPU, which may have some impact on the smoothness of transform animations that I can't really measure.

If we do need to emulate the caret, the only method that I can think of to do so is to insert (and subsequently remove) a DOM element at the position where they tap. This will also have some impact on performance at the beginning and end of the animation. Unless you can think of a better way to handle it.

@raineorshine
Copy link
Contributor

raineorshine commented Jan 29, 2025

Thank you for the update!

I've spent a lot of time playing with it, and there definitely does seem to be a difference in time spent in layout, which I haven't found any way to eliminate.

Okay, good to validate that. It's unfortunate, but it makes sense given what we know about browser rendering.

But I can't capture any meaningful difference in the profiler.

Yes, I'm not getting much useful information from that either.

I think it's worth mentioning that CPU throttling probably doesn't affect GPU very much, so we're looking at an emulated device with 10% of a modern CPU but 100% of a modern GPU, which may have some impact on the smoothness of transform animations that I can't really measure.

That's a very good point. It looks like there are various methods to throttle the GPU, just not from Chrome Dev Tools. Testing on a low power device may also provide reliable anecdotal evidence.


While we're not getting the most definitive result, I do think there is a performance hit here, and if we switch to position: relative I suspect it will come back to bite us later (in production), and there won't be an easy fix. The caret appears to be tied directly to the layout, so it's unlikely that we can force it to animate with transform, which is applied to the layer outside the layout.

If we do need to emulate the caret, the only method that I can think of to do so is to insert (and subsequently remove) a DOM element at the position where they tap. This will also have some impact on performance at the beginning and end of the animation. Unless you can think of a better way to handle it.

Yes, that's what I'm thinking. I responded further in the original issue to continue non-position: relative conversation back there.

@ethan-james
Copy link
Collaborator Author

Testing on a low power device may also provide reliable anecdotal evidence.

I did find the older iPhone, but unfortunately it's a 12 Pro so the hardware is not terribly different from a new one. I heard tell that there's an even older one buried in a box somewhere, so I'll try to find that one.

@ethan-james
Copy link
Collaborator Author

The caret appears to be tied directly to the layout, so it's unlikely that we can force it to animate with transform, which is applied to the layer outside the layout.

My hunch is that WebKit is offloading the entire transform operation onto the GPU and not doing anything to track caret position in the CPU. Which doesn't explain how other browsers were able to get it right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Render faux caret during indentation animation
2 participants