AnimXYZ: Composable CSS Animations for Vue and HTML

Most animation libraries such as GSAP and Framer Motion are built entirely with JavaScript or TypeScript. AnimXYZ takes a different approach: it is a composable CSS animation toolkit built mainly with SCSS. It works by leveraging CSS variables, allowing you to create custom animations without writing a single CSS keyframe. Its declarative nature makes it straightforward to animate elements as they enter or leave the page: add the class xyz-in to fade an element into view, or xyz-out to animate it out.

The toolkit works with plain HTML, Vue.js, and React, though React support is still under development at the time of writing. It is also notably compact, with a base size of 2.68 KB and 11.4 KB when including the convenience utilities. This article assumes a basic understanding of Vue.js and CSS.

Core Concepts

Utilities and Variables

AnimXYZ gives you descriptive class names and attributes to compose animations. For example, applying an xyz attribute with a value of fade to a paragraph will make it fade into the page. To fade it out, you would use a different composable set. These utilities handle common effects like fading, flipping, and rotating without custom CSS.

Contexts for Group Animations

The xyz attribute also provides context for animating related groups of elements. If you add the xyz attribute to a parent element, all child elements will inherit the same animation utilities. For instance, setting xyz="fade flip-up flip-left" on a parent will make all child divs with a class of shape fade into the page while flipping to the upper left.

To animate a child element differently from its parent, simply add an xyz attribute with different instructions to the child. This resets all animation properties it inherited from the parent, giving you granular control.

Staggering Elements

To animate a list of elements in sequence rather than all at once, you can use the stagger utility. This controls the animation-delay CSS property for each element in a list, so that their animations are triggered one after another. Adding the stagger utility to a parent div will cause its children to animate sequentially from left to right. The order can be reversed with stagger-rev.

Using AnimXYZ with Plain HTML and CSS

Integrating AnimXYZ into a project is straightforward via a CDN link. Simply add the link to the head of your HTML document. From there, you can begin applying AnimXYZ classes and attributes directly to your markup.

For a card example, you might add an xyz attribute like the following to a div with an id of glass:

  xyz="fade flip-down flip-right-50%  duration-10"

In this example, the composable utilities make the card fade into the page. The flip-down value flips the card into view from the bottom, and flip-right flips it by 50% when leaving. An animation duration of 10 (representing 1 second) sets the length of one animation cycle.

Integrating AnimXYZ in Vue.js

Setup and Installation

To use AnimXYZ in a Vue.js project, scaffold a new application using the Vue CLI:

vue create animxyz-vue

Then install the VueAnimXYZ package, which includes both the core toolkit and the Vue.js-specific bindings:

npm install @animxyz/vue

After installation, import the VueAnimXYZ package and register the plugin globally in your main.js file:

import VueAnimXYZ from '@animxyz/vue'  // import AnimXZY vue package
import '@animxyz/core' // import AnimXZY core package

Vue.use(VueAnimXYZ)

The XyzTransition Component

The XyzTransition component is built on top of Vue.js's transition component. It is designed to animate individual elements into and out of the page. This component abstracts away much of the complexity of the native Vue.js transition component, leaving you to focus on only a few props: appear, appear-visible, duration, and mode.

To use it, wrap the element you intend to animate inside the XyzTransition component. The child element will inherit the utilities applied to the parent component. For conditionally rendered elements, you can use a directive such as v-if to toggle the element into and out of the DOM.

<div id="app">
    <button @click="isAnimate = !isAnimate">Animate</button>
    <XyzTransition
      appear
      xyz="fade up in-left in-rotate-left out-right out-rotate-right"
    >
      <div class="square" v-if="isAnimate"></div>
    </XyzTransition>
</div>

The XyzTransitionGroup Component

For animating groups of elements, AnimXYZ provides the XyzTransitionGroup component. Built on top of Vue.js's transition-group, it similarly simplifies the API, focusing on the appear, appear-visible, duration, and tag props. This component is ideal for lists where multiple items need coordinated animation.

<XyzTransitionGroup
  appear={ boolean }
  appear-visible={ boolean | IntersectionObserverOptions }
        duration={ number | 'auto' | { appear: number | 'auto', in: number | 'auto',
                   out: number | 'auto' } }
        tag={ string } >
        <child-component />
        <child-component />
        <child-component />
</XyzTransitionGroup>

Building an Animated Modal

To illustrate more complex use cases, consider building a modal with AnimXYZ. By adding an xyz="fade out-delay-5" property to the XyzTransition component wrapping the modal, you can control its entrance and exit animations.

When animating dialog content, you may want to apply the class .xyz-nested to child elements. This is necessary to trigger their animations when the modal opens. Additionally, an ease-out-back property on the dialog container will add a slight overshoot effect when the dialog opens and closes.

To make the animation feel more natural, add an in-delay to child elements. This delays their animation until the other content of the modal has animated in, creating a cascading effect.

  <section class="xyz-animate">
    <div class="alerts__wrap copy-content">
      <div class="alert reduced-motion-alert">
        <p>
          AnimXYZ animations are disabled if your browser or OS has
          reduced-motion setting turned on.
          <a href="https://web.dev/prefers-reduced-motion/" target="_blank">
            Learn more here.
          </a>
        </p>
      </div>
    </div>
    <h1>Modal Animation With AnimXYZ and Vue.js</h1>
    <button
      class="modal-toggle modal-btn-main"
     
     
     
      id="label_modal_kdf8e87cga"
      aria-haspopup="dialog"
      ref="openButton"
      @click="open"
      autofocus
    >
      Open Modal
    </button>
    <span
      id="js-modal-overlay"
      class="simple-modal-overlay"
     
      title="Close this window"
      v-if="isModal"
      @click="close"
    >
      <span class="invisible">Close this window</span>
    </span>
    <div
      role="dialog"
      class="simple-modal__wrapper"
      aria-labelledby="modal-title"
    >
      <XyzTransition duration="auto" xyz="fade out-delay-5">
        <section
          id="modal1"
          aria-labelledby="modal1_label"
          aria-modal="true"
          class="modal xyz-nested"
          xyz="fade small stagger ease-out-back"
          v-if="isModal"
          tabindex="-1"
          ref="modal"
          @keydown.esc="close"
        >
          <div class="modal_top flex xyz-nested" xyz="up-100% in-delay-3">
            <header
              id="modal1_label modal-title"
              class="modal_label xyz-nested"
              xyz="fade right in-delay-7"
            >
              Join our community on Slack
            </header>
            <button
              type="button"
              aria-label="Close"
              xyz="fade small in-delay-7"
              class="xyz-nested"
              @click="close"
              title="Close"
            >
              <svg viewBox="0 0 24 24" focusable="false" aria-hidden="true">
                <path
                  fill="currentColor"
                  d="M.439,21.44a1.5,1.5,0,0,0,2.122,2.121L11.823,14.3a.25.25,0,0,1,.354,0l9.262,9.263a1.5,1.5,0,1,0,2.122-2.121L14.3,12.177a.25.25,0,0,1,0-.354l9.263-9.262A1.5,1.5,0,0,0,21.439.44L12.177,9.7a.25.25,0,0,1-.354,0L2.561.44A1.5,1.5,0,0,0,.439,2.561L9.7,11.823a.25.25,0,0,1,0,.354Z"
                ></path>
              </svg>
            </button>
          </div>
          <div class="modal_body xyz-nested" xyz="up-100% in-delay-3">
            <div class="modal_body--top flex justify_center align_center">
              <img
                src="../assets/slack.png"
                alt="slack logo"
                class="slack_logo"
              />
              <img src="../assets/plus.png" alt="plus" class="plus" />
              <img
                src="../assets/discord.png"
                alt="discord logo"
                class="discord_logo"
              />
            </div>
            <p><span class="bold">929</span> users are registered so far.</p>
          </div>
          <form class="modal_form" autocomplete>
            <label for="email"
              ><span class="sr-only">Enter your email</span></label
            >
            <input
              id="email"
              type="email"
              placeholder="[email protected]"
              autocomplete="email"
              aria-describedby="email"
              class="modal_input"
              required
            />
            <button type="submit" class="modal_invite_btn">
              Get my invite
            </button>
            <p>Already joined?</p>
            <button
              type="button"
              aria-describedby="open_slack"
              class="
                modal_slack_btn
                flex
                align_center
                justify_center
                xyz-nested
              "
              xyz="fade in-right in-delay-7"
              id="open_slack"
            >
              <span
                ><img src="../assets/slack.png" alt="slack logo" role="icon"
              /></span>
              Open Slack
            </button>
          </form>
        </section>
      </XyzTransition>
    </div>
  </section>

In your modal component, use the v-if="isModal" directive to keep the modal hidden by default. When a button is clicked, call an open() method that sets the isModal property to true. This reveals the modal and applies all specified animation properties.

<script>
export default {
  data() {
    return {
      isModal: false,
    };
  },
  methods: {
    open() {
      if (!this.isModal) {
        this.isModal = true;
        this.$nextTick(() => {
          const modalRef = this.$refs.modal;
          console.log(modalRef);
          modalRef.focus();
        });
      }
    },
    close() {
      if (this.isModal) {
        this.isModal = false;
        this.$nextTick(() => {
          const openButtonRef = this.$refs.openButton;
          openButtonRef.focus();
        });
      }
    },
  },
};
</script>

Respecting Reduced Motion

AnimXYZ animations are automatically disabled when a user has enabled the reduced-motion setting in their browser or operating system. To provide a clear user experience, you can display a helper message for users who have opted in to reduced motion. Use the @media screen and (prefers-reduced-motion) media query to show a custom notification about the disabled animations.

<style>
@media (prefers-reduced-motion: reduce) {
  .alerts__wrap {
    display: block;
  }
}
</style>

Summary

AnimXYZ offers a fast, declarative route to CSS animations with minimal code. Whether you are working with plain HTML or Vue.js components, you can create engaging effects such as fading, flipping, staggering, and custom transitions just by adding composable utility classes. The library handles performance and accessibility considerations, including reduced-motion preferences, out of the box. For further reference, the official AnimXYZ documentation is the primary resource for details on utilities, CSS variables, and the Vue.js component API.