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

Fix rendering performance bottleneck in carousel #2281

Open
wants to merge 1 commit into
base: next
Choose a base branch
from

Conversation

patrick-mcdougle
Copy link

@patrick-mcdougle patrick-mcdougle commented Nov 22, 2024

Fixes #2280

Before

Screenshot 2024-11-22 at 4 00 41 PM

After

Screenshot 2024-11-22 at 4 03 14 PM

Notice how none of the carousels on the right side have any synchronous Layout anymore, but the browser does one big layout once in the requestAnimationFrame callback.

NOTE: The scales on these screenshots are not the same...but the key point to notice is there there aren't as many tiny red dog-ears on each of the individual carousels which is the concern I was trying to fix.

Copy link

vercel bot commented Nov 22, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
shoelace ✅ Ready (Inspect) Visit Preview Nov 22, 2024 10:36pm

@patrick-mcdougle
Copy link
Author

I did some light testing in chrome by using the override feature and it seemed to work alright, but I must warn, this will convert something that used to be synchronous to be async, so there might be some assumptions hiding somewhere which could be the source of bugs.

Someone (and I could be willing to do this), should probably audit all of the callers of goToSlide to see if any of the code after it's called could possibly expect that the actual scrolling has been completed. Biggest likely offender is that state property this.pendingSlideChange which would now be guaranteed to still be true in this frame, but not in this task/tick of the event loop.

@claviska
Copy link
Member

@alenaksu any concerns before we merge this?

Copy link
Collaborator

@alenaksu alenaksu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've checked the changes and I don't believe it will cause any issue. The pendingSlideChange property is used to prevent slide synchronization when a slide change is triggered by user interaction, such as clicking a navigation dot, so it is not an issue in this case.

However, there could be a side effect causing multiple synchronizations during the initial render. I left a comment wwith a suggestion to overcome this, please take a look and let me know what you think.

Comment on lines +509 to +524
const doWork = () => {
const scrollContainer = this.scrollContainer;
const scrollContainerRect = scrollContainer.getBoundingClientRect();
const nextSlideRect = slide.getBoundingClientRect();

const nextLeft = nextSlideRect.left - scrollContainerRect.left;
const nextTop = nextSlideRect.top - scrollContainerRect.top;

if (nextLeft || nextTop) {
this.pendingSlideChange = true;
scrollContainer.scrollTo({
left: nextLeft + scrollContainer.scrollLeft,
top: nextTop + scrollContainer.scrollTop,
behavior
});
}
Copy link
Collaborator

@alenaksu alenaksu Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep the original behaviour of the pendingSlideChange, we could set the value before the raf callback and then update it within if needed:

Suggested change
const doWork = () => {
const scrollContainer = this.scrollContainer;
const scrollContainerRect = scrollContainer.getBoundingClientRect();
const nextSlideRect = slide.getBoundingClientRect();
const nextLeft = nextSlideRect.left - scrollContainerRect.left;
const nextTop = nextSlideRect.top - scrollContainerRect.top;
if (nextLeft || nextTop) {
this.pendingSlideChange = true;
scrollContainer.scrollTo({
left: nextLeft + scrollContainer.scrollLeft,
top: nextTop + scrollContainer.scrollTop,
behavior
});
}
this.pendingSlideChange = true;
const doWork = () => {
const scrollContainer = this.scrollContainer;
const scrollContainerRect = scrollContainer.getBoundingClientRect();
const nextSlideRect = slide.getBoundingClientRect();
const nextLeft = nextSlideRect.left - scrollContainerRect.left;
const nextTop = nextSlideRect.top - scrollContainerRect.top;
if (nextLeft || nextTop) {
scrollContainer.scrollTo({
left: nextLeft + scrollContainer.scrollLeft,
top: nextTop + scrollContainer.scrollTop,
behavior
});
} else {
this.pendingSlideChange = false;
}

Comment on lines +526 to 530
if ('requestAnimationFrame' in window) {
window.requestAnimationFrame(doWork);
} else {
doWork();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can avoid this check and call rAF directly since it's supported by all browsers

    requestAnimationFrame(() => {
      ...
   });

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, wasn't sure what minimum browser support was, so played it cautious. I can clean this up.

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.

sl-carousel goToSlide has synchronous layout
3 participants