question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

animateTransform property does not work in RN iOS

See original GitHub issue

Only building an iOS app, so can’t talk for Android, but when the following is run, the svg appears but not the animation. I have tried importing the animateTransform property too, still not working.

  <Svg
    width="38px"
    height="38px"
    viewBox="0 0 38 38"
    xmlns="http://www.w3.org/2000/svg"
    stroke="blue"
    aria-label="spinner"
  >
    <G fill="none" fillRule="evenodd">
      <G transform="translate(1 1)" strokeWidth="2">
        <Circle strokeOpacity=".5" cx="18" cy="18" r="18" />
        <Path d="M36 18c0-9.94-8.06-18-18-18">
          <animateTransform
            attributeName="transform"
            type="rotate"
            from="0 18 18"
            to="360 18 18"
            dur="1s"
            repeatCount="indefinite"
          />
        </Path>
      </G>
    </G>
  </svg>

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:2
  • Comments:11 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
msandcommented, Jan 4, 2020

It’s relatively easy to write a function which translates the animate syntax to animated, e.g. like this: https://snack.expo.io/@msand/animatetransform

import * as React from 'react';
import { Animated, Easing, PanResponder, View } from 'react-native';
import { Svg, Circle, G, Path } from 'react-native-svg';
const AnimatedPath = Animated.createAnimatedComponent(Path);
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
const AnimatedSvg = Animated.createAnimatedComponent(Svg);
/*
  <Svg
    width="38px"
    height="38px"
    viewBox="0 0 38 38"
    xmlns="http://www.w3.org/2000/svg"
    stroke="blue"
    aria-label="spinner"
  >
    <G fill="none" fillRule="evenodd">
      <G transform="translate(1 1)" strokeWidth="2">
        <Circle strokeOpacity=".5" cx="18" cy="18" r="18" />
        <Path d="M36 18c0-9.94-8.06-18-18-18">
          <animateTransform
            attributeName="transform"
            type="rotate"
            from="0 18 18"
            to="360 18 18"
            dur="1s"
            repeatCount="indefinite"
          />
        </Path>
      </G>
    </G>
  </svg>
*/
function animateTransform({ type, from, to, dur, repeatCount }) {
  const duration = parseFloat(dur.slice(0, -1)) * 1000;
  const [fromAngle, fromCX, fromCY] = from.split(' ').map(Number);
  const [toAngle, toCX, toCY] = to.split(' ').map(Number);
  const t = new Animated.Value(0);
  const animateTransform = [
    Animated.timing(t, {
      duration,
      toValue: 1,
      useNativeDriver: true,
      easing: Easing.linear,
    }),
  ];
  const animation = Animated.loop(Animated.sequence(animateTransform), {
    iterations: -1,
  }).start();
  const rotateAngle = t.interpolate({
    inputRange: [0, 1],
    outputRange: [fromAngle + 'deg', toAngle + 'deg'],
  });
  const cx = t.interpolate({
    inputRange: [0, 1],
    outputRange: [fromCX, toCX],
  });
  const cy = t.interpolate({
    inputRange: [0, 1],
    outputRange: [fromCY, toCY],
  });
  const icx = t.interpolate({
    inputRange: [0, 1],
    outputRange: [-fromCX, -toCX],
  });
  const icy = t.interpolate({
    inputRange: [0, 1],
    outputRange: [-fromCY, -toCY],
  });
  const style = {
    transform: [
      { translateX: cx },
      { translateY: cy },
      { rotateZ: rotateAngle },
      { translateX: icx },
      { translateY: icy },
    ],
  };
  return { t, animation, style, rotateAngle, cx, cy, icx, icy };
}

export default () => {
  const { style } = animateTransform({
    type: 'rotate',
    from: '0 18 18',
    to: '360 18 18',
    dur: '1s',
    repeatCount: 'indefinite',
  });
  return (
    <View>
      <Svg
        width="100%"
        height="50%"
        viewBox="0 0 38 38"
        aria-label="spinner"
        fillRule="evenodd"
        stroke="blue"
        fill="none">
        <G transform="translate(1 1)" strokeWidth="2">
          <Circle strokeOpacity=".5" cx="18" cy="18" r="18" />
          <AnimatedPath d="M36 18c0-9.94-8.06-18-18-18" style={style} />
        </G>
      </Svg>
      <App2 />
    </View>
  );
};

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, splines };
}

function panHandler() {
  const x = new Animated.Value(0);
  const y = new Animated.Value(0);
  const dx = new Animated.Value(0);
  const dy = new Animated.Value(0);

  const panResponder = PanResponder.create({
    // Ask to be the responder:
    onStartShouldSetPanResponder: (evt, gestureState) => true,
    onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
    onMoveShouldSetPanResponder: (evt, gestureState) => true,
    onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,

    // The gesture has started. Show visual feedback so the user knows
    // what is happening!
    // gestureState.d{x,y} will be set to zero now
    onPanResponderGrant: (evt, gestureState) => {},

    // The most recent move distance is gestureState.move{X,Y}
    // The accumulated gesture distance since becoming responder is
    // gestureState.d{x,y}
    onPanResponderMove: Animated.event([
      null, // ignore the native event
      // extract dx and dy from gestureState
      // like 'pan.x = gestureState.dx, pan.y = gestureState.dy'
      { dx, dy },
    ]),

    onPanResponderTerminationRequest: (evt, gestureState) => true,
    // The user has released all touches while this view is the
    // responder. This typically means a gesture has succeeded
    onPanResponderRelease: (evt, gestureState) => {
      x.setValue(x._value + gestureState.dx);
      y.setValue(y._value + gestureState.dy);
      dx.setValue(0);
      dy.setValue(0);
    },
    // Another component has become the responder, so this gesture
    // should be cancelled
    onPanResponderTerminate: () => {},

    // Returns whether this component should block native components from becoming the JS
    // responder. Returns true by default. Is currently only supported on android.
    onShouldBlockNativeResponder: (evt, gestureState) => {
      return true;
    },
  });

  return {
    x,
    y,
    dx,
    dy,
    panResponder,
    translateX: Animated.add(x, dx),
    translateY: Animated.add(y, dy),
  };
}

export function App2() {
  /*
    <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();

  const { panResponder, translateX, translateY } = panHandler();

  return (
    <View {...panResponder.panHandlers}>
      <AnimatedSvg
        width="100%"
        height="100%"
        viewBox="0 0 32 32"
        style={{
          transform: [{ translateX }, { translateY }],
        }}>
        <AnimatedCircle cx="16" cy="16" r={value} />
      </AnimatedSvg>
    </View>
  );
}

1reaction
SaeedZhianycommented, Feb 16, 2020

I just test it on my android emulator, it seems ratateZ can not be animated by setting useNativeDriver and I’m getting a runtime error.

by the way, the generate animations going to be very slow when more SVG components exist on the screen. is it possible to animate the components using react-native-reanimated library? have you tried it before yourself and if yes how much the performance improved?

Read more comments on GitHub >

github_iconTop Results From Across the Web

animateTransform property does not work in RN iOS #1019
Only building an iOS app, so can't talk for Android, but when the following is run, the svg appears but not the animation....
Read more >
SVG AnimateTransform not working in Safari..? - Stack Overflow
The animation can be toggled on and off once, but then again fails. According to the specs, animateTransform should be supported in Safari...
Read more >
<animateTransform> - SVG: Scalable Vector Graphics | MDN
The animateTransform element animates a transformation attribute on its target element, thereby allowing animations to control translation, ...
Read more >
How to Use “animateTransform” for Inline SVG Animation
The short answer is, the animateTransform element generates animations by setting transform properties on the SVG shape to which it's ...
Read more >
Animations - React Native
Animations are very important to create a great user experience. ... Each property can be run through an interpolation first.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found