One Pager: The Ultimate Javascript Animation Course
Tags: animations, javascript, css, html
Published: 2022-04-04 05:44:05

In this post I share the knowledge I got from the course shown in the trailer video below. These are notes that helped me learn, not a transcript. Credit goes to Dev Ed.

{% embed https://youtu.be/8ybWsJuwwz8 %}

#Basic (CSS)
Use transition for hover state
```css
/* CSS */
button {
  background: lightgray;
  color: black;
  transition: background 0.75s;
  /* transition: (property you want to animate) duration; */
}

button:hover {
  background: black;
  color: white;
}
```

Use transform for displacement. Add a CSS class with the transform with an event (for example, a click)
```css
/* CSS */
.nav-slide {
    transition: transform 1s ease-out;
    transform: translateY(-100%);
}
```
```javascript
/* Javascript */
const button = document.querySelector("button")
const nav = document.querySelector("nav")

button.addEventListener("click", () => {
    nav.classList.add("nav-slide")
})
```

Better to use toggle. Better to put transition properties on the element themselves since toggling the class removes the elements.
```css
/* CSS */
nav {
    transition: transform 1s ease-out;
}

.nav-slide {
    transform: translateY(-100%);
}
```
```javascript
/* Javascript */
const button = document.querySelector("button")
const nav = document.querySelector("nav")

button.addEventListener("click", () => {
    nav.classList.toggle("nav-slide")
})
```

#Using GSAP
Quick taste: fade something in and out.
```javascript
// Javascript
// Fade out downward
gsap.to(".text", {y: 10, opacity: 0, duration: 0.75})

// Fade in upward
gsap.fromTo(".text", {opacity: 0, y: 20}, {opacity: 1, y: 0, duration: 1})

// Appear with a bang
gsap.fromTo(".container", {scale: 0}, {scale: 1, duration: 0.75})
```

##Chaining / Timeline
Use gsap.timeline() to make a series of animations chained together. You can set default duration for all animations.
```javascript
// Javascript
const tl = gsap.timeline({defaults: {duration: 0.75}})
// these events will run in sequence, one after the other
// both with default duration set above
tl.fromTo(".container", {scale: 0}, {scale: 1})
tl.fromTo(".text", {opacity: 0}, {opacity: 1})
```

Any overwriting or further specifications happen in the second object taken as parameter by fromTo():
```javascript
// Javascript
const tl = gsap.timeline({defaults: {duration: 0.75}})
// duration is overwritten
tl.fromTo(".container", {scale: 0}, {scale: 1, duration: 1.5})
tl.fromTo(".text", {opacity: 0}, {opacity: 1})
```

Add "<" (quotes included) to the end of a fromTo() expression to play the animation right after the previous one, like this:
```javascript
// Javascript
const tl = gsap.timeline({defaults: {duration: 0.75}})
// <some previous animations here>
// animation below happens at the same time than last animation above
tl.fromTo(".text", {opacity: 0}, {opacity: 1}, "<")
```

You can also set default easing:
```javascript
// Javascript
const tl = gsap.timeline({defaults: {duration: 0.75, ease: "back.out(1.7)"}})
```

Add "<50%" (quotes included) to the end of a fromTo() expression to play the animation when the previous one is half way done:
```javascript
// Javascript
const tl = gsap.timeline({defaults: {duration: 0.75}})
// <some previous animations here>
// animation below begins when the last one above is exactly midway
tl.fromTo(".text", {opacity: 0}, {opacity: 1}, "<50%")
```

Add repeat and yoyo to both specify how many times to repeat the animation and to have it smoothly go back and forth:
```javascript
// Javascript
// repeat: 1 makes it do a full cycle
// repeat: -1 makes it go forever
tl.fromTo(".my-image", {y: 0}, {y: -20, rotation: "-20deg", yoyo: true, repeat: 1})
```

##Text Animations
The strategy is to have text pieces wrapped in <span></span>
and animate separately. Note: spans must have display: block to accept movement and dimension animations.
```css
/* CSS */
.my-span {
    display: block;
}
```
```javascript
// Javascript
// text moves into place while becoming visible
tl.fromTo(
    ".my-span",
    { x: 100, opacity: 0 },
    {
        x: 0,
        opacity: 1
    }
)
```
You can use percentages as the value for x above, but make sure to use quotation marks:
```javascript
// Javascript
// text moves into place while becoming visible
tl.fromTo(
    ".my-span",
    { x: "100%", opacity: 0 },
    {
        x: "0%",
        opacity: 1
    }
)
```

##Split text animation
The strategy is to iterate over the text content of the target element, substitute it with spans with class to target for each letter, make sure the spans have a display property of inline-block, and finally set an animation to it (making sure to use stagger so each letter moves at different moments):
```javascript
// Javascript
const word = document.querySelector(".word")
const letters = word.textContent.split("")
word.textContent = "" // clear current text content

letters.forEach((letter) => {
    // substitute previous letters with span with class
    word.innerHTML += `<span class="letter">` + letter + "</span>"
})
// add inline block to make them movable
// just block would put them vertically
gsap.set(".letter", { display: "inline-block" })
// more on gsap.set() below
// animate all with class ".letter", stagger prevents them moving at once
gsap.fromTo(".letter", { y: 30 }, { y: 0, delay: 2, stagger: 0.05 })

```

gsap.set() basically grabs something from the DOM based on class or id and gives you access to add or modify its CSS properties (see code above and below for examples)

##Animating SVGs

This:
```javascript
// Javascript
gsap.set(".bell", {transformOrigin: "top center"})
```
...is equivalent to this:
```css
/* CSS */
.bell {
  transform-origin: top center;
}
```

######When bringing in an svg from Figma, it can be helpful to check the box for include id attribute when exporting

To make a swinging bell animation with an SVG of a bell, the strategy is not to control the swing to one side, a return to the middle, then a swing to the other side, and so on. Instead, with only one gsap.fromTo() and an ease you are mostly done:
```javascript
// Javascript
gsap.set(".bell", {transformOrigin: "top center"})
gsap.fromTo(".bell", {rotation: -5}, {rotation: 0, duration: 2, ease: "elastic.out(5.5, 0.2)"}
})
```

Reverse/Inverse an image by setting the scale from 1 to -1, making sure to properly set the transform origin. For example, for opening the flap of an envelope:
```javascript
// Javascript
gsap.set(".flap", {transformOrigin: "top"})
messages.addEventListener("click", () => {
    tl.fromTo(".flap", {scale:1}, {scale: -1})
})
```

The elastic line effect for the form inputs was made by:

**Getting 2 lines from Figma**

* drawing a line in Figma (hold shift to keep on major axis)
* flattening it (right click > flatten) to make it a path
* added a point in the middle by double clicking the line
* selecting point using move tool
* using bend tool to click point while holding shift while dragging along the line to the right, new points appear on both sides
* switch to move tool, drag center point down to see bent line (curve)
* de-select line by double clicking outside of it
* right click and copy as svg on both line and curved versions
* paste in code editor

**Modifying existing line svgs' path elements, specifically the d attributes**

* iterate over the DOM container holding your line svgs for each input and add an event listener on focus that checks if the input has a value. If it doesn't then run animation
* the animation itself takes each line being iterated on and goes from an attr: {d: start} to attr: {d: end}. This d attribute is part of the svgs obtained from Figma and controls the path of the line.

Here is the iteration part:
```javascript
// Javascript
inputContainer.forEach(container => {
    const input = container.querySelector(".input")
    const line = container.querySelector(".elastic-line")

    input.addEventListener("focus", () => {
        // Check if input has value
        if (!input.value) {
            tl.fromTo(line, { attr: { d: start } }, { attr: { d: end }, ease: "Power2.easeOut", duration: 0.75 })
            tl.to(line, { attr: { d: start }, ease: 'elastic.out(3, 0.5)' }, '<50%')
        }
    })
})
``` 

To revert any animation when the user clicks off an input element, and if the input has no value:
```javascript
// Javascript
if (document.activeElement !== input) {
    if (!input.value) {
        // revert animation here
    }
}
```

To actively check the user's inputs are appropriate to the input type:
```javascript
// Javascript
input.addEventListener("input", (e) => {
    if (e.target.type === "<input type here>") {
        // Make checks here with e.target.value
    }
}
```

Here are two validation functions, one for email and the other for phone numbers:
```javascript
// Javascript
function validateEmail(email) {
    let re = /\S+@\S+\.\S+/;
    return re.test(email);
}
  function validatePhone(phone) {
    let re = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im;
    return re.test(phone);
}
```

Something fun is to change the color of the line if the validation checks return false. This can be done by accessing the stroke attribute of an svg's path:
```javascript
// Javascript
gsap.to(".myLineSvgsPath", {stroke: "my-color"})
```

To make a checkmark in a checkbox be drawn "handwritten style":
```javascript
// Javascript
const checkMarkPath = document.querySelector(".check-mark path")
// getTotalLength is a javascript function that works on svgs
const pathLength = checkMarkPath.getTotalLength()

// Set these attributes to make prepare for a handwritting effect
gsap.set(checkMarkPath, {
    // these belong to the checkmark svg's path element
    strokeDashoffset: pathLength,
    strokeDasharray: pathLength
})

checkbox.addEventListener("click", () => {
    if (checkbox.checked) {
        tl.fromTo(
            checkMarkPath,
            { strokeDashoffset: pathLength },
            {strokeDashoffset: 0}
        )
    }
})
```

When animating blinking eyes it can be useful to center the transform origin, add repeatDelay, and add ease:
```javascript
// Javascript
gsap.set("#eye", {transformOrigin: "center"})
gsap.fromTo("#eye", {scaleY: 1}, {scaleY: 0.3, repeat: -1, yoyo: true, repeatDelay: 0.5, ease: "Power2.easeOut"})
```

#Using Barba JS
Their documentation takes you through how to make a basic fade out fade in transition. Here is an example code with 2 more things beyond the basic transition: preventRunning and this.async():
```javascript
// Javascript
barba.init({
    // prevent running keeps you from starting another page transition
    // before the previous one is done by clicking really fast
    preventRunning: true,
    transitions: [
        // showcase transitions
        {
            name: "default",
            leave(data) {
                // this async function lets one animation finish before
                // another one starts
                const done = this.async()
                let current = data.current.container
                gsap.fromTo(
                    current,
                    { opacity: 1 },
                    { opacity: 0, duration: 1, onComplete: done })
            },
            enter(data) {
                // this async function lets one animation finish before
                // another one starts
                const done = this.async()
                let next = data.next.container
                gsap.fromTo(
                    next,
                    { opacity: 0 },
                    { opacity: 1, duration: 1, onComplete: done })
            }
        }
    ]
})
```

Besides leave and enter, there is also a once() that will run one time when the page is first loaded.

#GSAP Scroll Animations
Can be done by accessing a free GSAP plug in called Scroll Trigger. Import by copying their CDN into body of HTML.

##Simple nav fade out scroll animation
Trigger is the target that must hit the top of the page of a scroll animation to happen. Start is the vertical position where the animation will start, correspondingly for end. Scrub set to true means that the intermediate percentages between start and end will correspond to intermediate transitions between the start and finished animation.
```javascript
// Javascript
const tlIntro = gsap.timeline({
    scrollTrigger: {
        trigger: ".first-page",
        start: "0%",
        end: "25%",
        scrub: true
    }
})

tlIntro.fromTo("nav", {opacity: 1}, {opacity: 0})
```

When defining the gsap timeline for a scroll animation you can add markers (like page guidelines) to see where each animation will start and end: 
```javascript
// Javascript
const tlIntro = gsap.timeline({
    scrollTrigger: {
        // others
        markers: true
        // or define their colors
        markers: { startColor: "blue", endColor: "blue" }
    }
})
```

You can also add two more details to make the content below your target move upwards while covering the target. Like pinning it:
```javascript
// Javascript
const tlIntro = gsap.timeline({
    scrollTrigger: {
        // others
        pin: true,
        pinSpacing: false
    }
})
```

Beyond this point in the course there are 3 big things:
How to make a carousel?
How to make a video animation?
How to make a parallax effect?
...but I'm just dying to implement what I've learned so far. Check out Dev Ed's course for more!