Create a JavaScript library. CSS animation support
Tags: javascript, typescript, webdev, css
Published: 2020-06-30 10:28:21

As you can see, the current implementation of the library implies changing the `display` property from `none` to `block`. The only problem with this approach is that this property cannot be animated via CSS, which is why the modal window opens too sharply. Today we are implementing support for CSS animations when opening/closing a window.

{% youtube f6BzW3kPIKM %}

## CSS animation

Animations help improve the perception of your content and make your project truly unique.

There are several ways to implement animations, including JavaScript and CSS. CSS animations are now a very powerful tool and the main plus of such animations in performance and optimization at the browser level.

Let's take a closer look at how it works.

### Keyframes

`keyframes` are used to specify animation property values ​​at various points in the animation. Keyframes determine the behavior of one animation loop. Animation can be repeated zero or more times.

The basic syntax looks like this:

```css
@keyframes animationName {
  from {
    /* code here*/
  }

  to {
    /* code here*/
  }
}
```

In this syntax, we have a description of the animation in two states. There is also a more flexible syntax if your animation is more complex and cannot be described only by the initial and final state

```css
@keyframes animationName {
  0% {
    /* code here*/
  }

  100% {
    /* code here*/
  }
}
```

Usage example:

{% codepen https://codepen.io/alexandrshy/pen/xxZLBLw %}

### CSS Animation Properties

The property that was used in the example `animation: shade .5s alternate infinite ease;` it is a short-hand which can consist of 8 separate properties.

#### `animation-name`

This is the name of the keyframe specified by `@keyframes` rule.

```css
animation-name: none;
animation-name: rebound;
animation-name: rebound-slow;
animation-name: reboundFast;
```

#### `animation-duration`

This property determines the duration of one animation cycle. Set in seconds with `s` or milliseconds with `ms`. If an element has more than one animation specified, then you can set a different time for each, listing the values ​​separated by commas.

```css
animation-duration: 1s;
animation-duration: 200ms;
animation-duration: .25s, 2s;
```

#### `animation-timing-function`

This property describes how an animation will develop between each pair of keyframes.

The property accepts the following value:

* linear
* ease
* ease-in
* ease-out
* ease-in-out
* cubic-bezier
* step-start
* step-end
* steps

```css
animation-timing-function: linear;
animation-timing-function: ease;
animation-timing-function: cubic-bezier(.2, .5, .3, .9);
animation-timing-function: steps(3, end);
```

{% codepen https://codepen.io/alexandrshy/pen/gOPGPYj %}

#### `animation-delay`

This property is determined when the animation begins. Set in seconds with `s` or milliseconds with `ms`.

```css
animation-delay: .25s;
animation-delay: 1s, 100ms;
```

#### `animation-iteration-count`

This property indicates how many times the animation loop is played; this is `1` by default. The value of `infinite` indicates that the animation will play repeat forever.

```css
animation-iteration-count: infinite;
animation-iteration-count: 2;
```

#### `animation-direction`

This property indicates whether the animation should play forward, backward, or alternately forward and backward. 
Available property values:

* `normal` - This is the default value and the animation played as normal (forwards)
* `reverse ` - The animation is played backward
* `alternate` - The animation reverses direction each cycle
* `alternate-reverse` - The animation reverses direction each cycle, with the first iteration being played backward

```css
animation-direction: normal;
animation-direction: reverse;
animation-direction: alternate;
animation-direction: alternate-reverse;
```

#### `animation-fill-mode`

This property defines how to apply styles to the animation object before and after it is executed. Available property values:

* `none` - Do not apply any styles to the animated element before or after the animation is executing
* `forwards` - Retain styles from the last keyframe
* `backwards` - Get styles from the first keyframe and retain this style during animation-delay state
* `both` - Extend animation properties in both directions forwards and backwards

```css
animation-fill-mode: none;
animation-fill-mode: forwards;
animation-fill-mode: backwards;
animation-fill-mode: both;
animation-fill-mode: none, backwards;
```

#### `animation-play-state`

This property determines whether the animation will start or pause. Stopping animation inside a loop is possible using JavaScript. You can also stop the animation if the state is `hover`. Available property values:

* `running` - The animation is currently playing. Default value
* `paused` - The animation is currently paused

```css
animation-play-state: running;
animation-play-state: paused;
```

## Implement animation support

Now we implement animation support in the library.

Let's start by adding a new `hasAnimation` property. It'll take a `boolean` value that is `false` by default. In order to add an animation of the "appearance" of a modal window, we need to add a new class at the time of its opening that will contain a property that describes the animation. It seems that we can use the previously written `open` method. But we need this class for the duration of the animation and after the animation is complete, it should be deleted. All this logic will be written in a separate `preparationOpeningModal` method

```js
/**
 * Preparing a modal window for opening
 */
preparationOpeningModal() {
    if (this.hasAnimation) {
        this.$modal?.classList.add(CLASS_NAMES.IS_OPENING);
        const handler = () => {
            this.$modal?.classList.remove(CLASS_NAMES.IS_OPENING);
            this.$modal?.removeEventListener('animationend', handler);
        };
        this.$modal?.addEventListener('animationend', handler);
    }
}
```

If `hasAnimation` is false we don't need to do anything. The `animationend` event is fired when a CSS animation has completed. In our case, after the modal window is opened, the class name `.isOpening` is added and the `animationend` event is subscribed. After the animation is complete, we delete the subscription and the `.isOpening` class name.

Styles for an opening modal window:

```css
:root {
  --animation: cubic-bezier(0.66, 0.28, 0.09, 0.53);
}

.modal.isOpening {
  animation: fadeIn .35s var(--animation);
}

.modal.isOpening .modal__container {
  animation: downUp .35s var(--animation);
}

@keyframes downUp {
  0% {
    transform: translateY(8%);
  }

  100% {
    transform: translateY(0);
  }
}

@keyframes fadeIn {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}
```

And here is our result. A modal window that appears smoothly

![Animated modal window](https://dev-to-uploads.s3.amazonaws.com/i/t99w9vbgzn2rigxfq0nd.gif)

But closing this window is still fast, since we haven't added a separate method for this. Let's fix this

```js
/**
 * Preparing a modal window for closing
 */
preparationClosingModal() {
    if (this.hasAnimation) {
        this.$modal?.classList.add(CLASS_NAMES.IS_CLOSING);
        const handler = () => {
            this.$modal?.classList.remove(CLASS_NAMES.IS_CLOSING);
            this.$modal?.classList.remove(this.openClass);
            this.$modal?.removeEventListener('animationend', handler);
        };
        this.$modal?.addEventListener('animationend', handler);
    } else {
        this.$modal?.classList.remove(this.openClass);
    }
}
```

As you can see, this is very similar to what we did in `preparationOpeningModal`, but there is still a significant difference. Deleting the main class should only occur after the animation is complete (if there is one), so we will transfer this part of the code from the `close` method to the `preparationClosingModal` method. For the animation when closing, we'll use the class name `.isClosing`.

Add styles:

```css
.modal.isClosing {
  animation: fadeOut .35s var(--animation);
}

.modal.isClosing .modal__container {
  animation: centerUp .35s var(--animation);
}

@keyframes centerUp {
  0% {
    transform: translateY(0);
  }

  100% {
    transform: translateY(-8%);
  }
}

@keyframes fadeOut {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}
```

Now we have animation in two directions

![Animated modal window](https://dev-to-uploads.s3.amazonaws.com/i/2xbuk7uu2923y0tdizpu.gif)

___

Thanks for reading! Next time we'll add a few callbacks and add a small implementation for keyboard control. Also, very soon I'll return to what I love endlessly - creating templates, and these will be ready-made templates for modal Windows that you can immediately use with the library. Subscribe, it'll be interesting! See you soon 👋