Animating In & Animate Out

Here we will handle the animate in and animate out functions for the graphic.

We still have to handle the remaining play out commands executed by CasparCG. To begin, we are going to add two more functions to our graphic, animateIn and animateOut.

'use strict';

// lower-third.1.js

const _graphic = (function() {

    /* Update Function */
    function animateIn() { }
    function play() { }
    function next() { }
    function animateOut() { }
    function stop() { }
    function remove() { }

    function handleError(e) {console.error(e)}
    function handleWarning(w) {console.log(w)}
    return { }

})();

Handle the Play Command

Let's handle the play function first. First we want to make sure the graphic is in the correct state. If it is, we can call animateIn and increment the state to 2.

function animateIn() { }

function play() {
    // State 1 means graphic is loaded and ready to be played
    if(state === 1) {
        animateIn();
        state = 2;
    }
}

function next() { }

Animating In

Setting up the Variables

Our animateIn function will be similar to the applyStyles and applyData functions. We will begin by getting a reference to all the elements that will be animated.

/* Update Function */
function animateIn() {
    const graphic = document.querySelector('.lt-style-one .graphic');
    const [pathLeft, pathRight] = graphic.querySelectorAll('svg path');
    const title = graphic.querySelector('h1');
    const subtitleCon = graphic.querySelector('.subtitle');
    const subtitle = subtitleCon.querySelector('p');
}
/* Play Function */

We will use the deconstructing trick with the SVG paths again. Then have to address some scaling issues with our SVG paths.

Dynamic Stroke Length w/ Dash Array & Dash Offset

The strokeDasharray and strokeDashoffset are two new properties for SVG path elements. The strokeDasharray property breaks our path's stroke into mini segments while the strokeDashoffset offsets the path's stroke. Combining these two properties together will give you an animation that looks like the paths are being "drawn onto" the screen. We are going to want to allow the graphic to change size dynamically based on the length of the h1 element. This means the value used for strokeDasharray and strokeDashoffset will very based on the .graphic element's size.

At first glance it may look like graphic.style.width would be the easy answer but, this value is and empty string due to the width being set by the style sheet. We can use two functions called getComputedStyle and getPropertyValue to get the width the browser is using. Let's start with an example.

/* Example */
const elem = document.querySelector('body');
const computedStyles = window.getComputedStyle(elem);
const width = computedStyles.getPropertyValue('width');

The function getComputedStyle takes one argument and that is a DOM element. It then returns the styles set by the CSS style sheet and browser. From there, we call getPropertyValue on the computedStyles. It takes one argument and it can be any CSS value we want to know. We could get the width of the .graphic element in our animateIn function, but to make it more versatile, we are going to write a separate function that can be used in a variety of situations.

// Gets the CSS values used by the browser
// @param {DOM Node} elem - The element whos styles you want
// @param {string | string[]} styles - The CSS properties needed
// @returns {any[]} An array of strings and/or numbers
function getComputedStyle(elem, styles) {
    // Get the element's computed styles
    const computedStyles = window.getComputedStyle(elem);
    // Create an array to hold the requested results
    const values = [];
    if(Array.isArray(styles)) {
        // Loop over each style requested and all the value to the result
        styles.forEach(s => 
            values.push(computedStyles.getPropertyValue(s)));
    } else {
        values.push(computedStyles.getPropertyValue(styles));
    }
    return values.map(v => {
        // Clean up pixel values
        if(v.includes('px')) v = Number(v.substring(0, v.length - 2));
        return v;
    });
}

function animateIn() {
    const graphic = document.querySelector('.lt-style-one .graphic');
    const [pathLeft, pathRight] = graphic.querySelectorAll('svg path');
     /* Other Element Variable Definitions */
    
    // Request the .graphic elements width
    const graphicWidth = getComputedStyle(graphic, 'width')[0];
    // Our path wraps around our graphic making 
    // it's path width about twice as big
    const pathWidth = graphicWidth * 2; 
}

This getComputedStyles function allows us to pass in an element and either an CSS property or an array of CSS properties and get them back as an array of values.

Animating In

Now that we have our dynamic width, we can animate the graphic in!

function animateIn() {
    /* Element Variable Definitions */
    
    const tl = new gsap.timeline({duration: 1, ease: 'power1.out'});
    tl.set([pathLeft, pathRight], {
            strokeDashoffset: pathWidth, 
            strokeDasharray: pathWidth 
        }).set(title, {y: '15vh'})
        .set(subtitleCon, {y: '10vh'})
        .set(subtitle, {y: '20vh'})
        // Reveal the graphic
        .set(graphic, {opacity: 1})
        // Animation Begins
        .to([pathLeft, pathRight], {
            strokeDashoffset: 0, 
            duration: 1.5 // Overides the default duration
        }).to(title, {y: 0}, '-=1')
        .to(subtitleCon, {y: 0}, '-=.9')
        .to(subtitle, {y: 0}, '-=1');
}

We haven't seen the set function yet but, it simple changes the values instantly, instead of animating them over time. If we save the JS file, refresh the browser or reload Caspar, run update with our data, and then run play we should see our graphic animate in.

Handling the Stop Command

Creating the stop function is very similar to the play function but we will check for state 2 instead.

function animateOut() { }

function stop() {
    // State 2 means graphic is played and ready to be stopped
    if(state === 2) {
        animateOut();
        state = 1;
    }
}

function remove() { }

Animating Out

Animating in was the majority of the work since an animation out, is typically the same animation in but backwards. Let's reorder the animateIn function and change some values to get out animateOut function.

function animateOut() {
    /* The same vas the animateIn function */
    const graphic = document.querySelector('.lt-style-one .graphic');
    const [pathLeft, pathRight] = graphic.querySelectorAll('svg path');
    const title = graphic.querySelector('h1');
    const subtitleCon = graphic.querySelector('.subtitle');
    const subtitle = subtitleCon.querySelector('p');
    const titleWidgth = getComputedStyle(graphic, 'width')[0];
    const pathLength = titleWidgth * 2;
    
    const tl = new gsap.timeline({duration: 1, ease: 'power1.in'});
    tl.to(title, {y: '15vh'})
        .to(subtitleCon, {y: '10vh'}, '-=.75')
        .to(subtitle, {y: '20vh'}, '-=.55')
        .to([pathLeft, pathRight], {
            strokeDashoffset: pathLength, 
            ease: 'power1.inOut', 
            duration: 2
        }, '-=1')
        .to(graphic, {opacity: 0}, '-=.25');
}

The animate out value's are the values used for the set function in the animation in. Feel free to play around with the properties until you find an animation you prefer.

Resources

Last updated