software-mansion / react-native-reanimated

React Native's Animated library reimplemented
https://docs.swmansion.com/react-native-reanimated/
MIT License
8.99k stars 1.3k forks source link

Using custom interpolate function #1302

Closed sbycrosz closed 3 years ago

sbycrosz commented 4 years ago

Description

Hi, I wanted to use d3-interpolate-path with reanimated-v2 to make animated charts

chart-animation

At the moment, I'm kind of stuck when calling the interpolate function from useAnimatedProps, which I suspect is because d3-interpolate-path is being run on RN thread instead of the UI thread. Is this correct? Is there a way to have external functions run on UI thread instead?

Thanks!

Code

https://github.com/sbycrosz/reanimated-2-playground/blob/master/Screen.js#L27

import * as Svg from 'react-native-svg';

import Animated, {
  Easing,
  useAnimatedProps,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
import {Button, View} from 'react-native';

import React, { useState } from 'react';
import { interpolatePath } from 'd3-interpolate-path';

const PATH1 = 'M0,300 L100,100 L200,200 L300,100 L400,140';
const PATH2 = 'M0,300 L100,150 L200,100 L300,200 L400,240';

const interpolator = interpolatePath(PATH1, PATH2);

const AnimatedSvgPath = Animated.createAnimatedComponent(Svg.Path);

export default function AnimatedStyleUpdateExample(props) {
  const progress = useSharedValue(0);

  const animatedProps = useAnimatedProps(() => {
    const path = interpolator(progress.value); // <<<<  path will return undefined here (because it's run on the main thread?)
    return { d: path };
  });

  return (
    <View style={{ flex: 1, alignItems: 'center' }}>
      <Svg.Svg
        style={{ borderWidth: 1 }}
        height={300}
        width={400}>
        <AnimatedSvgPath
          animatedProps={animatedProps}
          strokeWidth={1}
          stroke={'#000'} />
      </Svg.Svg>

      <Button
        title="toggle-animated"
        onPress={() => {
          progress.value = withTiming(progress.value ? 0 : 1, {
            duration: 1000,
            easing: Easing.inOut(Easing.cubic),
          })
        }} />
    </View>
  );
}

Package versions

"react-native": "0.63.1",
"react-native-reanimated": "2.0.0-alpha.7",
"react-native-svg": "12.1.0",
"d3-interpolate-path": "^2.2.1",
terrysahaidak commented 4 years ago

Yes, it's because d3 is being run on JS Thread.

Right now there is no way to run external functions on UI Thread.

cc @kmagiera @Szymon20000

jakub-gonet commented 3 years ago

We're aware of this issue but for now, it's not possible to run an arbitrary function on the JS thread.

terrysahaidak commented 3 years ago

But it is possible to convert that to worklet. But yeah, it's hard.

arturz commented 3 years ago

@terrysahaidak Is it possible to easily convert my own pure function to worklet, that for example looks like that: fn = (x, y) => x * y Or should I have to entirely rewrite this function as a worklet?

(in my case I have a much bigger function that calculates props for <Line /> basing on x, y and length, but it is still a function without any side-effects)

haibert commented 3 years ago

Whats the correct way to morph from one SVG Path to another ? Do we need to use the d3-interpolate-path library or its possible to do it with the tools provided with reanimated its self ?

sbycrosz commented 3 years ago

Hi @haibert, I managed to add reanimated support to d3-interpolate-path, which you can check out here

Alternatively, on your package.json

    "d3-interpolate-path": "https://github.com/sbycrosz/d3-interpolate-path.git#feat/react-native-reanimated-support",
haibert commented 3 years ago

That is amazing. Im sorry but Im confused as to how we would actually implement the worklets? Using the code in this issue as an example makes my app crash, i'm assuming this is because the interpolation is not happening on the UI thread? Do we have to run the interpolation on the UI thread by using runOnUI? This is how the code looks right now.

import React, { useState } from 'react'
import { View } from 'react-native'
import Animated, {
    useSharedValue,
    useAnimatedProps,
    withTiming,
    Easing,
    runOnUI,
} from 'react-native-reanimated'
import Svg, { Path } from 'react-native-svg'

//d3-interpolator
import { interpolatePath } from 'd3-interpolate-path'

const AnimatedPath = Animated.createAnimatedComponent(Path)

Animated.addWhitelistedNativeProps({
    stroke: true,
})

const PATH1 = 'M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2V9z'
const PATH2 =
    'M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.501 5.501 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z'

const HomeSVG = ({ color, size }) => {
    const progress = useSharedValue(0)
    const interpolator = interpolatePath(PATH1, PATH2)

    const animatedProps = useAnimatedProps(() => {
        const path = runOnUI(interpolator)(progress.value)
        return { d: path }
    })

    return (
        <View
            onTouchStart={() => {
                progress.value = withTiming(progress.value ? 0 : 1, {
                    duration: 1000,
                    easing: Easing.inOut(Easing.cubic),
                })
            }}
        >
            <Svg width={size} height={size} viewBox="0 0 24 24">
                <AnimatedPath
                    stroke={color}
                    strokeWidth={2}
                    fill="none"
                    fillRule="evenodd"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    animatedProps={animatedProps}
                />
            </Svg>
        </View>
    )
}

export default HomeSVG
sbycrosz commented 3 years ago

Hi, you can check out my WIP branch. I haven't worked on it for a while but hopefully, it still works.

haibert commented 3 years ago

Hello, I just realized I was not installing your branch. After I install your branch and run I get the following error message

While trying to resolve module `d3-interpolate-path` from file `/Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/src/components/animatedNavBarTest/HomeSVG.js`, the package `/Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/node_modules/d3-interpolate-path/package.json` was successfully found. However, this package itself specifies a `main` module field that could not be resolved (`/Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/node_modules/d3-interpolate-path/build/d3-interpolate-path.js`. Indeed, none of these files exist:

  * /Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/node_modules/d3-interpolate-path/build/d3-interpolate-path.js(.native|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx|.ios.js|.native.js|.js|.ios.jsx|.native.jsx|.jsx|.ios.json|.native.json|.json)
  * /Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/node_modules/d3-interpolate-path/build/d3-interpolate-path.js/index(.native|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx|.ios.js|.native.js|.js|.ios.jsx|.native.jsx|.jsx|.ios.json|.native.json|.json)

InternalError Metro has encountered an error: While trying to resolve module `d3-interpolate-path` from file `/Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/src/components/animatedNavBarTest/HomeSVG.js`, the package `/Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/node_modules/d3-interpolate-path/package.json` was successfully found. However, this package itself specifies a `main` module field that could not be resolved (`/Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/node_modules/d3-interpolate-path/build/d3-interpolate-path.js`. Indeed, none of these files exist:

  * /Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/node_modules/d3-interpolate-path/build/d3-interpolate-path.js(.native|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx|.ios.js|.native.js|.js|.ios.jsx|.native.jsx|.jsx|.ios.json|.native.json|.json)
  * /Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/node_modules/d3-interpolate-path/build/d3-interpolate-path.js/index(.native|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx|.ios.js|.native.js|.js|.ios.jsx|.native.jsx|.jsx|.ios.json|.native.json|.json): /Users/hb/Desktop/EjectEventShare/1EventShare-dev-client/node_modules/metro/src/node-haste/DependencyGraph.js (376:17)

seems like theres a 'build/d3-interpolate-path.js' file missing.

sbycrosz commented 3 years ago

It's working fine on my machine. Try deleting your node_modules and then reinstalling

haibert commented 3 years ago

hmmm, still same error. im doing yarn add sbycrosz/d3-interpolate-path, do you see the build/d3-interpolate-path.js file ? im using expo im not sure if this makes a difference

sbycrosz commented 3 years ago

I have not tried Expo so I can't comment on that, but did you try my example repo?

Also, you need to add the specific branch of my fork. AFAIK yarn add sbycrosz/d3-interpolate-path will try to pull master by default