marius96 Posted August 17, 2020 Share Posted August 17, 2020 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 Link to comment Share on other sites More sharing options...
ZachSaucier Posted August 17, 2020 Share Posted August 17, 2020 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: 2 Link to comment Share on other sites More sharing options...
marius96 Posted August 19, 2020 Author Share Posted August 19, 2020 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.". 1 Link to comment Share on other sites More sharing options...
OSUblake Posted August 19, 2020 Share Posted August 19, 2020 Setting background position... bad. Using transforms... good. See the Pen 393a8cb91527a0d238d626eb134c0ab0 by osublake (@osublake) on CodePen But canvas is probably better... using a packed sprite sheet. See the Pen KKzgqKr by osublake (@osublake) on CodePen 7 Link to comment Share on other sites More sharing options...
ZachSaucier Posted August 19, 2020 Share Posted August 19, 2020 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. Link to comment Share on other sites More sharing options...
ZachSaucier Posted August 19, 2020 Share Posted August 19, 2020 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) }); 5 Link to comment Share on other sites More sharing options...
OSUblake Posted August 19, 2020 Share Posted August 19, 2020 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 8 Link to comment Share on other sites More sharing options...
marius96 Posted August 19, 2020 Author Share Posted August 19, 2020 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 Link to comment Share on other sites More sharing options...
marius96 Posted August 20, 2020 Author Share Posted August 20, 2020 Nobody has any idea on how can I fix this for mobile devices? Link to comment Share on other sites More sharing options...
ZachSaucier Posted August 20, 2020 Share Posted August 20, 2020 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 3 Link to comment Share on other sites More sharing options...
marius96 Posted August 21, 2020 Author Share Posted August 21, 2020 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! Link to comment Share on other sites More sharing options...
ZachSaucier Posted August 21, 2020 Share Posted August 21, 2020 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 1 Link to comment Share on other sites More sharing options...
ZachSaucier Posted October 28, 2020 Share Posted October 28, 2020 Another option for this sort of effect is to use carefully encoded videos, potentially also using data blobs. See this demo by Shaw for more info. 2 Link to comment Share on other sites More sharing options...
4nz Posted December 20, 2020 Share Posted December 20, 2020 Hey @ZachSaucier, I am using your example from above (canvas + keyframes) and I would like to know how you would enhance the code to pause the keyframe animation on specific keyframes while the text is displayed and then continue playing the animation? Apple is doing this on their AirPods Pro site right here (see screenshot for exact section): Pausing Animation on Apple Website I would be very glad for any input on how to achieve this effect. Link to comment Share on other sites More sharing options...
ZachSaucier Posted December 21, 2020 Share Posted December 21, 2020 Hey @4nz and welcome to the GreenSock forums. Are you saying that you're using the frame object approach that I posted a demo of above? If so, to leave a particular frame up for longer you'd need to animate the frame object differently. Perhaps it'd make more sense to have different tweens in a timeline to animate that object. Link to comment Share on other sites More sharing options...
4nz Posted December 22, 2020 Share Posted December 22, 2020 Hey @ZachSaucier thanks for the welcome. Yes I am using the frame object approach and you described perfectly what I would like to achieve: leaving a particular frame up for longer (so I have time to fade in + fade out some text to describe a detail shown in that frame). Could you elaborate a little more the "different tweens in a timeline" approach? That sounds kind of doable, although I am unsure on how or where to start. Thank you! Link to comment Share on other sites More sharing options...
ZachSaucier Posted December 22, 2020 Share Posted December 22, 2020 In the demo look at the tween that affects the airpods object's frame value. That's what's controlling which image is being shown. Instead of using a single tween for that animation, you likely want to replace it with a timeline of different tweens to give you more control. Link to comment Share on other sites More sharing options...
4nz Posted December 23, 2020 Share Posted December 23, 2020 Thank you @ZachSaucier, that works perfectly well. Link to comment Share on other sites More sharing options...
Nikhil Tyagi Posted February 19, 2021 Share Posted February 19, 2021 On 8/19/2020 at 6:41 PM, ZachSaucier said: 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) }); Is there any demo related to this code Link to comment Share on other sites More sharing options...
ZachSaucier Posted February 19, 2021 Share Posted February 19, 2021 3 hours ago, nikhiltyagicse said: Is there any demo related to this code Sorry, no. The psuedo-code that I provided should help you get started. Link to comment Share on other sites More sharing options...
Nikhil Tyagi Posted February 25, 2021 Share Posted February 25, 2021 See the Pen NWbXwPE?editors=1001 by nikhiltyagicse (@nikhiltyagicse) on CodePen 1) In Your Code // 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); } } I want to use canvas to display hide images, how can i do that, 2) startpercent, endpercent Small Description, how to declare these value correctly; Example: i have a animation of duration :75, If i want to start first animation at 0.3 sec and end at 0.5 sec. What is the best way to calculate. 3) Declare startScrollTL I need help in declaring startScrollTL What am i doing wrong? Pls help, i am trying this from many days, aligning text and bg images, Any help/ guidance would be appreciated Link to comment Share on other sites More sharing options...
ZachSaucier Posted February 25, 2021 Share Posted February 25, 2021 9 hours ago, Nikhil Tyagi said: I want to use canvas to display hide images, how can i do that, I'd probably keep an object that represents what image to show. Then animate that object with the timeline (making sure to snap to the nearest whole number). Then inside of the onUpdate of the animation, if the image number changed, I would have the logic for drawing the new image over the old one. 9 hours ago, Nikhil Tyagi said: 2) startpercent, endpercent Small Description, how to declare these value correctly; Example: i have a animation of duration :75, If i want to start first animation at 0.3 sec and end at 0.5 sec. What is the best way to calculate. Sorry, I don't really understand your question. I think it would help you if you read about how duration works with a scrub, covered in the ScrollTrigger docs. Link to comment Share on other sites More sharing options...
errrrs Posted March 31, 2021 Share Posted March 31, 2021 Any chance someone can show how you could play a part of the image sequence on page load? So for example, in the Airpod example, once the page loaded it would autoplay the first 10-15 frames? See the Pen ZEbGzyv by j-v-w (@j-v-w) on CodePen Link to comment Share on other sites More sharing options...
GreenSock Posted April 1, 2021 Share Posted April 1, 2021 18 hours ago, errrrs said: Any chance someone can show how you could play a part of the image sequence on page load? So for example, in the Airpod example, once the page loaded it would autoplay the first 10-15 frames? You're saying that the first 10-15 frames would play after they load, and then the scroll-linked animation would only show the portion of the image sequence AFTER those frames? In other words, the only time users would see the first 10-15 frames would be the auto-play onload and they'd never be able to scroll up to see those again? You can't really have it both ways unless you literally force the page to scroll initially to go through those frames. See what I mean? It's all very doable, but unfortunately we don't have the resources to provide free consulting services to build effects like this but we'd be happy to answer any GSAP-specific questions about the API or ScrollTrigger functionality, etc. 1 Link to comment Share on other sites More sharing options...
errrrs Posted April 1, 2021 Share Posted April 1, 2021 Hey thanks for getting back to me, I actually figured out a way to do it by tweening the first 30 frames on load in one render, and then having the scroll-linked animation take over after that starting at 30th frame in another render. Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now