Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
marius96

AirPods image sequence animation using ScrollTrigger

Recommended Posts

I'm trying to recreate Apple's Airpods Pro presentation page with ScrollTrigger.

This is what I'm trying to make: 

See the Pen ZEbGzyv by j-v-w (@j-v-w) on CodePen


My idea is to use an array which holds all the images and then make use of ScrollTrigger.update() to update the img src  based on the scrolling position.

See the Pen poybrBd by make96 (@make96) on CodePen

Share this post


Link to post
Share on other sites

Hey marius. Switching out the srcs is guaranteed to be slow because it will have to load the images every time you switch it. It's better to use a sprite or display/hide images. These threads talk more about this sort of thing:

 

  • Like 2

Share this post


Link to post
Share on other sites

 

Thank you for responding, @ZachSaucier.

Sorry for  the late response, but after a few tries I think I started to get the hang of this.

See the Pen yLOaVpE by make96 (@make96) on CodePen

As @sbest58 said in this topic, this is a process that takes a lot of trial and error.

For desktop, the sweet spot for me is 15 rows and 10 columns for a total of 147 images.

Testing this on my iPhones, it crashes, so I think I need to create a new grid.

Switching from png to webp did wonders, the hero-section image file went from 21mb to around 4mb. The Codepen link provided by me, has a .png file since imgur doesn't support .webp

I have a few questions:

  • If you check Apple's page, they are doing some sort of scaling with the images when you start scrolling: is this possible with sprites?
  • I want to split this into a few sections because if I create only one image grid with all the images, that image will take forever to load. I tried copying and pasting code for the second section, but that doesn't work. Can explain to me how can I create a new section? I tried of copying and pasting that code for every new section, with the trigger modified accordingly, but that doesn't work.
  • And the last question, how can I do what Apple does animate their text: translate - fade in - translate - fade out the text in a pinned section? I'm referring to the "Active Noise Cancellation for immersive sound.".
  • Like 1

Share this post


Link to post
Share on other sites
3 hours ago, marius96 said:

If you check Apple's page, they are doing some sort of scaling with the images when you start scrolling: is this possible with sprites?

You can have different size sprites or you could scale the image and still change the position of the sprite so yes.

 

3 hours ago, marius96 said:

I want to split this into a few sections because if I create only one image grid with all the images, that image will take forever to load. I tried copying and pasting code for the second section, but that doesn't work.

I'm guessing by "one image grid" you mean one sprite?

 

In terms of doing multiple we'd have to see what you're doing wrong. You'll need to have a different target, ScrollTriggers, variables, etc. for each section.

Share this post


Link to post
Share on other sites

For what it's worth, I did something similar to the Apple site for a client. I tried two approaches, both using individual img elements (not a sprite) positioned fixed behind the normal content.

 

The first I tried to create animations and ScrollTriggers for each section of the page and then sync those to the correct timing with the background images. While this worked (and was easier to get pinning working), it proved to be quite difficult to keep things synced on different viewports. I had a lot of conditional values that depended on breakpoints. And when I updated the height of one section, the timings of the other sections would get thrown off. Not optimal.

 

The second approach, and the one we went with in the end, is making use of one big timeline for both the background images and and animations of the content. We fixed the position of the content as well and just used the timeline to reveal, "pin" (it's a fake pin just positioning things in the same place for a bit), and hide the content. I used set percentages (hand picked) for each animation so that it stays perfectly synced with the background images. I also allowed other configuration parameters (like ease, distance of translation, etc.) to be set via data attributes and used for the animations for that element. CustomEase was a big help. For some reason it helped certain browsers to use really short tweens for the background image displaying vs .set()s. The basic setup is as follows:

const tl = gsap.timeline({
  defaults: { duration: 0.0001 }, 
  paused: true,
  scrollTrigger: { 
    // ... 
  }
});

// Create the background image animation - this needs to come first
for (let i = 0; i < frameCount; i++) {
  // Show the image briefly
  tl.to(frameImages[i], {opacity: 1}, i);

  // Hide the image after a bit
  if(i !== frameCount - 1) {
    tl.to(frameImages[i], {opacity: 0}, i + 1);
  }
}

// Get the duration of the timeline to use for our positioning
const TLDur = tl.duration();

// Create the animations for each section 
myElems.forEach((elem, i) => {
  // Set things up
  const myStartTime = elem.dataset.startpercent/100 * TLDur;
  const myDur = (elem.dataset.endpercent - elem.dataset.startpercent)/100 * TLDur;

  // Get other parameters here

  gsap.set(elem, {
    position: 'fixed',
    // Other styles set here
  });

  // Animate the position and autoAlpha separately for more fine control
  startScrollTL.fromTo(elem, {
    autoAlpha: 1
  }, {
    autoAlpha: 0,
    duration: myDur,
    // I used a modified slow ease with yoyoMode: true to go in and out in one tween for this ease
    // https://greensock.com/docs/v3/Eases/SlowMo
    ease: myAlphaEase
  }, myStartTime)
  .to(elem, {
    // I only animated y here but you can do whatever
    y: () => `-${elem.dataset.endy - elem.dataset.starty}vh`,
    duration: myDur,
    ease: myYEase
  }, myStartTime)
});

 

  • Like 2

Share this post


Link to post
Share on other sites
On 8/17/2020 at 5:14 PM, marius96 said:

My idea is to use an array which holds all the images and then make use of ScrollTrigger.update() to update the img src  based on the scrolling position.

 

Changing the image source isn't a good idea. It takes 1 line of canvas code to draw an image in canvas.

function render() {
  context.drawImage(images[airpods.frame], 0, 0); 
}

 

If the images have a transparent background, then it would be only 2 lines.

function render() {
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.drawImage(images[airpods.frame], 0, 0); 
}

 

See the Pen 2152a28cffe2c2c0cca8a3e47f7b21c6 by osublake (@osublake) on CodePen

 

 

  • Like 6

Share this post


Link to post
Share on other sites

Thanks a lot for your suggestions.
I tested @OSUblake your first two examples and, ideed, they are working. Sadly, there are quite a few problems:

  • Microsoft Edge has a weird problem where if I start scrolling back up from the end to the top, it shows one random image and then suddenly starts the sprite process.
  • I tested quite a ton of grids for mobile, and the webpage can't properly load up. I'm sure this is because the file is too large or the grid is too big for mobile. 

@ZachSaucier I'm afraid I don't understand your code without seeing a CodePen.

@OSUblake Your last Pen is the best solution so far. It works across every browser, the performance of mobile is great and I don't have to play around too much with media queries to optimize this on mobile.

Sadly, I've encountered other problem.
Apple's way of fading in and fading out is pretty neat: immediately after first text element finishes fading out, the second element starts to fade in.

I managed to do that on desktop, but on mobile I can't do it.

I'm using ScrollTrigger.matchMedia to achieve the desired animation on mobile, but it doesn't work.

If I replace end: "bottom top" with end: "bottom -50%" in the all : function() { } , the effect works on mobile, but If I replace it in the "(max-width: 799px)" : function() { } , it doesn't. I think I'm doing something wrong with the media queries.

scrollTrigger: {
          trigger: target,
          markers: true,
          scrub: true,
          start: "center 50%",
          end: "bottom top",
          pin: true
        }

Here is a new Pen:

See the Pen LYNRrJY by make96 (@make96) on CodePen

Share this post


Link to post
Share on other sites

Nobody has any idea on how can I fix this for mobile devices?

Share this post


Link to post
Share on other sites

If you start stripping things out (to focus on the issue at hand) you will find that when you delete the "all" section it works just fine. Looking inside of that part, there's a competing ScrollTrigger that is never removed since it's within the "all". Did you mean to put it inside of the desktop breakpoint instead?

See the Pen yLOVJxd?editors=0010 by GreenSock (@GreenSock) on CodePen

  • Like 1

Share this post


Link to post
Share on other sites

Yes, that's what I was trying to do.

I'm sorry, I misunderstood how match media works. I thought, when the mobile viwerport would get triggered, the code in all section would not run and would get replaced by the mobile viewport code.

Thanks again!

Share this post


Link to post
Share on other sites
4 hours ago, marius96 said:

when the mobile viwerport would get triggered, the code in all section would not run and would get replaced by the mobile viewport code.

That's a testable hypothesis :) 

 

All means all ;) 

  • Like 1

Share this post


Link to post
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.

×