Jump to content
GreenSock

catepillar

Maximizing performance when rapidly panning

Go to solution Solved by GreenSock,

Recommended Posts

Hey GSAP forum - hope folks had a good 3 day weekend. I'm reviving a side project, and wanted to use GSAP to help achieve silky smooth 60FPS panning via the trackpad. 

 

I've gotten fairly close! Here's the code - https://codesandbox.io/s/affectionate-dawn-zq3u9?file=/src/App.js - and here's the full-screen output - https://zq3u9.csb.app/ (Trackpad likely required)

 

I've been tinkering with the above code for the past two days, and have observed the following:

- React is a bottleneck, things go much faster if no state changes or rerenders occur

- Plugging in an external monitor turns GPU acceleration on for my Macbook and makes things flawless (I thought I was going crazy trying to figure this one out)

- At least on my 2019 Macbook Pro, Safari is significantly smoother than Chrome

- Smaller or reduced screen sizes are significantly smoother than larger screen sizes

- "will-change: transform" seemed to bump Chrome performance significantly

- Testing in Chrome's incognito mode seemed to bump performance a little (some React plugin is probably slowing things down)

- Chrome's DevTools indicate that every pan performs within a frame (Here's the rough numbers... Javascript takes .50ms, Recalculate Style takes 5ms, Update Layer Tree takes .60s, and Composite Layers takes .10ms)

- It's unclear to me if Chome's DevTools can accurately report when the speed drops below 60FPS... I'm doing a lot of look and feel testing with these experiments

 

In all honesty, the performance already is quite good! But my inner Steve Jobs can't help but notice a little jaggedness on Chrome or less-powerful laptops... is there anything else I can do to squeeze out the last bit of perf here?  Thx for looking!

Screen Shot 2021-02-16 at 3.12.06 PM.png

Link to comment
Share on other sites

Hm, I'm struggling to understand why you'd be doing a zero-duration tween there. Since it's such a high-touch, performance-sensitive part of the code and it doesn't look like you're doing anything else transform-related (which GSAP could help with), I'd probably set the transform directly and skip the tween altogether: 

let x = 0;
let y = 0;
const panCanvasWithTrackpad = (e) => {
  e.preventDefault();
  x -= e.deltaX;
  y -= e.deltaY;
  elem.current.style.transform = `translate(${x}px, ${y}px)`;
};

Side note: you might want to switch to listening to the "wheel" event instead of "mousewheel" because I recently learned that Firefox doesn't like the legacy "mousewheel" thing. 

 

Does that help at all? 

  • Like 2
Link to comment
Share on other sites

Hey Jack! Good catch on the event handler - I've also tried just using CSS transforms (as well as the translate3d version of your code), but GSAP seems to perform slightly better on Chrome! Is this my imagination? Does GSAP tweens do any optimizations with requestAnimationFrame? 

 

RE zero duration - The zero duration version seemed to mimic the "snappiness" that other canvas-based applications like Figma had. I'll try increasing it to .1s and see if it feels better or worse.

Link to comment
Share on other sites

  • Solution
10 hours ago, catepillar said:

I've also tried just using CSS transforms (as well as the translate3d version of your code), but GSAP seems to perform slightly better on Chrome! Is this my imagination? Does GSAP tweens do any optimizations with requestAnimationFrame? 

Hm, that kinda surprises me to be honest. I mean it's great if the GSAP version is faster, but I just don't see a reason why it would be. Are you saying you actually measured it and it's faster, or that it just "seems" faster to your eye? If it just "seems" faster, I suspect it's an illusion :)

 

If you're doing a zero-duration thing, you could also see if this performs any better for you: 

let x = 0,
	y = 0,
	xSetter, ySetter;
const panCanvasWithTrackpad = (e) => {
	e.preventDefault();
	if (!xSetter) { // can't create these initially because elem.current doesn't exist yet.
		xSetter = gsap.quickSetter(elem.current, "x", "px");
		ySetter = gsap.quickSetter(elem.current, "y", "px");
	}
	x -= e.deltaX;
	y -= e.deltaY;
	xSetter(x);
	ySetter(y);
};

Reusing a gsap.quickSetter() can eliminate some overhead. Frankly, I kinda doubt you'll notice any real-world difference, but it's worth a shot. 

 

Good luck!

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×