Animation with CSS and JS

Animation with CSS and JS

Simple CSS Animation and Intersection Observer

To create animations with CSS and JS to need to style what the element looks like

  • before animation
  • after animation I will be using .active class selector for the "after" animation styles in JavaScript.

Simple Animation

The following examples only require CSS and HTML. We need to understand the following properties:

  • transition: allows you to change property values smoothly over a given duration
  • opacity: specifies the transparency of an element
  • transform: lets you rotate, scale or translate an element
  • pointer-events: lets you control how HTML elements respond to mouse/touch events

To use these properties to animate elements, we need to style the element to what we want it to look like before and after the animation. This is my example:

animationeg.gif

HTML

    <body>
        <section class="parent">
            <button>Click Me</button>
            <div class="text-element">Hello World</div>
        </section>
    </body>

To achieve this animated look I initially set the CSS of the text element to this

.text-element {
    color: purple;
    font-size: 10rem;
    background-color: transparent;

    opacity: 0;
    pointer-events: none;
    transform: translateY(-3rem);
    transition: opacity 200ms ease-in-out, transform 200ms ease-in-out;
}
  • we want the text to have an initial opacity of 0 so it is hidden
  • we want the pointer-events: none; so that users can't accidentally click on it while it is not visible (not so important for this example, but you'll find this handy for future projects)
  • transform in the Y direction is used so that the text has the appearance of sliding downwards
  • transition is applied to this base element and dictates how long it takes to change from initial to final properties

CSS for what the element will look like after transition, while user is clicking the button:

button:focus + .text-element{
    pointer-events: auto;
    opacity: 1;
    transform: translateY(0px);
}
  • We are selecting the CSS pseudo-class :focus, so that when the user interacts with the button, the property styling will be applied to the text-element which makes it visible.

Smooth scroll

The effect smooth scroll looks like below, and is really simple to achieve.

smoothscroll.gif

This only requires one line of CSS

html {
    scroll-behavior: smooth;
}

The downside is that it is not cross browser compatible, check which browser it'll work for here.

Another method we can use to implement smooth scroll is via using this github repository: github.com/cferdinandi/smooth-scroll. This would work across all browsers, simply copy the CDN link into your HTML and use the following JS code.

HTML

<script src="https://cdn.jsdelivr.net/gh/cferdinandi/smooth-scroll@15.0.0/dist/smooth-scroll.polyfills.min.js"></script>

JS

const scroll = new SmoothScroll('a[href*="#"]');

Intersection Observer

Why should we use Intersection Observer? It's great for:

  • infinite scrolling
  • lazy loading images
  • scroll based animation

animate.gif

So, what is Intersection Observer?

Intersection Observer calls a function when a target element "intersects" with another element. This target element is by default the viewport.

Therefore, a callback function will be called when an element becomes visible to the user via scrolling

We need to use new IntersectionObserver constructor function with .observe() method.

Have a look at this example code:

CSS

.menu {
  opacity: 0; 
  transition: opacity 600ms ease-in;
}

.active {
  opacity: 1; 
}

Step by step JavaScript code

const observer = new IntersectionObserver(function( ), options)
  • IntersectionObserver has 2 parameters: a callback function and options for how to configure it.
const observer = new IntersectionObserver( entries => {
   entries.forEach(entry => {
      console.log(entry); 
    })
})
  • Callback function takes in a list of entries, which are all the elements that have "intersected" or are visible. Therefore, we need to use forEach to loop through each element

image.png

  • from using console.log we can see that the Intersection Observer fires all the elements as soon as the page loads
  • We don't want all our elements to be firing even when they are not visible, so we need to add an if statement
const observer = new IntersectionObserver( entries => {
   entries.forEach(entry => {
        if (entry.isIntersecting){
            entry.target.classList.toggle("fade-in");
        } else {
            return;
        }
    })
})
  • entry.isIntersecting returns true or false depending if the element is visible or not, so when element is not visible we exit out of function
  • When element is visible we add .active class which animates the element
const observer = new IntersectionObserver(
  entries => {
    entries.forEach(entry => {
        if (entry.isIntersecting){
            entry.target.classList.toggle("fade-in");
        } else {
            return;
        }
    }, {
  threshold: 0.5; 
  })
  • Options: By default the threshold is 0, which represents 0%. This means as soon as one pixel of the element is visible the function will be called. This results in the animation happening too fast, therefore I am setting it to 0.5.
const menus = document.querySelectorAll(".menu");

menus.forEach(menu => {
    observer.observe(menu)
}
  • We need to use observe() to observe when the element changes intersections
  • observer() only takes in one element at a time, therefore we need a forEach loop to go through each element we want to animate
  • note: I applied a class of .menu to all the elements I wanted to animate shown by the gif example

Now our code does this:

animate 2.gif

Notice how the elements are constantly fading-in and and fading-out, if we want out elements to not keep disappearing, like the first gif example, we can add this line of code to the JS:

const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
        if (entry.isIntersecting){
            entry.target.classList.toggle("fade-in");
      //entries don't animate away 
            observer.unobserve(entry.target);
        } else {
            return;
        }
    })
}, {
    threshold: 0.5, 
})
  • we will nowunobserve any element that is visible, which means it'll stay static after user has scrolled away

Other useful options

  • rootMargin: -100px makes our intersecting container 100px smaller from the top and bottom. +100px makes our intersecting container 100px larger which can be useful for pre-loading images before user scrolls to it.
  • root by default is set to the viewport but we can change it to any ancestor element

Conclusion

If you want to see the code for my cafe-website check it out here