create-keyframe-animation
DefinitelyTyped icon, indicating that this package has TypeScript declarations provided by the separate @types/create-keyframe-animation package

0.1.0 • Public • Published

create-keyframe-animation

Generate CSS keyframe animations dynamically in the browser with JavaScript.

You describe them in JS and it generates and inserts a <style> in the document <head> with a CSS keyframe animation.

This lets you dynamically calculate values based on positions and lets you actually run the animations performantly without having to deal with or run requestAnimationFrame loops.

** caveat ** this is experimental and I'm not entirely sure this is a good idea, nor am I sure I actually want to maintain this lib :) But I'm sharing this here because I thought it was interesting and sharing it for educational purposes. If someone wants to take this and run with it ping me on twitter: @HenrikJoreteg.

DEMO TIME! Here's a fun thing I built with this tool: http://dot.surge.sh

I'll probably write a post about this approach, I think it has some merits. Largely because requestAnimationFrame loops eat up battery power on mobile devices if left running and it's nice that once you've registered an animation, actually runnning it happens off of the main JS thread. Plus, as I said, you don't have to write CSS with known values ahead of time.

so...

Normally CSS animations are declared in a separate CSS style sheet, like so:

@keyframes move {
  0% {
    transform: translate3d(0,0,0)
  }
  100% {
    transform:translate3d(1px,1px,0)
  }
}

This can be a bit tedious to write (even with preprocessors) and becomes difficult if you want to dynamically change those fixed pixel values when your app is running in the browser.

What I wanted to be able to do was this:

var animations = require('create-keyframe-animation')
 
// this creates the animation above
animations.registerAnimation({
  name: 'move',
  // the actual array of animation changes
  animation: [
    [0,0], 
    [1,1]
  ],
  // optional presets for when actually running the animation
  presets: {
    duration: 1000,
    easing: 'linear',
    delay: 500
  }
})
 
// then run it
animations.runAnimation(el, 'move', function () {
    // callback gets called when its done
})
 
// it also will return a promise if a `Promise` global exists letting you easily chain animations
animations.runAnimation(el, 'moveUp')
    .then(function () {
        return animations(el, 'wiggle')
    })
    .then(function () {
        return animations(el, 'jiggle')
    })
    .then(function () {
        return animations(el, 'shake')
    })
    .then(function () {
        console.log('done!')
    })
    .catch(function (err) {
        console.error(err)
    })
 

Or we can get fancy and animate other properties that are cheap to animate like, scale and opacity.

registerAnimation('moveit', [
    {
        translate: [0, 50],
        opacity: 0.5,
        scale: 2
    },
    {
        translate: [0, 50],
        opacity: 0.5,
        scale: 2
    },
    ...,
    ...
])

Or get extra fancy...

The following is an excerpt from the code in the demo: http://dot.surge.sh/

It creates an animation of two portions, using the element's current translated position as a starting point.

It drop it vertically from its current position then bounces and spin it so it lands on the bottom left corner of the screen.

This is the code used to generate the animation for each of the 5 dots when you hit the "X" to bring the back to the stating position in this demo: http://dot.surge.sh/

import getPosition from 'get-css-translated-position'
import animations from 'create-keyframe-animation'
import adaptiveQuadratic from 'adaptive-quadratic-curve'
import ww from 'window-watcher'
 
 
export default function (el, name) {
  const {x, y} = getPosition(el, {includePageOffset: true})
  const distanceToDrop = ww.height - y
 
  // calculate dynamic bounce in animation
  var fullWidth = ww.width + 50
 
  var start = [x, y]
  var c1 = [x, distanceToDrop]
  var middle = [x, ww.height - 50]
  var c2 = [fullWidth * 0.22, (distanceToDrop * 0.75)]
  var end = [20, ww.height - 70]
 
  var points = []
  adaptiveQuadratic(start, c1, middle, 50, points)
  adaptiveQuadratic(middle, c2, end, 50, points)
 
  var middle = Math.round(points.length / 2)
 
  points = points.map(function (point, i) {
    var rotate = 0
    if (i) {
      rotate = (-360 * (/ points.length))
    }
    if (=== (points.length - 1)) {
      rotate = -360
    }
    return {
      translate: point,
      rotate: rotate
    }
  })
 
  animations.registerAnimation({
    name: name,
    animation: points,
    presets: {
      duration: 1000,
      easing: 'linear',
      delay: 500
    }
  })
}

install

npm install create-keyframe-animation

basic API docs

.registerAnimation(opts)

options and defaults below

opts.name string (required)

The name of the animation. This is what you use when you run it.

opts.animation (object or array)

If you give it an object, they key is the keyframe so all the following are valid and identical:

animations.registerAnimation({
    name: 'move',
    animation: {
        start: [0, 0],
        end: [1, 1]
    }
}
 
animations.registerAnimation({
    name: 'move',
    animation: {
        0: [0, 0],
        100: [1, 1]
    }
}
 
animations.registerAnimation({
    name: 'move',
    animation: {
        '0%': [0, 0],
        '100%': [1, 1]
    }
}
 
animations.registerAnimation({
    name: 'move',
    // when you give it an array the keyframe percentages are calculated
    // linearly based on index
    animation: [[0, 0], [1, 1]]
}

The positional args are always assumed to be pixels and will be applied using transform: translate3d() for best performance.

You can also animate the other things that are cheap to animate.

animations.registerAnimation({
    name: 'moveFadeInGrow',
    animation: [
        {
            x: 0,
            y: 0,
            opacity: 0,
            scale: 0.5
        },
        {
            x: 1,
            y: 1,
            opacity: 0.8,
            scale: 2
        }
    ]
}

Other options and their defaults

{
  duration: 1000, // duration in milliseconds
  fillMode: 'both', // css animation fill mode property
  easing: 'ease', // default easing
  iterations: 1, // default number of iterations
  delay: 0, // delay in milliseconds
  direction: 'normal', // animation direction
  resetWhenDone: false, // if true will apply the final animation state as a `transform` property
  clearTransformsBeforeStart: false // whether or not to clear any existing transforms before animation starts
}

.runAnimation(element(s), name string or options object, [callback])

Run animation with the name specified on the nodes you pass in.

Returns a promise if: Promise exists on the window object and you don't pass a callback.

element (actual dom node or nodes to apply animation to)

This can be a single element, an array of elements or the result of querySelectorAll, jQuery should work too, but I don't use it so I haven't tested.

name or options object

If this is a string it's assumed to be the name of the animation to run.

If you set presets when you registered the animation, you can run them easily:

animations.runAnimation(document.querySelectorAll('.dots'), 'wigggle')

If you pass an object it has to contain the name:

animations.runAnimation(document.querySelectorAll('.dots'), {
    name: 'wiggle',
    delay: 1500 // here we can override any of the preset options as described above
}, function () {
    console.log('done!')
})

callback

If you pass a callback it will get called when the animationend or the browser specific equivalent is called.

credits

If you like this follow @HenrikJoreteg on twitter.

license

MIT

Readme

Keywords

none

Package Sidebar

Install

npm i create-keyframe-animation

Weekly Downloads

72

Version

0.1.0

License

MIT

Last publish

Collaborators

  • henrikjoreteg