Jump to content
Search Community

Performance issues: Page transition

awagner test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Hi everyone! 

 

Would love to get your opinion on what might be causing performance bottlenecks in a page transition animation I'm working on.

Live WIP site is here: http://adamwagner.io/

  1. Click on the first image below the Lorem Ipsum "about me" section. The image is of a band playing.
  2. Notice the low frame rate as the image animates to the hero position, a circular splash element hides the page, and then the route changes to the project detail page.
  3. Click the "back" text at bottom left below the hero image. This will take you back to the home page with a reverse animation. 

 

NOTE: only works in Chrome because I'm using webm for the image I refer to above. Will fix for prod of course.

 

Performance increases after multiple runs, but typically first run performance is very janky. I would love some tips - even if they involve a totally different approach. Thank you so much in advance!

 

I've attempted to use best practices. For instance, I clone the image before animating, making it fixed position to avoid reflow. GSAP delightfully takes care of optimizing position tweens, but I also need to animate height and width. I also need to set autoRound: false, because I need perfect overlap with the real hero image once the transitory clone is removed.

 

Here's the code:

 

import React from 'react'
import { injectGlobal, styled } from "styled-components";
import {TweenMax, Bezier, TimelineMax, Power3, Sine} from "gsap";
import store from 'store'
// import ripple from  '../utils/splash'


let globalSpeed = 1;

export default class ProjectImage extends React.Component {

  getDuration(start, end) {
    let pixelsPerSecond = 700;
    let duration = Math.abs((end - start) / pixelsPerSecond);
    // set min value of .2
    duration = duration < .25 ? .25 : duration
    // set max value of .5
    duration = duration > .5 ? .5 : duration
    return duration * globalSpeed;
  }


  onImageClick = (e) => {
    // get the first element - the <body>
    let body = document.body

    // get window width minus scrollbar
    let w = body.clientWidth
    let h = body.clientHeight

    // get clicked project element and position data
    let el_original = this.refs.project_image
    let {top, left, width, height} = el_original.getBoundingClientRect();

    // clone it
    let el = el_original.cloneNode()

    // place element on top of original
    // find a cleaner way to copy these over
    let elStyles = `
      cursor: pointer;
      width: 100%;
      height: 40vw;
      will-change: width, transform;
      background-image: url(${this.props.image});
      background-position: center center;
      background-size: cover;
      pointer-events: none;
    `
    el.style.cssText = elStyles + `z-index:200; position: fixed; top:${top}px; left:${left}px; width:${width}px; height:${height}px;`
    body.append(el)

    // get mouse click coordinates
    let {clientY, clientX} = e;

    // save original image coords for "going back" transition
    store.set('lastClickedProject', {top, left, width, height})

    // create and append splash circle
    let splash = document.createElement('div')
    splash.style = `
      border-radius:100%;
      width: 50px;
      height: 50px;
      background: ${this.props.pageColor};
      position: fixed;
      top:${clientY}px;
      left:${clientX}px;
      z-index:100;
      opacity: 0.75;
      will-change: transform;
    `
    body.append(splash)

    // animate splash
    TweenMax.to(splash, (0.6 * globalSpeed),  {scale:80, opacity:1, transformOrigin: "50% 15%"}, Sine.easeIn);

    // animate image to the hero position
    TweenMax.to(el, this.getDuration(0, top), {bezier:
      {curviness:0.25, values:[
        {x:0, y:0, width:width, height: height},
        {x:-left, y:-(top/2.5), width: w, height: height+25},
        {x:-left, y:-top, width: w, height: height+100},
    ]}, ease:Power3.easeOut, autoRound:false});


    // redirect to detail page
    setTimeout(() => {
      el.remove()
      splash.remove()
      window.___navigateTo(this.props.path)
    }, (this.getDuration(0,top)*1000))
  }


  render() {
    let style = {
      cursor:'pointer',
      width:'100%',
      height:'40vw',
      willChange:'width, transform',
      backgroundImage:`url(${this.props.image})`,
      backgroundPosition:'center',
      backgroundSize:'cover',
    }

    return (
      <div onClick={this.onImageClick} style={style} ref="project_image">
      </div>
    )
  }
}

 

Link to comment
Share on other sites

Hi @awagner

 

I'm not sure what, but something is different on the first run, causing that flicker.

 

It's really hard to tell what might be a performance problem as the animation runs too fast. Making a simplified demo of what you have might help so other people can play around with the code. CodePen is good, but even something designed for react, like codesandbox.io will work.

 

Sometimes precomputing an animation can reduce some initial jank. No guarantees though.

myAnimation.progress(1).progress(0);

 

I also noticed some incorrect syntax here.

TweenMax.to(splash, (0.6 * globalSpeed),  {scale:80, opacity:1, transformOrigin: "50% 15%"}, Sine.easeIn);

// Should be
TweenMax.to(splash, (0.6 * globalSpeed),  {scale:80, opacity:1, transformOrigin: "50% 15%", ease: Sine.easeIn});

 

Here's a post that might give you some ideas on doing a full-screen transition. For what you're doing, using another element or two might help with animating the transition.

 

For some really advanced Bezier movement for transitions, this thread has a couple of examples.

 

 

  • Like 4
Link to comment
Share on other sites

Update: Just fixed the really bad flicker during the route transition. Turns out I wasn't pre-caching the assets for that route. So that's gone. 

 

But the animation itself (especially from the detail page back to the index page) is still choppy until run a few times.  Will continue exploring @OSUblake's suggestions. 

Link to comment
Share on other sites

3 hours ago, awagner said:

Thanks so much, Blake! Based on your username, I assume you're from Ohio :) So am I. Didn't attend OSU though (boo). 

 

Hehe. Sometimes I get people asking if that's Oklahoma or Oregon, but it's Ohio. Or as it's officially known, The Ohio State University, like there are other ones. :roll:

 

2 hours ago, awagner said:

But the animation itself (especially from the detail page back to the index page) is still choppy until run a few times. 

 

What browsers is it choppy in? 

 

Maybe we can summon our resident React expert @Rodrigo to take a look.

 

I did notice that it doesn't seem to work correctly in Edge or Firefox for me on Windows. In Firefox, I noticed that depending on the scroll position, the ripple might not fill the entire screen, causing a snapping behavior. I wonder if that might be contributing to the choppiness you're seeing.

 

If so, here's a demo that shows the math to calculate a full-screen ripple from a mouse click. It uses canvas, but the calculations should work the same for a div.

 

 

  • Like 3
Link to comment
Share on other sites

@OSUblake - thank you so much for the canvas example. Funny thing - I was just experimenting with using Canvas for these animations yesterday morning. I had trouble integrating it with GSAP, and abandoned that route. So, thank you very much! 

 

I updated the live site (remember, *chrome only* for now) with the canvas version of the ripple, and am happy to report a performance improvement! Now, I'm wondering if I should do the same thing with the image, since its movement is still a bit uneven. The challenge I see there is getting a canvas image element to act like a div with "background-size: cover". However, I've seen a couple of examples of it, and will give it a shot. 

 

@OSUblake - do you have a "buy me coffee" link, or anything to which I could send a thank you? Your help was pivotal in moving this forward for me.

  • Like 3
Link to comment
Share on other sites

8 hours ago, awagner said:

 

@OSUblake - do you have a "buy me coffee" link, or anything to which I could send a thank you? Your help was pivotal in moving this forward for me.

 

Hehe. I get asked that a lot, and then somehow I always forget to set it up. I should probably add that to my profile on here.

 

8 hours ago, awagner said:

@OSUblake - thank you so much for the canvas example. Funny thing - I was just experimenting with using Canvas for these animations yesterday morning. I had trouble integrating it with GSAP, and abandoned that route. So, thank you very much! 

 

Yeah, it seems that a lot of people have hard time understanding how to get GSAP and canvas to play together. Here's a good thread with a bunch of canvas demos.

 

 

 

8 hours ago, awagner said:

The challenge I see there is getting a canvas image element to act like a div with "background-size: cover"

 

It's just an aspect ratio. Here's a demo showing how it's calculated. If you set the aspectType variable to "meet", that is like contain, "slice" is like cover, and "none" will stretch it.

 

 

 

And how that might look using canvas.

 

See the Pen MEwmKe?editors=0010 by osublake (@osublake) on CodePen

 

 

 

 

 

  • Like 3
Link to comment
Share on other sites

@OSUblake - wow. Don't know what to say, other than you'll be getting a hefty comment block in the canvas implementation of this transition. THANK YOU. Let me know if there are any links you'd like to plug, other than your GH.

 

Speaking of that ... here it is: http://adamwagner.io/

Should work in Chrome, FF, and Safari on OSX now. Haven't checked Windows yet. Please let me know if you experience jank, stutter, flashing. 

 

Even with the canvas implementation, I had some issues with the previous route flashing when the animation ended. I ended up reducing the impact of this by registering a callback on the Timeline that changes the route 95% of the way through the animation. It's a pretty good compromise, but does add some stutter to the animation depending on CPU throttling. When throttling is set to 20x slower in Chrome perf tools, the animation actually proceeds non-linearly (progressing, regressing, then progressing again). I experimented with using "useFrames", but it was not an improvement. I did update to React 16, which uses Fiber, and should theoretically be more performant alongside animations. Didn't see an improvement though.

 

At this point, I think the performance issues are caused by changing the route at the end, or near the end of the animation. It's a SPA running on react, so it's not a full page load, but it does spike the JS load. 

 

Thanks a million for all of your help.

 

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

Looks good! And I don't have anything to plug, but thanks for asking.

 

I wish I knew more about React to help out. I'm more familiar with Angular and Vue. Have you tried using React TransitionGroupPlus? Here's a comparative demo.

 

I don't know if it's recommended to use with a router at the moment, but I did notice these issues.

https://github.com/cheapsteak/react-transition-group-plus/issues/18

https://github.com/cheapsteak/react-transition-group-plus/issues/22

 

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.
×
×
  • Create New...