Frame Fusion

Animate sequences of images.

Just like animated GIFs or animated PNGs, but you can use higher quality images and have more control over the animation.

Find it on GitHub.


Motivation

Recently, I had a client that relies on the heavy use of animated GIFs in their website. They had used them for a most of their campaigns, but at one point, the quality of the GIFs became an issue because of the design and the style they were trying to achieve. The GIFs had a lot of soft shadows and gradients, which looked terrible when were converted to an animated GIF.

For this reason, I had to implement a solution that would allow me to animate the images in a sequence, and have the best of both worlds — the ability to use high quality images and the ability to have more control over the animation.

The solution I came up with worked pretty well, albeit it was complicated further with the need to work inside the carousel, which required the animation to be reinitialized on the slides where it appears, as the carousel plugin was copying the images and was losing the original references.

That’s why I decided to create a new library that would allow me to animate the images in a sequence, and expand from there to support other use cases, such as stepping through the images one by one, stopping and pausing the animation, and more.

Installation

Install the library.

npm install @chameleonmind/frame-fusion

Frame Fusion comes with a small CSS file that you can include in your project, you can import it with the following code.

import '@chameleonmind/frame-fusion/styles/main.css'

Styles

You can totally skip the default styles if you would like to use the library with your own styles.

The original CSS file is using the data attributes to identify the elements and apply the styles, but you can opt to write your own classes and styles.

You can provide your own active and poster hidden classes in options, check the options section below.

Here's the complete CSS that's included in the library, so you can use it as a reference.

/*
As the images are all absolutely positioned,
you need to set the container to relative positioning
*/
[data-ff-wrapper] {
  position: relative;
  overflow: hidden;
}

/*
You could replace this to target only the images,
could be more performant if you have a lot of frames
*/
[data-ff-wrapper] > * {
  display: block;
  width: 100%;
  height: auto;
  position: absolute;
  top: 0;
  left: 0;
  visibility: hidden;
}

/*
This is the poster image that is shown before the animation starts
*/
[data-ff-wrapper] > [data-ff-poster] {
  visibility: visible;
}

/*
This is the active image that is shown during the animation.
Note that we are using the visibility property to show the image.
Avoid using display none, as it will not load the image,
and also this way you can use the transitions if you want.
*/
[data-ff-wrapper] > [data-ff-active] {
  visibility: visible;
}

/*
This data-ff-hidden is used to hide the images that are not active, for example, the poster image
*/
[data-ff-wrapper] > [data-ff-hidden] {
  visibility: hidden;
}

Usage

If you have a sequence of images, place them inside a container with the data-ff-wrapper attribute.

In this example, we have 6 images in a container with the data-ff-wrapper attribute, an id which we will use to initialize the animation, and a height of 400px, just to avoid the additional CSS.

<div id="animatedFrames" data-ff-wrapper style="height: 400px">
  <img src="https://placehold.co/600x400?text=1" alt="Frame 1" />
  <img src="https://placehold.co/600x400?text=2" alt="Frame 2" />
  <img src="https://placehold.co/600x400?text=3" alt="Frame 3" />
  <img src="https://placehold.co/600x400?text=4" alt="Frame 4" />
  <img src="https://placehold.co/600x400?text=5" alt="Frame 5" />
  <img src="https://placehold.co/600x400?text=6" alt="Frame 6" />
</div>

Then initialize the animation with the sequenceAnimation function.

import { sequenceAnimation } from '@chameleonmind/frame-fusion'

const animation = sequenceAnimation('#animatedFrames', {
  autoplay: true,
  delay: 50,
})

The autoplay option is set to true so the animation will start automatically when the page and images are loaded.

If you're using the provided CSS, you'll notice that all the images are absolutely positioned and hidden by default. They are hidden using the visibility property, so they are not visible on the page, but they will be loaded and taken into account when the page renders.

Be careful not to change the CSS for the image, and add z-index or transitions to it, because it can cause unexpected behavior.

Actually, you can play with the transitions if you're using this lib to just cycle through images, it could create some interesting effects.

Options

You can pass an options object to the sequenceAnimation function, for example:

import { sequenceAnimation } from '@chameleonmind/frame-fusion'

const animation = sequenceAnimation('#animatedFrames', {
  // options here
})
OptionTypeDefaultDescription
autoplaybooleanfalseStart the animation automatically when the page and images are loaded.
delaynumberundefinedDelay in milliseconds between each frame.
frameratenumber24Number of frames per second.
fillModestringforwardsThe fill mode of the animation, on which frame it should stop. Can be forwards (stops on the last frame), backwards (stops on the beginning frame) or poster (stops on poster image).
directionstringnormalThe direction of the animation. Can be normal, reverse, alternate or alternate-reverse.
repeatnumberundefinedThe number of times the animation should repeat.
visibleClassstringundefinedClass name to add to the visible frames. Use this if you want to provide your own CSS.
posterHiddenClassstringundefinedClass name to add to the poster image when it is hidden. Use this if you want to provide your own CSS.
keepFramesVisiblebooleanfalseKeep the frames visible for the previous frames. Use this if you want your animation to stack up all your frames. Works only with direction: normal for now.
selectorstringundefinedCSS selector to use to find the images. Useful if you have images that shouldn't be included in the animation, but are in the container element. Potentialy, it could be used for frames that are not necessarily images.
framesstring[]undefinedArray of image paths. If provided, the image elements will be created from the paths. You cannot apply any other attributes or classes to the images. The images will be appended to the main element.
posterstringundefinedPath to the poster image. If provided, the image will be created from the path. You cannot apply any other attributes or classes to the image. The image will be appended to the main element.

Callbacks

You can pass a callback function to the sequenceAnimation function, for example:

import { sequenceAnimation } from '@chameleonmind/frame-fusion'

const animation = sequenceAnimation('#animatedFrames', {
  // options here
  onChangeState: (state) => {
    console.log('state', state)
  },
})
OptionTypeArgsDescription
onLoadfunctionvoidCallback function that is called when the animation is loaded.
onEndfunctionvoidCallback function that is called when the animation ends.
onRepeatfunctionvoidCallback function that is called when the animation repeats.
onStopfunction{ currentIndex: number, nextFrameNumber: number, animationDirection: 'forward' | 'reverse' }Callback function that is called when the animation is stopped.
onPlayfunction{ currentIndex: number, nextFrameNumber: number, animationDirection: 'forward' | 'reverse' }Callback function that is called when the animation is played.
onPausefunction{ currentIndex: number, nextFrameNumber: number, animationDirection: 'forward' | 'reverse' }Callback function that is called when the animation is paused.
onChangeStatefunctionstate: 'play' | 'pause' | 'stop'Callback function that is called when the animation state changes.
onReversefunctionvoidCallback function that is called when the animation is reversed.

Methods

You can call the following methods on the animation object returned by the sequenceAnimation function. Other than goToFrame method, the methods don't take any arguments.

For example:

import { sequenceAnimation } from '@chameleonmind/frame-fusion'

const animation = sequenceAnimation('#animatedFrames', {
  // options here
})

animation.play()

// then you could have a button that calls
animation.stop()

// or a button that calls
animation.pause()

// or a button that calls
animation.nextFrame()

// or a button that calls
animation.goToFrame(3)
MethodDescription
playStarts the animation.
pausePauses the animation.
stopStops the animation.
nextFrameMoves to the next frame.
previousFrameMoves to the previous frame.
restartRestarts the animation.
goToFrameGo to a specific frame.

Examples