Motion: [QUESTION] Custom Interpolation Migration from Pose

Created on 20 Sep 2019  路  3Comments  路  Source: framer/motion

Issue

Animating transitions between path d attributes cause Complex Values errors.

In react-pose, we discussed the approach for this using custom interpolation via d3-interpolate. Reference: https://github.com/Popmotion/popmotion/issues/645

Question

How would one convert something like the following to motion?

import posed from 'react-pose';
import { spring } from 'popmotion';
import { interpolate } from 'd3-interpolate';

// Handle complex transitions with d3-interpolate
export const d3Interpolate = ({ from, to }) =>
  spring({
    from: 0,
    to: 1,
    velocity: 10,
    damping: 100
  }).pipe(interpolate(from, to));

// Common Transition Maps
export const transition = {
  d: d3Interpolate,
  transform: d3Interpolate
};

// Simple Example of Path Transition
export const PosedPath = posed.path({
  enter: {
    d: ({ enterProps }) => enterProps.d
    transition
  },
  exit: {
    d: ({ exitProps }) => exitProps.d
  }

Demo

You can see the pose implementation live in reaviz:

bug

All 3 comments

I think I might have a solution, would love to get feedback though...

import React, { useEffect } from 'react';
import { motion, useMotionValue, useSpring } from 'framer-motion';
import { interpolate } from 'd3-interpolate';

const variants = {
  initial: ({ exitProps }) => exitProps,
  animate: ({ enterProps }) => enterProps
};

export const MotionPath = ({ custom, ...rest }) => {
  const d = useMotionValue(custom.exitProps.d);
  const prevPath = useMotionValue(custom.exitProps.d);
  const spring = useSpring(prevPath, {
    from: 0,
    to: 1,
    velocity: 10,
    damping: 50
  });

  useEffect(() => {
    const interpolator = interpolate(prevPath.get(), custom.enterProps.d);
    spring.onChange(v => d.set(interpolator(v)));
    prevPath.set(custom.enterProps.d);
  });

  return (
    <motion.path
      {...rest}
      initial="initial"
      exit="initial"
      animate="animate"
      variants={variants}
      custom={custom}
      d={d}
    />
  );
};

and then I use it like:

<MotionLine
  {...extras}
  pointerEvents="none"
  stroke={stroke}
  strokeWidth={strokeWidth}
  fill="none"
  transition={transition}
  custom={{
    enterProps,
    exitProps
  }}
/>

Here is the code I ended up with: https://github.com/jask-oss/reaviz/blob/master/src/common/Motion/MotionPath.tsx

Yeah I think that's the best approach for now, you can of course also use Popmotion to drive the animation. But I am thinking of better ways of accomplishing this in the meantime. I think this https://github.com/framer/motion/issues/353 would be a good start, potentially there's also room for attaching a custom interpolator to a MotionValue.

Custom interpolation using d3-interpolate-path was also something I needed. I got it working in a slightly different way (shown below) but it would be great to have the ability to pass it directly to components.

import { tween, ColdSubscription } from 'popmotion';
import { interpolatePath } from 'd3-interpolate-path';

const usePathMorph = (d: null | string) => {
  const ref = useRef<SVGPathElement>(null);
  const refTween = useRef<ColdSubscription>();

  useEffect(() => {
    if (ref.current) {
      const dAttrVal = ref.current.getAttribute('d');

      if (d && !dAttrVal) {
        ref.current.setAttribute('d', d);
      } else if (d && dAttrVal && d !== dAttrVal) {
        if (refTween.current) {
          refTween.current.stop();
        }

        refTween.current = tween()
          .pipe(interpolatePath(dAttrVal, d))
          .start((v: string) => {
            if (ref.current) {
              ref.current.setAttribute('d', v);
            }
          });
      }
    }
  }, [d]);

  return ref;
};

Then use it like.

const { d } = props;
const ref = usePathMorph(d);

<motion.path ref={ ref } />

There's probably an edge case in there somewhere.

Edit: Updated hook for interrupting tween

Was this page helpful?
0 / 5 - 0 ratings