Passive values

Sometimes we don’t want to explicitly define a state for a value, we might just want it to change whenever another value does.

For instance, we might want an element to disappear as it moves beyond certain boundaries:

For this, we can use passive values. In this tutorial we’ll see how to define them, and how to make them respond to changes in parent values too.

Defining a passive value

Open the this draggable example and replace the posed component config with this:

Box: posed.div({
  draggable: 'x'
});

The dragging motion of the element is locked to the x axis. We can actually lock movement to the diagonal by defining y as a passive value.

The passive syntax will become clearer in the future but for now they’re defined as tuples, like this:

const Box = posed.div({
  draggable: 'x',
  passive: {
    y: ['x', v => v]
  }
});

The first item in the tuple is the name of the value we want to link to. In this case, that’s 'x'.

The second item is a mapping function. This takes the output of the linked value and returns our passive value. In this example, we’re simply returning x, and creating this motion:

By using this mapping function we can start creating new effects. For instance, returning the negative of x creates diagonal movement in the opposite direction:

y: ['x', v => -v]

Or by using Math.sin we can make wavey behaviour:

y: ['x', v => v * Math.sin(v * 0.01)]

Changing non-numerical values

So far, we’ve mapped two pixel values. But we can set any kind of value with any other.

Long-time users of Popmotion Pure will recognise the signature of the mapping function. It accepts one value, and returns another. Which means we can compose this function using Popmotion’s transformers.

For instance, instead of y let’s create a function that will map x to backgroundColor.

For this we’ll need to import four functions from popmotion.transform:

import transform from 'popmotion';
const { pipe, clamp, interpolate, blendColor } = transform;

Our steps will be:

1) Convert the output of x from pixels to a 0 to 1 range 2) Clamp that output to within 0 and 1 3) Use that progress number to blend between two colors

Which means our function will look like this:

backgroundColor: ['x', pipe(
  interpolate([-200, 200], [0, 1]),
  clamp(0, 1),
  blendColor('#FF1C68', '#198FE3')
)]

Linking to ancestors

We can also link a passive value to a value in one of the poser’s ancestors.

Let’s revist our sidebar example from earlier.

Currently, we’re actively animating the children by setting poses on both the parent and the children.

But, it’s possible to change the opacity of the items as the x of their sidebar parent changes.

To do this, we pass true as the third and final argument of the tuple. true says “link to my parent”.

Add a slower transition to Sidebar‘s 'visible' pose to help us see this in effect.

transition: { duration: 1000 }

Now, import pipe and interpolate from 'popmotion':

import { transform } from 'popmotion';
const { pipe, interpolate } = transform;

And replace Item‘s config with this:

Item: posed.li({
  passive: {
    opacity: ['x', pipe(
      parseFloat,
      interpolate([-100, 0], [0, 1])
    ), true]
  }
});

As you can see, we’re passing in a third parameter to the passive tuple, true. This says “listen to the x value, but do so on my immediate parent”.

Distant ancestors

Using true is fine if we want to look just one part up the ancestor chain. But it’s also possible to go much further up using label.

By explicitly naming our posers with a label, we can refer to any poser in the ancestor chain.

Add the label 'sidebar' to our Sidebar config:

Sidebar: posed.ul({
  label: 'sidebar',
  /* other props */
});

Now replace true in the Item config with 'sidebar'. It still works, and it will still work if you decide to put a different posed component between Sidebar and Item.