Scroll Animation


See it in Action – clcik the image

Apple is known for its product design and innovations. It also has one of the most exciting web designs and features. One of the most interesting effects is scroll animation highlighting their products. I think there are many ways to achieve the effect, and each solution presents pros and cons of its own.

I tried to duplicate the effect using a sequence of images with CSS and Javascript. There are many resources and I few different methods found online and picked one I thought was the best. this method utilizes canvas.

html

You would need a canvas element with ID so that javascript can easily call it. I used it in a bootstrap flex container and gave it img-fluid class to make it responsive.

<canvas id="hero-animation" class="img-fluid py-4"/> 

css

I did not give it any style other than bootstrap class mentioned above. If you are not using the bootstrap, you might want to limit the size of image with “max-height: 100vh; max-width: 100vw;” or something similar. It is needed so that the image does not go beyond the viewport.

javascript

Now the main course. I am using the method presented in the tutorial on CSS-Tricks.
First you will need to setup the canvas:

const canvas = document.getElementById("hero-animation");
const context = canvas.getContext("2d");

It is good to know how animation is rendered out. In animation, image sequence is usually rendered out in a following format: filename+padding###+image#. Knowing that one should be able to create:

const currentFrame = index => ( `imgpath/${index.toString().padStart(4, '0')}.jpg')
const frameCount = 148; // total # of images
const images = [];

and pre-load to improve the performance. – example is 148 images, that is a lot of images.

const preloadImages = () => {
   for (let i = 1; i < frameCount; i++) {
      images[i] = new Image(); // same as document.createElement('img').
      images[i].src = currentFrame(i);
   }
};

preloadImages();

Now, drawing the first image

const img = new Image();
        img.src = currentFrame(1);
        img.onload = function(){
            context.drawImage(img, 0, 0);
        }

All elements are in place. We need to listen for the scroll event and get the appropriate frame – image.

const html = document.getElementsByTagName('html');
   window.addEventListener('scroll', () => {
   const scrollTop = html[0].scrollTop; //start
   const maxScrollTop = html[0].scrollHeight - window.innerHeight; //end
   const scrollFraction = (scrollTop / maxScrollTop) * 3; // control speed
   const frameIndex = Math.min(
      frameCount - 1,
      Math.floor(scrollFraction * frameCount)
      );

   requestAnimationFrame(() => context.drawImage(images[frameIndex + 1], 0, 0));

});

I am still working on this but created something with added fade-in animation to other elements.

Fade-in animation

html

Setup html element with classes. You will need two classes – off and active

Here, I am using div with reveal class

<div class="text-center py-4 reveal">
   <h1 class="text-light display-1 text-fu-b-i">
      All New Product
   </h1>
   <p class="lead text-light">
      Lorem, ipsum dolor sit amet consectetur adipisicing elit. Dolorem, consequatur.
   </p>
</div>

css

You will set the element initial class away from the intended position translateY is vertical translateX is horizontal and make it invisible by setting opacity to 0 on active state, you will move it to the intended position by setting translateY or translateX to 0

.reveal {
    position: relative;
    transform: translateY(150px); // y move up
    opacity: 0; // set the init opacity
    transition: 1s all ease; // animation
}
  
.reveal.active {
    transform: translateY(0); // y move up
    opacity: 1; // make it visible
}

you are all set.

javascript

This is where magic happens.

function reveal() {
   var reveals = document.querySelectorAll(".reveal");
  
   for (var i = 0; i < reveals.length; i++) {
      var windowHeight = window.innerHeight;
      var elementTop = reveals[i].getBoundingClientRect().top;
      var elementVisible = 150;
  
      if (elementTop < windowHeight - elementVisible) {
         reveals[i].classList.add("active");
         } else {
         reveals[i].classList.remove("active");
      }
   }
}

window.addEventListener("scroll", reveal);

This is easy to implement and highly effective way to make things more interesting especially if you mix things up by moving some on x axis while moving some on y axis.