Forces

Like Middleware, Forces allow us to define custom functions that apply to a tween.

The previous lesson stated that Middleware is applied at the Timeline level, and converts non-tweenable values (stings etc.) to tweenable values (numbers).

Forces differ in that they are applied at the Shape level. Their role is to adjust the value of a Shape's points and/or attributes post-tween.

Why might we want to adjust a Shape's values?

The main use case for a Force is to apply an "external force" to a Shape. An external force could be a motion path, rotation or physics.

Let's take the case of a motion path. Normally, when we animate a Shape from position A to position B, the movement between the two positions will occur in a straight line. To move the Shape along a motion path instead, we can use a Force. This Force would offset the Shape depending on its position in the tween.

Create a force

A Force is just a function that is called on each tick of a tween. The function receives two arguments.

  1. A Frame Shape – the data required to render a Shape. A Frame Shape is calculated by tweening between two Keyframes. It takes the form of an object that can contain three properties.
    1. points – shape data as defined by Points.
    2. attributes – an object of additional Shape attributes (eg. fill).
    3. childFrameShape – an array of child Frame Shapes.
  2. A Frame Position – a number (between 0 and 1 inc.) representing the Shape's current progress between two Keyframes.

A Force must return a Frame Shape.

As an example, let's create a Force called shakeForce, that randomly adjusts the x and y value of each point in a Shape.


import { shape, timeline, render, play } from 'wilderness'

const randomlyAdjustPoint = point => ({
  ...point,
  x: point.x + Math.floor(Math.random() * 10) - 5,
  y: point.y + Math.floor(Math.random() * 10) - 5
})

const shakeForce = ({ points, attributes, childFrameShapes }, framePosition) => ({
  points: points.map(randomlyAdjustPoint),
  attributes,
  childFrameShapes
})

const plainShapeObject = {
  type: 'rect',
  x: 5,
  y: 5,
  width: 10,
  height: 10,
  fill: '#1F9FFD'
}

const keyframe1 = plainShapeObject

const keyframe2 = {
  ...plainShapeObject,
  transforms: [[ 'offset', 80 ]],
  forces: [ shakeForce ]
}

const shakyRect = shape(keyframe1, keyframe2)

const animation = timeline(shakyRect, {
  duration: 2000,
  iterations: Infinity,
  alternate: true
})

render(document.querySelector('svg'), animation)

play(animation)
    

Wowzers. What a shaky little guy.

Notice in the example above that we add Forces to a Shape by defining a forces array on the Keyframe we are tweening to.

Force helpers

Wilderness has a couple of Force function helpers built in.