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

Merged

Conversation

patrick-mcdougle
Copy link
Contributor

@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 Dec 3, 2024 5:43pm

@patrick-mcdougle
Copy link
Contributor 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?

@claviska claviska requested a review from alenaksu December 2, 2024 18:29
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;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This would make pendingSlideChange=true more often than it is today.

Today it's only true if either the left or top is non zero, but here we would always be marking it has pending until the next frame.

I'm ok with that, but wanted to point out that it's also not a clean refactor / preservation of existing behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See 68be53c

src/components/carousel/carousel.component.ts Outdated Show resolved Hide resolved
@patrick-mcdougle
Copy link
Contributor Author

this might also fix #2296 since the render making scrollContainer exist will happen before the rAF callback does

@claviska claviska merged commit 97fcab8 into shoelace-style:next Dec 4, 2024
1 check passed
@patrick-mcdougle patrick-mcdougle deleted the carousel-rendering-performance branch December 4, 2024 20:16
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