0.4.1 • Published 4 years ago

light-trails-inspector v0.4.1

Weekly downloads
2
License
MIT
Repository
github
Last release
4 years ago

Light trails

TypeScript Prettier bundle size min zip bundle size npm npm CI

The extendable animation library with timeline inspector.

yarn add light-trails
# or
npm install light-trails

Features

  • Highly extendable with a lot of small parts which you can replace
  • Advanced animation inspector as a separate package
  • Easy composition even for completely different renderers
  • Timeline based so you can control the animation using seek(t)
  • Written in TypeScript so it's come with pretty good typings
  • Rock-solid declarative way of doing animations
  • Build for large animation sets - previously as part of Phenomenon web slides engine, now it's a stand-alone package

TOC

Example

CodeSandbox Demo

import { lightTrails, trail, fromTo, delay, val, color, parallel } from 'light-trails'

const bodyTrail = trail('body', [
  fromTo({
    backgroundColor: color('#FF0000', '#00FF00')
  }, 1500)
])

const titleTrail = trail('#title', [
  delay(500),
  fromTo({
    y: val(0, 50, 'px'),
    opacity: val(0, 1),
    rotate: val(0, 360, 'deg'),
  }, 500)
])

const animation = lightTrails(
  parallel([
    bodyTrail,
    titleTrail,
  ])
)

animation.play()

Inspector

First you have to install inspector:

yarn add --dev light-trails-inspector
# or
npm install --save-dev light-trails-inspector

And just put lightTrails instance into inspector function:

import { inspector } from 'light-trails-inspector'

// ...

const animation = lightTrails(/* … */)

inspector(animation)

Documentation

Trails

To make an animation, first of all, we have to start with trail() function. Under this name, you can find a combination of renderer (by default it's a HTML/CSS renderer) and set of operators which describes animation steps.

There is a example of fade in trail:

const fadeInTrail = trail('h1', [
  fromTo({ opacity: val(0, 1) }, 400)
])

You are probably wondering what fromTo() or val() are. Let me explain what's going on.

The fromTo is one of the operators which takes an object with functions that can return a value in a specific time. Parameter n here is the percentage value from 0 to 1:

fromTo({ opacity: (n) => … }, 400)

For example, if the animation will be at 200ms this function will be cal with n = 0.5. It depends on how you want to change, you can make your own value function or use one of build-in. The most common in are val(0, 100, 'px') and color('#FFF', '#000').

There are more operators which can work together, for example set() when you want to change value immediately or delay():

const fadeInTrail = trail('h1', [
  set({ display: ['none', 'block']}),
  delay(1000),
  fromTo({ opacity: val(0, 1) }, 400),
])

Controller

Previously mentioned trail function carries only information about animation steps for the specific element. To run our animation we have to have a controller named lightTrails in this lib:

const fadeInTrail = trail('h1', [
  set({ display: ['none', 'block']}),
  delay(1000),
  fromTo({ opacity: val(0, 1) }, 400)
])

const animation = lightTrails(fadeInTrail)

animation.play()

The plural in the name reveals one of the key features of this library. And you guessed it. It's not about one trail, it's about composition.

Composition

To run related trails together you can use one of many functions, the simplest ones are sequence and parallel which I think are self-explanatory.

Let's use them in some example: We have two different trails, one for body background color and a second one for the h1 fade-in animation:

const bodyTrail = trail('body', [
  fromTo({ backgroundColor: color('#000', '#FFF') }, 1400)
])

const fadeInTrail = trail('h1', [
  set({ display: ['none', 'block']}),
  delay(1000),
  fromTo({ opacity: val(0, 1) }, 400)
])

Assume we want to run them one after the other, let's use sequence function.

const combinedTrails = sequence([bodyTrail, fadeInTrail])

Now we have a single trail so we can finally run our trail using a controller as it was before:

const animation = lightTrails(combinedTrails)

animation.play()

Composition functions have exactly the same type as a trail, so you can combine them together eg parallel inside a sequence.

const combinedTrails = sequence([
  bodyTrail,
  parallel([
    topTrail,
    contentTrail,
    footerTrail,
  ])
])

API

lightTrails(trail, [options])

const animation = lightTrails(trail, {
  onPlay() {
    // animation starts playing
  },
  onComplete() {
    // animation is completed
  },
  onPause() {
    // animation paused by `animation.pause()` or `pause()` operator
  },
  onUpdate() {
    // triggered on every frame
  }
})

Returns animation instance:

.play()

Starts the animation

animation.play()

.pause()

Pauses the animation

animation.pause()

.seek(t: number)

Moves the animation head to a specific point in time

animation.seek(200)

.prepare()

Prepares the animation by assigning initial values, it is useful when you do not immediately play the animation.

animation.prepare()

.getStatus()

// Returns object with current animation status
animation.getStatus()
{
  playing: boolean
  ended: boolean
  started: boolean
  currentTime: number
  currentTimeIndex: number
  total: number
}

Trail

Combination of renderer and operators array, string or HTMLElement as render will be changed to build-in HTML/CSS renderer.

trail(renderer: string | HTMLElement | Function, operators: Array)

Example:

const myTrail1 = trail('#my', [ … ])
const myTrail2 = trail(document.getElementById('my'), [ … ])
const myTrail3 = trail(document.body, [ … ])

The second argument is an array of operators, for example:

const myTrail = trail('#my', [
  set({ display: ['none', 'block']}),
  fromTo({ opacity: val(0, 1) }, 1000),
])

lightTrails(myTrail).play()

For more information, see the "Operators" section.

Custom renderer

It's possible to make your own renderer function. Let's assume that we want to "render" values as object. This may be useful for libraries like ThreeJS.

Full example:

const position = { x: 0 }

const myTrail = trail(
  (values) => {
    position.x = values.posX;
  },
  [
    fromTo({ posX: val(0, 100) }, 1000),
  ]
)

lightTrails(myTrail).seek(500)

console.log(position.x) // → 50

Composition

Joining trails together.

parallel(trails: Array)

Stack trails to run at the same time

const bodyTrail = trail('body', [ … ])
const fadeInTrail = trail('h1', [ … ])

// → aaaa
// → bbbb
// → cccc
const combinedTrails = parallel([bodyTrail, fadeInTrail, …])

lightTrails(combinedTrails).play()

sequence(trails: Array)

Stack trails one after the other

// → aaaa
// →     bbbb
// →         cccc
const combinedTrails = sequence([bodyTrail, fadeInTrail, …])

cascade(trails: Array, options)

Cascade with offset based on the index

// → aaaa
// →   bbbb
// →     bbbb
const combinedTrails = cascade([bodyTrail, fadeInTrail, …], {
  offset: (i) => i * 300
})

Operators

fromTo(values: Object, duration: number, easing: Function)

Smoothly changes values within a specified time.

Values object with functions which receive the percentage value from 0 to 1

{ valueName: (n: number) => any }

Example:

fromTo({
  height: val(0, 100, 'px'),
  width: (n) => Math.sin(n) * 100 + 'px',
})

set(values: Object)

Sets a specific value depends on where animation head is.

Values object with 2 item array (tuple)

{ valueName: [any, any] }

Example:

set({
  display: ['none', 'block'],
  height: [0, 'auto']
})

delay(duration: number)

Delays next operators by given time

Example:

const elTrail = trail(el, [
  delay(200),
  fromTo({ opacity: val(1, 0) }, 200),
  delay(100),
  set({ display: ['block', 'none'] })
])

pause()

Pauses animation in specific place:

const elTrail = trail(el, [
  fromTo({ opacity: val(0, 1) }, 200),
  pause(),
  fromTo({ opacity: val(1, 0) }, 200),
])

Values

"Values" are small functions built specifically for fromTo operator. Basically it is a linear interpolation between values so you can use them wherever you like.

val(a: number, b: number, suffix?: string)

const opacity = val(0.2, 0.8)
const size = val(100, 200, 'px')

opacity(0) // 0.2
size(0.5) // '150px'

Example:

const size = val(100, 200, 'px')

const elTrail = trail(el, [
  fromTo({
    opacity: val(0.2, 0.8),
    width: size,
    height: size,
  }, 200),
])

color(a: string, b: string)

Same thing as val but for colors.

const color1 = color('#FFF', '#000')
const color2 = color('rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 1)')

color1(0.5) // 'rgba(180, 180, 180, 1)'
color2(0.5) // 'rgba(0, 0, 0, 0.5)'

Example:

const elTrail = trail(el, [
  fromTo({
    color: color('#FFF', '#000'),
    backgroundColor:  color('rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 1)'),
  }, 200),
])

Value chaining

valChain(a: number, suffix?: string)
colorChain(a: string)

Sometimes you have to change the same value multiple times starting from the previous value, for example:

const elTrail = trail(el, [
  fromTo({
    color: color('#FFF', '#000'),
    height: val(0, 50, 'px'),
  }, 200),
  fromTo({
    color: color('#000', '#ABC123'),
    height: val(50, 100, 'px'),
  }, 200),
])

Look at the same example but using valChain or colorChain can be useful in this case:

const elColor = colorChain('#FFF')  // First, define starting values
const elHeight = valChain(0, 'px')

const elTrail = trail(el, [
  fromTo({
    color: elColor('#000'), // Put the next value
    height: elHeight(50),
  }, 200),
  fromTo({
    color: elColor('#ABC123'), // And so on
    height: elHeight(100),
  }, 200),
])

FAQ

How to loop animation?

There is no built-in option for that but you can play the animation again after completion, for example:

const bodyTrail = trail('body', [
  fromTo({ backgroundColor: color('#000', '#FFF') }, 1000)
])

const animation = lightTrails(bodyTrail, {
  onComplete() {
    animation.play()
  }
})

animation.play()

Can I use this library with React?

Yep, simply use useRef and useEffect hooks:

const MyComponent = () => {
  const ref = useRef(null)

  useEffect(() => {
    const elTrail = trail(ref.current, [
      fromTo({ opacity: val(0, 1) }, 1000)
    ])

    const animation = lightTrails(elTrail)
    animation.play()

    return () => animation.pause()
  }, [])

  return <div ref={ref}>Test</div>
}