Coder Social home page Coder Social logo

moti's Introduction

The universal React Native animation library, powered by Reanimated 3.

<MotiView from={{ opacity: 0 }} animate={{ opacity: 1 }} />

Documentation & Examples

Next.js Conf

Screen Shot 2021-10-22 at 3 00 05 PM

I spoke at at Next.js Conf 2021 on October 26 about React Native + Next.js. Watch the video to see how we do it.

Highlights

  • Universal: works on all platforms
  • 60 FPS animations on the native thread
  • Mount/unmount animations, like framer-motion
  • Powered by Reanimated 3
  • Web support, out-of-the-box
  • Expo support
  • Intuitive API
  • Variants
  • Strong TypeScript support
  • Highly-configurable animations
  • Sequence animations
  • Loop & repeat animations

Preview

Follow

Follow me on Twitter to stay up to date.

Sponsor

Sponsorships via GitHub are appreciated.

Analytics by Splitbee.io

License

Moti has an MIT license. That said, a lot of free work goes into it, so if your company uses it, please sponsor, write a blog post, or tweet about it!

moti's People

Contributors

agrawal-rohit avatar alantoa avatar andrew-levy avatar archcorsair avatar bennettfrazier avatar carlos3g avatar cmaycumber avatar codinsonn avatar coletownsend avatar derek-primumco avatar efstathiosntonas avatar eveningkid avatar filipengberg avatar gabimoncha avatar hugo-chq avatar jstheoriginal avatar khzouroussama avatar la55u avatar llaver avatar louisholley avatar madsroskar avatar mlynchdev avatar nandorojo avatar redbar0n avatar tperich avatar vbylen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

moti's Issues

Changes to state not updating animation

Hi.
I'm following along with the documentation, and there's an example that reads:

<View animate={{ opacity: isLoading ? 1 : 0 }} />

I've got the following code which has a button that toggles a piece of state, and a box that should fade in / out โ€” the state is changing but the animation is not, neither on simulator nor device.

Am I doing this the correct way?

  • Bare Expo Workflow
  • "moti": "^0.5.7",
  • "react-native-reanimated": "^2.0.0-rc.3"
import React, { useState, useEffect } from 'react'
import { Text, Button } from 'react-native'
import styled from 'styled-components/native'
import tw from 'tailwind-rn'
import { View as MotiView } from 'moti'

import { SCREEN } from '@/utils/constants'

interface Props {
  isOpen: boolean
}

const VideoPlayer = (props: Props) => {
  const [isOpen, setOpen] = useState(false)

  return (
    <Container>
      <MotiView
        style={{
          height: 200,
          width: 200,
          backgroundColor: 'red',
          justifyContent: 'center',
          alignItems: 'center',
        }}
        animate={{ opacity: isOpen ? 0 : 1 }}
        transition={{
          delay: 100,
          type: 'timing',
          duration: 350,
        }}
      >
        <Text>Video Player</Text>
      </MotiView>
      <Button title="Animate Me" onPress={() => setOpen(true)} />
    </Container>
  )
}

const Container = styled(MotiView)`
  ${tw(`absolute bg-blue-100 justify-center items-center`)};
  width: ${SCREEN.W}px;
  height: ${SCREEN.H}px;
`

export default VideoPlayer

Animated Presence like solution for react navigation

Hi I was thinking, if this could already work or if not would be great to have this feature, so when we close a page or open it in a stack navigator for example the contents would animate.

Well the enter can already happen but the exit is the tricky part, the navigation should be prevented animation should run and then the prevented action should happen only after it.

I've already done this with the old react navigation and old reanimated 1.

But those are now depreciated.
What do you think please let me know.

'durationโ€˜ not working with type: 'spring' -> defaults to ~1000ms

Thanks for this great library! I'm very excited to see it grow.

-> see the title for issue - occurs both with vanilla RN and expo.
The animation works fine but duration only works with type: 'timing'

environment:

  • RN 0.63.2 / [email protected]
  • moti 0.6.0 and reanimated-2.0.0
  • (expo built with the starter project (npx create-react-native-app -t with-moti))
//...
import {View as MotiView} from 'moti';

export default function App() {
    const [visible, setVisible] = useState(true);

    return (
        <Pressable onPress={()=> setVisible(prev => !prev)}>
            <MotiView
                animate={{
                    rotate: visible ? '90deg' : '0deg',
                }}
                style={{height: 30, width: 30, backgroundColor: 'darkred'}}
                transition={{
                    duration: 100,
                    type: 'spring', //works with 'timing' 
                }}
            />
        </Pressable>
    );
}

Getting Invalid hook call on Barebones project

Using

"moti": "^0.10.1",
"react-native": "0.63.4",

and this is the code (or any other code I use moti components)

import {View} from 'moti';

const Profile = (props) => {
  return <View />;
};
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

This error is located at:
    in ForwardRef(Moti) (at Profile/index.js:16)
    in RCTSafeAreaView (at SafeAreaView.js:51)
    in ForwardRef(SafeAreaView) (at Profile/index.js:14)
    in Profile (at SceneView.tsx:122)
    in StaticContainer
    in StaticContainer (at SceneView.tsx:115)
    in EnsureSingleNavigator (at SceneView.tsx:114)
    in SceneView (at useDescriptors.tsx:153)
    in RCTView (at View.js:34)
    in View (at ResourceSavingScene.tsx:68)
    in RCTView (at View.js:34)
    in View (at ResourceSavingScene.tsx:63)
    in ResourceSavingScene (at DrawerView.tsx:183)
    in RCTView (at View.js:34)
    in View (at src/index.native.tsx:145)
    in ScreenContainer (at DrawerView.tsx:162)
    in RCTView (at View.js:34)
    in View (at Drawer.tsx:645)
    in RCTView (at View.js:34)
    in View (at createAnimatedComponent.js:441)
    in AnimatedComponent(View) (at createAnimatedComponent.js:452)
    in ForwardRef(AnimatedComponentWrapper) (at Drawer.tsx:638)
    in RCTView (at View.js:34)
    in View (at createAnimatedComponent.js:441)
    in AnimatedComponent(View) (at createAnimatedComponent.js:452)
    in ForwardRef(AnimatedComponentWrapper) (at Drawer.tsx:628)
    in PanGestureHandler (at GestureHandlerNative.tsx:13)
    in PanGestureHandler (at Drawer.tsx:619)
    in DrawerView (at DrawerView.tsx:215)
    in RNCSafeAreaProvider (at SafeAreaContext.tsx:76)
    in SafeAreaProvider (at SafeAreaProviderCompat.tsx:42)
    in SafeAreaProviderCompat (at DrawerView.tsx:213)
    in RCTView (at View.js:34)
    in View (at DrawerView.tsx:212)
    in DrawerView (at createDrawerNavigator.tsx:47)
    in DrawerNavigator (at navigation/index.js:52)
    in MainNavigator (at navigation/index.js:28)
    in Unknown (at App.js:24)
    in EnsureSingleNavigator (at BaseNavigationContainer.tsx:409)
    in ForwardRef(BaseNavigationContainer) (at NavigationContainer.tsx:91)
    in ThemeProvider (at NavigationContainer.tsx:90)
    in ForwardRef(NavigationContainer) (at App.js:22)
    in App (at CodePush.js:585)
    in CodePushComponent (at renderApplication.js:45)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:106)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:132)
    in AppContainer (at renderApplication.js:39)

Mistake in your example

https://moti.fyi/examples/auto-height

this example backgroundColor: 'green' is set in the wrong place, making it feel like a bug, what's happening is that going to 300 pixels the green view seems to be animated which actually isn't but going back to 100 feels like a bug jumping back without animating the height, but it's normal, it wasn't animating in the first place either just overflow hidden has made it look like that ,what you should have to fix this is :

            <MotiView
                animate={{height}}
                style={{overflow: "hidden", backgroundColor: "green"}}
            >
                <View onLayout={onLayout} style={{height: open ? 100 : 300}} />
            </MotiView>
            <Button title="toggle" onPress={toggle} />

Some bug reports

I tried to create a snack but It gave errors about reanimated 2 when I added moti.

import {AnimatePresence} from "moti";
import React, {useState} from "react";
import {Button, Text, TouchableOpacity} from "react-native";

const test = () => {
  const [presence, toggle] = React.useReducer((s) => !s, true)
  return (
         <>
            <AnimatePresence>
                {presence && (
                    <MotiView
                        from={{
                            translateY: -50,
                            rotateX: "-90deg",
                        }}
                        animate={{
                            translateY: 0,
                            rotateX: "0deg",
                        }}
                        exit={{
                            rotateX: "90deg",
                            translateY: -50,
                        }}
                        transition={{
                            type: "timing",
                            duration: 2000,
                        }}
                    >
                        <Text style={{fontSize: 50, color: "red"}}>213</Text>
                    </MotiView>
                )}
            </AnimatePresence> 
            <Button title="press-me" onPress={toggle} />
          </>
)
}
export default test;

using these :

    "moti": "^0.10.1-alpha.1",
    "react-native-reanimated": "~2.1.0",
...
expo sdk 41

If I remove translate here the rotate works and if I remove rotate the translate works, but they don't both work at the same time and I get a glitch effect instead.

Cool error about colors but probably not needed anymore?

[moti]: You passed backgroundColor: blue, but not all color values are supported yet in Reanimated 2. โ˜น๏ธ
Is totally understandable but the weird part is it really works lol.
I don't get it why would it work and also have the error?

                from={{backgroundColor: "red"}}
                animate={{backgroundColor: visible ? "blue" : "purple"}}

using on iOS.

ReferenceError: Property 'Proxy' doesn't exist, js engine: hermes [Mon Feb 08 2021 19:21:54.427] ERROR Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication), js engine: hermes

when i import {View as MotiView} from 'moti';
i get this error only then.
"@react-native-community/masked-view": "^0.1.10",
"@react-navigation/native": "^5.9.2",
"@react-navigation/stack": "^5.14.2",
"moti": "^0.4.1",
"react": "16.13.1",
"react-native": "0.63.4",
"react-native-bootsplash": "^3.1.3",
"react-native-gesture-handler": "^1.9.0",
"react-native-reanimated": "2.0.0-rc.0",
"react-native-safe-area-context": "^3.1.9",
"react-native-screens": "^2.17.1"

[Skeleton] not working properly on web

Version 0.5.x of @motify/skeleton works fine. However, once I go to 0.6.x, it's no longer animating on web after the first time. I'll have to investigate to see why this is.

Exit animation not shown

What

Exit animation not shown

Expected

the "toast" should fade out

Things I tried

  • add animated presence (with and without exitBeforeEnter)
  • add key
  • add exit prop

docs

mount/ unmount animations
animate presence
exit before enter

relevant code

    <Container>
      <AnimatePresence exitBeforeEnter>
        <MotiView
          key="content"
          type={type}
          from={{
            opacity: 0.3,
            translateY: 25,
          }}
          animate={{
            opacity: 1,
            translateY: 0,
          }}
          exit={{
            opacity: 0,
            translateY: 25,
          }}
          transition={{
            type: 'timing',
            duration: 100,
          }}
        >
         
             .....


        </MotiView>
      </AnimatePresence>
    </Container>

Demo

Screen.Recording.2021-05-18.at.08.34.17.mov

Versions

   "moti": "^0.10.1",
   "react-native-reanimated": "^2.0.0",
   "expo": "~41.0.0",

Usage with Next.js

I keep getting this error;

/Users/myuser/development/app/node_modules/react-native-reanimated/src/Animated.js:1
import { Image, ScrollView, Text, View } from 'react-native';
^^^^^^
SyntaxError: Cannot use import statement outside a module

I have set up Dripsy and Moti to be transpiled by Next

const withTM = require("next-transpile-modules")([
  "dripsy",
  "@dripsy/core",
  "moti",
  "@motify/core",
]);

and I'm also using "react-native-reanimated": "2.0.0-rc.0".

conditional animation-states no longer working w/ [email protected] and above

issue description:
conditional animations no longer work (animation is not done at all) with reanimated-rc.3 and above.

env:
RN 0.63.4 (also tested with 0.63.2)
reanimated 2.0.0-rc.0 up to 2.0.0 (see checklist below)
moti 0.6.0

I tested below code with moti 0.0.6 and the latest reanimated-versions.
It works as expected for the checked versions, but not fรผr rc.3 and 2.0.0

โœ”๏ธ 2.0.0-rc.0
โœ”๏ธ 2.0.0-rc.1
โœ”๏ธ 2.0.0-rc.2
๐Ÿšซ 2.0.0-rc.3
๐Ÿšซ 2.0.0

code:

...
import {View as MotiView} from 'moti';

export default function App() {
  const [rotate, setRotate] = useState(true);

  return (
      <Pressable
          onPress={()=> setRotate(prev => !prev)}
          style={{
            alignItems: 'center',
            justifyContent: 'center',
            ...StyleSheet.absoluteFillObject
          }}>
        <MotiView
            animate={{
              rotate: rotate ? '90deg' : '0deg',
            }}
            style={{height: 30, width: 30, backgroundColor: 'red'}}
        />
      </Pressable>
  );
}

I'm sorry in advance, if this is not a moti-related issue but a reanimated one...
Also I know that the docs recommend reanimated@rc.0,
but since reanimated@2.0.0 has come out a few days ago I already upgraded to that in my main App.

Thank you!

transitionTo Callback

Hey there, I'll start by saying this library is pretty amazing ๐Ÿ˜

I'm currently leveraging the useAnimationState hook to define some different states for my View. After I've transitioned to some of these states I'd like to fire a function, but there doesn't appear to be a straightforward way to accomplish this. My current setup only requires that the callback is fired for a single state, so I have a workaround, however I could see this eventually being a need for several states in different applications. It would be great if we could so something like the following:

const animationState = useAnimationState({
  from: {
    opacity: 1,
    height: 100,
  },
  expanded: {
    opacity: 1,
    height: 200,
  },
  // ...
})

const onPress = () => {
  animationState.transitionTo("expanded", () =>
    console.log("transition has completed"),
  )
}

Tried to synchronously call function {_definedProperty} from a different thread

When I run example component Skeleton, i had bug crash app.

I don't use Expo

  • Version:
    -"@motify/skeleton": "^0.8.1"
    -"expo-linear-gradient": "^9.0.0"
    -"moti": "^0.8.1"
    -"react-native-reanimated": "^2.1.0"
    -"react-native-unimodules": "^0.12.0"
  • My Code:
    `import React, { useReducer } from "react";
    import { StyleSheet, Pressable } from "react-native";
    import { View } from "moti";
    import { Skeleton } from "@motify/skeleton";

const Spacer = ({ height = 16 }) => <View style={{ height }} />;

const transition = {
opacity: {
duration: 300,
},
};

export default function HelloWorld() {
const [dark, toggle] = useReducer((s) => !s, true);

const colorMode = dark ? "dark" : "light";

return (

<View
transition={{
type: "timing",
}}
style={[styles.container, styles.padded]}
animate={{ backgroundColor: dark ? "#000000" : "#ffffff" }}
>




<Skeleton
transition={transition}
colorMode={colorMode}
width={"100%"}
/>

<Skeleton
transition={transition}
colorMode={colorMode}
width={"100%"}
/>


);
}

const styles = StyleSheet.create({
shape: {
justifyContent: "center",
height: 250,
width: 250,
borderRadius: 25,
marginRight: 10,
backgroundColor: "white",
},
container: {
flex: 1,
justifyContent: "center",
},
padded: {
padding: 16,
},
});`

simulator_screenshot_D05A4B3C-694A-4D75-A8A3-7E887B608ACE

Idea: Moti + Reanimated 2 skeleton loader

I think a skeleton component would be great to have. I haven't seen one that works with Reanimated before.

Here's what I have in mind:

https://www.loom.com/share/93eafeead0774b43a692d2771ec08f9b

I imagine the API looking like this:

import { Skeleton } from '@motify/skeleton'
<Skeleton width={160} />

You can set a fixed height or width. Or, make it adjust to the size of its children.

Also, by default, it will hide when it has children. But if you want to control this directly, use the show prop.

Show/hide

Skeleton will hide when data exists by default.

<Skeleton>
  {!!data ? <Data /> : null}
</Skeleton> 

You can always show the skeleton:

<Skeleton show={loading}>
  <Data />
</Skeleton>

Or hide it:

<Skeleton show={false}>
  {!!data ? <Data /> : null}
</Skeleton>

Border radius

Use radius to show a circle, square, or custom border radius. Defaults to 8.

Circle

<Skeleton height={48} width={48} radius="round">
  {!!data ? <Data /> : null}
</Skeleton> 

Square

<Skeleton height={48} width={48} radius="square">
  {!!data ? <Data /> : null}
</Skeleton> 

Custom radius

<Skeleton radius={16}>
  {!!data ? <Data /> : null}
</Skeleton>

Color mores

light or dark

<Skeleton colorMode="light" />

Custom colors

<Skeleton colors={['blue', 'cyan']} />

Custom animation delay

<Skeleton delay={250} />

Auto PR

I can't for the life of me get auto to work with GitHub actions. What am I missing here? I am new to this library. I set the env variables and followed the steps, but no dice...

Tried to synchronously call function {assign} from a different thread.

Getting the following error with [email protected] and [email protected]:

Tried to synchronously call function {assign} from a different thread.

Occurred in worklet location: [redacted]/node_modules/@motify/core/src/use-map-animate-to-style.ts (235:33)

Possible solutions are:
a) If you want to synchronously execute this method, mark it as a Worklet
b) If you want to execute this method on the JS thread, wrap it using runOnJS 

babel.config.js:

module.exports = (api) => {
  api.cache(true)
  return {
    presets: ["babel-preset-expo", "babel-preset-const-enum", "babel-preset-power-assert"],
    plugins: [
      ["babel-plugin-macros"],
      ["@babel/plugin-transform-flow-strip-types"],
      ["babel-plugin-transform-typescript-metadata"],
      ["@babel/plugin-proposal-decorators", { legacy: true }],
      ["@babel/plugin-proposal-class-properties", { loose: true }],
      ["babel-plugin-console-source"],
      ["babel-plugin-graphql-tag"],
      ["babel-plugin-lodash"],
      ["babel-plugin-ramda"],
      ["babel-plugin-mst-async-action"],
      ["babel-plugin-styled-components"],
      // react-native-reanimated must be listed last
      ["react-native-reanimated/plugin"],
    ],
  }
}

the code that fails:

import React from "react"
import { View as MotiView } from "moti"

function Shape() {
  return (
    <MotiView
      from={{
        opacity: 0,
        scale: 0.5,
      }}
      animate={{
        opacity: 1,
        scale: 1,
      }}
      transition={{
        type: "timing",
      }}
      style={{
        backgroundColor: "white",
        borderRadius: 25,
        height: 250,
        justifyContent: "center",
        marginRight: 10,
        width: 250,
      }}
    />
  )
}

export const RootAppComponent: React.FC = () => (
  <Shape />
)

Everything works fine in the same monorepo using a managed Expo project but things fall apart with our bare Expo project. I wouldn't expect the latter to fail while the former works so I'm not sure what's going on ๐Ÿคท

[Next.js] TypeError: Cannot convert undefined or null to object

Using v0.0.5, I'm having trouble running moti in Next.js.

import React from 'react'
import { View } from 'moti'

export default function Test() {
  return <View />
}

This code produces the following error:

Screen Shot 2021-02-01 at 10 50 59 PM

My best guess is some use of Object.keys in useAnimatedStyle is breaking. Not sure.

start and end animation with state

Curious how you handle starting and stopping animations with Moti. In the docs for looped animation you explain that we can't change the animation on the fly, so I figured I'll use state to switch it on and off like so:

import { View } from 'moti';
const [animating, setAnimating] = useState(false);
<Pressable
  onPress={() => {
    setAnimated(true);
    Run_some_api_call({...})
    .then(() => {
      setAnimating(false);
      })
      .catch(() => {
          setAnimating(false);
      });
  }}>
  {animating ? (
    <View
      from={{
        scale: 1,
      }}
      animate={{
        scale: 0.7,
      }}
      transition={{
        loop: true,
        type: 'timing',
        duration: 200,
        delay: 100,
      }}>
      <Icon
        name={favorited ? 'heart' : 'heart-o'}
        type="font-awesome"
      />
    </View>
  ) : (
    <View>
      <Icon
        name={favorited ? 'heart' : 'heart-o'}
        type="font-awesome"
        size={Sizes.x1}
        color={Colors.dineden_400}
      />
    </View>
  )}
  ...

But it gets stuck when it's making the api call. I even placed setAnimating in the response inside a setTimeout to create a few seconds delay. There's no animation. I see it grow smaller without animation, and after the delay it jumps back to scale 1.

Similarly, I tried setting the Skeleton component show property with state while loading network images and it's not smooth either.

Using Expo sdk 41 with reanimated 2.1.0

How do you handle these scenarios?

Bug with percentage based values

When trying to use percentage based values in a Moti View's animate prop, I am getting errors of the format JSON value '50' of type NSString cannot be converted to a ABI40_0_0YGValue. Did you forget the % or pt suffix?

For example, this throws an error:

<MotiView animate={{ left: value ? '0%' : '50%' }} />

Define `exit` prop in `useAnimationState`

Currently, we can do this:

const state = useAnimationState({
  from: { opacity: 0 },
  to: { opacity: 1 }
})

Since useAnimationState is all on the native thread, and is more performant than using props directly, it would be nice to have an exit option here too:

const state = useAnimationState({
  from: { opacity: 0 },
  to: { opacity: 1 },
  exit: { opacity: 0 } // we should add this
})

How to create animation component with moti?

Hi, I want to create a custom animation component with Moti, how can I do it?
Ex: I'm using LinearGradient I want to custom like Animated.createAnimatedComponent(LinearGradient) and then I can using animation for this component

Question: can this sorting animation be done in moti?

Hey, everybody! ๐Ÿ‘‹
I tried to reproduce this animation in moti, but I've failed.
This simple animation is used a lot on Telegram, Whatsapp, etc.
Below I used LayoutAnimation to do the sorting.

Can it be done?
Thanks!

Mar-04-2021.10-41-21.mp4
import * as React from "react";
import { StyleSheet, FlatList, SafeAreaView, Text, Button, LayoutAnimation, View } from 'react-native'

const DATA = [Math.random().toString(), Math.random().toString(), Math.random().toString()];

const Item = ({ item }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{item}</Text>
  </View>
);

export default function App() {
  const [data, setData] = React.useState(DATA);

  const renderItem = ({ item }) => (
    <Item item={item} />
  );

  const onAdd = () => {
    const i = Math.random().toString()
    const newData = [...data, i]
    setData(newData)
  }

  const onSort = () => {
    LayoutAnimation.configureNext({
      duration: 200,
      create: {
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.opacity
      },
      update: {
        type: LayoutAnimation.Types.easeInEaseOut
      },
      delete: {
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.opacity
      }
    })
    setData([...data].sort((a, b) => a - b))
  }

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={data}
        extraData={data}
        renderItem={renderItem}
        keyExtractor={item => item}
      />
      <Button title='add' onPress={onAdd} />
      <Button title='sort' onPress={onSort} />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  item: {
    backgroundColor: '#f9c2ff',
    padding: 10,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 20,
  },
})

Handling animation types in single values (not sequences)

        <AnimatePresence exitBeforeEnter>
            <MotiView
                style={{
                    alignSelf: "start",
                }}
                from={{
                    translateY: -50,
                }}
                animate={{
                    translateY: 0,
                }}
                exit={{
                    translateY: [
                        {
                            value: -50,
                            type: "timing",
                        },
                        {
                            value: -50,
                            type: "timing",
                        },
                    ],
                }}
            >
                <Text style={{fontSize: 50}}>{props.letter}</Text>
            </MotiView>
        </AnimatePresence>

I want this to exit with timing and enter with spring looking at what I had to do, to make this happen makes it obvious what I'm talking about using a

exit={{
     translateY : {
                            value: -50,
                            type: "timing",
                        }
}}

Making a lot more sense but that gave errors so I had to use sequence with same values :( a really bad hack but works.
If we could have that option, this wouldn't happen anymore.
What do you think?

Refactor to monorepo

Is there any particular structure the monorepo should have?

There are quite a few free-floating files, curious where you think everything should fall.

Animating position on exit

I'm trying create a TabView component that animates between sliding tabs. The components height should adjust for the current tab and not be affected by the exiting tab.

I would expect this code to work, how ever the position: "absolute" is not applied on exit.

export default function TabView({ tabIndex }) {
  const tabs = [<View style={{ height: 100 }} />, <View style={{ height: 200 }} />]

  return (
      <AnimatePresence>
        <View
          key={tabIndex}
          from={{
            translateX: "100%",
          }}
          animate={{
            translateX: "0%",
             position: "relative"
          }}
          exit={{
            translateX: "-100%",
            position: "absolute"
          }}
          transition={{
            type: "timing",
          }}
        >
          {tabs[tabIndex]
        </View>
      </AnimatePresence>
      )
}

I'm not sure if this is expected behaviour or not

Skeleton flicker on mount

Love the library and also the new Skeleton component ๐Ÿฅ‡

Using the Skeleton component gives me a initial flicker on mount, it's because of the onLayout and state change, I can see three different solutions for this:

  1. Add opacity until we have the width calculated
  2. Enforce fixed width with px
  3. Get measure from Reanimated to work as we want: software-mansion/react-native-reanimated#1497

Let me know how we should tackle this and I can make a PR.

AnimatedPresence delay

Using the delay on animated presence, causes unexpected results, delay must do something else here,not what it normally does.

Can't get moti to work with Expo or native

Hey,

I can't seem to get moti working. I've tried it on both Expo and bare React Native project.

I've tried react-native-reanimated RC0, RC2, RC3.
I'm on moti v0.4.1.

I've added react-native-reanimated/plugin to my Babel config.

But I get different errors with different versions.

Bare React Native with RC2

	14:40:34.086	Can't find variable: _globalSetter

_f
	14:38:00.888	Tried to synchronously call function {assign} from a different thread.

Occurred in worklet location: app/node_modules/@motify/core/src/use-map-animate-to-style.ts (240:33)

Possible solutions are:
a) If you want to synchronously execute this method, mark it as a Worklet
b) If you want to execute this method on the JS thread, wrap it using runOnJS
	14:38:00.893	TypeError: undefined is not an object (evaluating 'ref.current.last')

This error is located at:
    in ForwardRef(Wrapped) (at spinner.tsx:60)
    in RCTView (at View.js:34)
    in View (at spinner.tsx:33)
    in Spinner (at loading.tsx:15)
    in RCTView (at View.js:34)
    in View (at loading.tsx:13)
    in Loading (at src/index.tsx:20)
    in Willa (at app/index.js:18)
    in QueryClientProvider (at app/index.js:16)
    in App (at renderApplication.js:45)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:106)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:132)
    in AppContainer (at renderApplication.js:39)
	14:38:00.899	TypeError: undefined is not an object (evaluating 'ref.current.last')

This error is located at:
    in ForwardRef(Wrapped) (at spinner.tsx:47)
    in RCTView (at View.js:34)
    in View (at spinner.tsx:33)
    in Spinner (at loading.tsx:15)
    in RCTView (at View.js:34)
    in View (at loading.tsx:13)
    in Loading (at src/index.tsx:20)
    in Willa (at app/index.js:18)
    in QueryClientProvider (at app/index.js:16)
    in App (at renderApplication.js:45)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:106)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:132)
    in AppContainer (at renderApplication.js:39)
	14:38:00.903	TypeError: undefined is not an object (evaluating 'ref.current.last')

This error is located at:
    in ForwardRef(Wrapped) (at spinner.tsx:34)
    in RCTView (at View.js:34)
    in View (at spinner.tsx:33)
    in Spinner (at loading.tsx:15)
    in RCTView (at View.js:34)
    in View (at loading.tsx:13)
    in Loading (at src/index.tsx:20)
    in Willa (at app/index.js:18)
    in QueryClientProvider (at app/index.js:16)
    in App (at renderApplication.js:45)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:106)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:132)
    in AppContainer (at renderApplication.js:39)
	14:38:00.905	TypeError: undefined is not an object (evaluating 'ref.current.last')

This error is located at:
    in ForwardRef(Wrapped) (at spinner.tsx:60)
    in RCTView (at View.js:34)
    in View (at spinner.tsx:33)
    in Spinner (at loading.tsx:15)
    in RCTView (at View.js:34)
    in View (at loading.tsx:13)
    in Loading (at src/index.tsx:20)
    in Willa (at app/index.js:18)
    in QueryClientProvider (at app/index.js:16)
    in App (at renderApplication.js:45)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:106)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:132)
    in AppContainer (at renderApplication.js:39)
	14:38:00.973	undefined is not an object (evaluating 'Object.keys(mergedStyles)')

keys@[native code]
_f
[native code]
styleUpdater
[native code]
_f
[native code]

Bare React Native with RC0

	14:41:18.856	
Tried to synchronously call function {assign} from a different thread.
Solution is:
a) If you want to synchronously execute this method, mark it as a Worklet
b) If you want to execute this method on the JS thread, wrap it using runOnJS
	14:41:19.010	undefined is not an object (evaluating 'Object.keys(mergedStyles)')

keys@[native code]
_f
[native code]
styleUpdater
[native code]
_f
[native code]
	14:41:19.097	
Tried to synchronously call function {assign} from a different thread.
Solution is:
a) If you want to synchronously execute this method, mark it as a Worklet
b) If you want to execute this method on the JS thread, wrap it using runOnJS
	14:41:19.185	undefined is not an object (evaluating 'Object.keys(mergedStyles)')

keys@[native code]
_f
[native code]
styleUpdater
[native code]
_f
[native code]
	14:41:19.242	
Tried to synchronously call function {assign} from a different thread.
Solution is:
a) If you want to synchronously execute this method, mark it as a Worklet
b) If you want to execute this method on the JS thread, wrap it using runOnJS
	14:41:19.349	undefined is not an object (evaluating 'Object.keys(mergedStyles)')

keys@[native code]
_f
[native code]
styleUpdater
[native code]
_f
[native code]
	14:41:19.413	
Tried to synchronously call function {assign} from a different thread.
Solution is:
a) If you want to synchronously execute this method, mark it as a Worklet
b) If you want to execute this method on the JS thread, wrap it using runOnJS
	14:41:19.503	undefined is not an object (evaluating 'Object.keys(mergedStyles)')

keys@[native code]
_f
[native code]
styleUpdater
[native code]
_f
[native code]

What am I missing? I'm using 16.13.1 for React and 0.63.4 for React Native.

Question regarding sequences of animations

First, thanks for an awesome library!

I would like to so a sequence in an animation like:

<MotiView
  from={{ opacity: 0 }}
  animate={{
    opacity: [
      {
        value: 1,
        delay: 200,
        type: 'timing',
        duration: 1000,
      },
      {
        value: 0,
        delay: 4000,
        type: 'timing',
        duration: 500,
      },
    ],
  }} />

I.e, I would like to have sequenced animation of a value but with different durations.

Reading the documention at: https://moti.fyi/animations#sequence-animations
It sound like "Any transition settings can be passed to a sequence object."

But for me, the duration setting is not working (and is shown as faulty by TypeScript).

Is it possible somehow to sequence animations with different durations (and easings)?

framer-motion dependency

Looks like framer-motion dependency adds around 90kb to js bundle. It's critical for web usage.
Is it necessary?

How to configure transition per-variant with moti?

Hi, thanks for this fantastic library! I wonder if is possible to configure different transitions per-custom-variant. E.g: Using spring transition for opening a floating menu and timing transition for closing it.

import React from 'react'
import { StyleSheet, Pressable } from 'react-native'
import { View, useAnimationState } from 'moti'

// you can create a reusable animation preset
const useToggle = () => {
    return useAnimationState({
        close: {
            translateY: -50,
        },
        open: {
            translateY: 30,
        },
    })
}

export default function HelloWorld() {
    const toggle = useToggle()

    const onPress = () => {
        toggle.transitionTo((state) => {
            if (state === 'close') {
                return 'open'
            }

            return 'close'
        })
    }

    return (
        <Pressable onPress={onPress} style={styles.container}>
            <View style={styles.button}></View>
            <View
                state={toggle}
                style={[styles.button, {backgroundColor: 'grey',}]}
                transition={{
                    // default settings for all style values
                    type: 'spring',
                    duration: 350,
                    // set a custom transition for open
                    open: {
                        type: 'spring',
                        delay: 350,
                    },
                    // set a custom transition for close
                    close: {
                        type: 'timing',
                        delay: 350,
                    },
                }}
            />
        </Pressable>
    )
}

const styles = StyleSheet.create({
    button: {
        justifyContent: 'center',
        height: 50,
        width: 50,
        borderRadius: 50,
        marginRight: 10,
        backgroundColor: 'black',
    },
    container: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'column',
        backgroundColor: 'cyan',
    },
})

<AnimatePresence initial={false} /> does not work as expected

Issue

This code will play the initial animation, even while initial is set to false

<AnimatePresence initial={false}>
 <View
      from={{
        opacity: 0,
        scale: 0.9,
      }}
      animate={{
        opacity: 1,
        scale: 1,
      }}
      exit={{
        opacity: 0,
        scale: 0.9,
      }}
      style={styles.shape}
    />
</AnimatePresence>

Workaround

Rename the from prop to initial

<AnimatePresence initial={false}>
 <View
      initial={{
        opacity: 0,
        scale: 0.9,
      }}
      animate={{
        opacity: 1,
        scale: 1,
      }}
      exit={{
        opacity: 0,
        scale: 0.9,
      }}
      style={styles.shape}
    />
</AnimatePresence>

I'm not sure if this is expected behaviour as the initial prop is not documented and how it differs to the from prop

Bumping/unwanted animation at each render (even if `animate` does not change)

Even if animate prop values don't change, there's a bumping animation at each render.

  • Shape component always uses the same animate values.
  • App component triggers a re-render every time you touch the screen.
  • Shape gets animated even if there should not be any animation.

Try this code, and touch the screen:

import * as React from "react";
import { StyleSheet, Pressable } from 'react-native'
import { View } from 'moti'

function Shape({ bg }) {
  return (
    <View
      from={{
        opacity: 0,
        scale: 0.5,
      }}
      animate={{
        opacity: 1,
        scale: 1,
      }}
      exit={{
        opacity: 0,
        scale: 0.9,
      }}
      style={[styles.shape, { backgroundColor: bg }]}
    />
  )
}

export default function App() {
  const [visible, toggle] = React.useReducer((s) => !s, true)

  return (
    <Pressable onPress={toggle} style={styles.container}>
      <Shape bg="hotpink" key="hotpink" />
    </Pressable>
  )
}

const styles = StyleSheet.create({
  shape: {
    justifyContent: 'center',
    height: 250,
    width: 250,
    borderRadius: 25,
    marginRight: 10,
    backgroundColor: 'white',
  },
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    flexDirection: 'row',
    backgroundColor: '#9c1aff',
  },
})

Problem with using AnimatePresence when using rotation

Using this example :
https://github.com/expo/examples/blob/master/with-moti/App.js

Just adding rotation to it :

            from={{
                opacity: 0,
                scale: 0.2,
                rotateZ: "45deg",
            }}
            animate={{
                opacity: 1,
                scale: 1,
                rotateZ: "0deg",
            }}
            exit={{
                opacity: 0,
                scale: 0.5,
                rotateZ: "45deg",
            }}

Causes some unexpected results on enter animation.

[AnimatePresence] Issues with spring animations

When using exit animations + springs + AnimatePresence, there are bugs where the exited item doesn't always exit. This only happens when you have more than one style key set.

I think it might be due to this line:

const isLastStyleKeyToAnimate =

Example:

<AnimatePresence>
  <MotiView animate={{ opacity: 1, translateY: 0 }} animate={{ opacity: 0, translateY: -1 }} />
</AnimatePresence>

The current logic is, once the last Reanimated animation callback has fired, we call safeToUnmount from framer motion. The problem is, if we're using springs, different animations will not take the same amount of time to animate necessarily.

This is the code now:

if (isExiting) {
  //   // if this is true, then we've finished our exit animations
  const isLastStyleKeyToAnimate = index + 1 === Object.keys(mergedStyles || {}).length
  if (isLastStyleKeyToAnimate) {
    runOnJS(reanimatedSafeToUnmount)()
  }
}

Possible solution

In the case above, translateY could finish before opacity, for instance. Rather than simple call safeToUnmount after the last animation in the dictionary finishes, we should probably be keeping memory of every animation that completes. After the last one completes, then we know we're ready to unmount.

// outside of for-loop
const needsExiting = Object.keys(exitStyles).reduce((acc, key) => ({ ...acc, [key]: true }), {})

// ...

if (isExiting) {
  needsExiting[key] = false
  const isLastStyleKeyToAnimate = !Object.values(needsExiting).some(Boolean)
  if (isLastStyleKeyToAnimate) {
    runOnJS(reanimatedSafeToUnmount)()
  }
}

It would maybe make more sense to use Set here, but not sure if that's supported by Reanimated, so this should be fine.

This might just be related to using 2.0.0-rc.0, which has issues with springs. I'll have to see if using stable reanimated v2 solves it.

Docs

Let's get some good docs setup.

Add dynamic animation state

This would be cool:

const state = useDynamicAnimation({
  // initial state, like useState()
  opacity: 0,
  scale: 1
})

onPress = () => {
  state.animateTo(current => ({ ...current, opacity: 1 }))

  // read in the current state:
  
}

return <MotiView state={state} />

It would allow us to have the performance benefits of useAnimationState, without needing static variants or relying on react state updates.

How to loop animation with reset the value

Hi, I don't know how to animate "the heart" like this.
Screen Recording 2021-03-19 at 10 10 24 AM
Like I understand we can create animation for the heart

  • opacity 0 -> 1 -> 0
  • translate A -> B
    and then it reset value -> loop again
    And in moti I can't reset the value to loop again like this
    Can you create an example for this?

Animated Pressable component

I'd like to add something like the Pressable component, but it should use react-native-gesture-handler under the hood. Rather than update state on native, it would use a shared value which you could interpolate.

<AnimatedPressable onPress={onPress}>
  {({ hovered, pressed }) => {
    return <MotiView animate={{ opacity: pressed.value ? 1 : 0 }} />
  }}
</AnimatedPressable>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.