react-native-svg-transformer icon indicating copy to clipboard operation
react-native-svg-transformer copied to clipboard

svg with animate is not useful

Open jun58 opened this issue 5 years ago • 5 comments

I have a svg file some code like: <circle transform="translate(8 0)" cx="0" cy="16" r="10" fill="#fff"> <animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0" keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" /> </circle> but animate is not active

jun58 avatar Jul 10 '19 10:07 jun58

animate does not seem to be supported by react-native-svg: https://github.com/react-native-community/react-native-svg#supported-elements

kristerkari avatar Jul 10 '19 12:07 kristerkari

@jun58 I'd recommend using svgr to convert the content to react components, and then manually translate the animate element to use the react-native Animated api instead: https://facebook.github.io/react-native/docs/animated

msand avatar Aug 10 '19 12:08 msand

The Easing api should have everything you need: https://facebook.github.io/react-native/docs/easing There's also https://github.com/osdnk/react-native-spline-interpolate which is for a unrelated problem, but also utilizes splines, but rather generates them to fit data, than just get them as input and calculate the output. Essentially the same logic could be used, but it's possible to implement more efficiently using the primitives from the Easing api.

msand avatar Aug 10 '19 12:08 msand

Couldn't help myself 😄 https://snack.expo.io/@msand/animated-svg-with-bezier-spline-calcmode

import * as React from 'react';
import { Animated, Easing } from 'react-native';
import { Svg, Circle } from 'react-native-svg';
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
/*
  <circle cx="16" cy="16" r="16">
    <animate 
      attributeName="r" 
      values="0; 4; 0; 0" 
      dur="1.2s" 
      repeatCount="indefinite" 
      begin="0" 
      keytimes="0;0.2;0.7;1" 
      keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" 
      calcMode="spline" />
  </circle>
*/
const values = [0, 4, 0, 0];
const dur = 1.2 * 1000;
const keyTimes = [0, 0.2, 0.7, 1];
const keySplines = [
  [0.2, 0.2, 0.4, 0.8],
  [0.2, 0.6, 0.4, 0.8],
  [0.2, 0.6, 0.4, 0.8],
];
export default () => {
  const t = new Animated.Value(keyTimes[0]);
  const splines = keySplines.map((spline, i) => {
    const [x1, y1, x2, y2] = spline;
    const fromValue = keyTimes[i];
    const toValue = keyTimes[i + 1];
    return Animated.timing(t, {
      toValue,
      duration: dur * (toValue - fromValue),
      easing: Easing.bezier(x1, y1, x2, y2),
      useNativeDriver: true,
    });
  });
  Animated.loop(Animated.sequence(splines)).start();
  return (
    <Svg width="100%" height="100%" viewBox="0 0 32 32">
      <AnimatedCircle
        cx="16"
        cy="16"
        r={t.interpolate({
          inputRange: keyTimes,
          outputRange: values,
        })}
      />
    </Svg>
  );
};

msand avatar Aug 10 '19 14:08 msand

A bit refactored to make it more reusable:

import * as React from 'react';
import { Animated, Easing } from 'react-native';
import { Svg, Circle } from 'react-native-svg';
const AnimatedCircle = Animated.createAnimatedComponent(Circle);

function animateSpline({
  values,
  dur,
  repeatCount,
  begin,
  keyTimes,
  keySplines,
}) {
  const duration = dur * 1000;
  const t = new Animated.Value(keyTimes[0]);
  const splines = keySplines.map((spline, i) => {
    const [x1, y1, x2, y2] = spline;
    const fromValue = keyTimes[i];
    const toValue = keyTimes[i + 1];
    return Animated.timing(t, {
      toValue,
      delay: i == 0 ? begin : 0,
      duration: duration * (toValue - fromValue),
      easing: Easing.bezier(x1, y1, x2, y2),
      useNativeDriver: true,
    });
  });
  const iterations = repeatCount === 'indefinite' ? -1 : +repeatCount;
  const animation = Animated.loop(Animated.sequence(splines), { iterations });
  const value = t.interpolate({
    inputRange: keyTimes,
    outputRange: values,
  });
  return { t, animation, value };
}

export default () => {
  /*
    <circle cx="16" cy="16" r="16">
      <animate 
        attributeName="r" 
        values="0; 4; 0; 0" 
        dur="1.2s" 
        repeatCount="indefinite" 
        begin="0" 
        keytimes="0;0.2;0.7;1" 
        keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" 
        calcMode="spline" />
    </circle>
  */
  const { animation, value } = animateSpline({
    values: [0, 4, 0, 0],
    dur: 1.2,
    repeatCount: 'indefinite',
    begin: 0,
    keyTimes: [0, 0.2, 0.7, 1],
    keySplines: [
      [0.2, 0.2, 0.4, 0.8],
      [0.2, 0.6, 0.4, 0.8],
      [0.2, 0.6, 0.4, 0.8],
    ],
  });
  animation.start();
  return (
    <Svg width="100%" height="100%" viewBox="0 0 32 32">
      <AnimatedCircle cx="16" cy="16" r={value} />
    </Svg>
  );
};

msand avatar Aug 10 '19 15:08 msand