Progressive Ember - Remove Liquid Fire

A walk through the real effort to transition an enterprise level Ember application to a progressive web application.

Part three: Removing liquid-fire

First

I love liquid-fire. I’m also really looking forward to Ed Faulkner’s next animaton toolkit for Ember, ember-animated.

There’s nothing wrong with liquid-fire, but I’m on a quest to reduce my vendor payload to improve application performance. My application really doesn’t do a lot of animation. The primary interaction that used animation was an expand/collapse animation. For 27 kB, I’m willing to part with it in order to reduce my vendor build size.

Pro Tip: Just because you can ember install something, doesn’t mean you need to. In my case, I don’t need 27 kB of JavaScript to achieve something that I can do for a few hundred bytes and some of my time.

Expand/collapse alternative

The main code we’re replacing is something like:

{{#liquid-spacer}}
    {{#liquid-if showContent}}
        some content
    {{/liquid-if}}
{{/liquid-spacer}}

When showContent is true, my containing element (liquid-spacer) will animate smoothly to fit my content. When showContent becomes false, my content will smoothly animate back to collapsed.

Animating from one height to the next is somewhat simple and can be achieved in several ways. Here is a way you could tackle this using window.requestAnimationFrame:

import { run } from '@ember/runloop';

animateHeight(selector, from, to) {
    run(() => {
        // this.element => assuming this is an Ember component
        const element = this.element.querySelector(selector);
        requestAnimationFrame(() => {
            element.style.height = `${from}px`;
            requestAnimationFrame(() => {
                element.style.height = `${to}px`;
            });
        });
    });
}

Simple. The real magic though, is when you don’t know the height. In my expand/collapse example, I know that I have a <div> that’s going to animate to some final state. Because I am conditionally displaying the content, that content isn’t in the DOM when the div is collapsed, so I don’t know what to animate to. If my content is static, I could give a rough approximation. For dynamic content, this is more challenging.

My general solution is to:

this.element.querySelector('.some-selector').offsetHeight

In practice, this might look something like:

import { task, waitForQueue } from 'ember-concurrency';
import { get, set } from '@ember/object';

animate: task(function*() {
    // compute initial height
    const initialHeight = this.getElementHeight();

    // toggle the content in the DOM - show/hide
    set(this, 'showContent', !get(this, 'showContent'));

    // wait for the Ember run loop
    yield waitForQueue('afterRender');

    // compute the final height
    const finalHeight = this.getElementHeight();

    // perform the animation
    this.animateHeight(initialHeight, finalHeight);
})

You will also need some basic CSS to make this work:

.your-element {
    overflow: hidden;
    transition: height 0.4s ease-out;
    height: auto;
}

Results

My vendor build before:

Baseline Build

After refactoring my animation, removing liquid-fire and velocity-animate from my vendor build:

No Liquid Fire - Build

27 kB removed! Up next, lets optimize our web font loading.

Keep reading