pmndrs / react-spring

✌️ A spring physics based React animation library
http://www.react-spring.dev/
MIT License
28.11k stars 1.19k forks source link

Remove transform on animation end #747

Closed arvigeus closed 4 years ago

arvigeus commented 5 years ago

πŸ› Bug Report

What I am trying to do is to remove the transform property after the animation is complete. I found out I can set a second value in enter function, but it tries to animate it from perspective(1000px) rotateY(0deg) to none, which results in crash. Transform interferes with position: fixed of a child element and it needs to be none (long live CSS bugs!). The only thing I can think of is to chain transition elements and use immediate on the second, but this sounds like an overkill.

To Reproduce

<Transition
    native
    initial={null}
    items={location}
    keys={location => location.pathname}
    from={{ opacity: 0, transform: "perspective(1000px) rotateY(180deg)" }}
    enter={[
        { opacity: 1, transform: "perspective(1000px) rotateY(0deg)" },
        { transform: "none", config: { immediate: true } }
    ]}
    leave={{ opacity: 0, position: "absolute", transform: "perspective(1000px) rotateY(-180deg)", pointerEvents: "none" }}
>
    {location => style => <animated.div style={style}>{children(location)}</animated.div> }
</Transition>

(tested with { transform: "none", immediate: true } as well)

Expected behavior

transform: none

Link to reproduce

https://gist.github.com/arvigeus/8ae09dc28cfd13b9f4e42418dd0eaa99

Environment

arvigeus commented 5 years ago

I missed the fact @aleclarson advised me to test it with v9. I did another test with 9.0.0-beta.9 and I got:

Module not found: Can't resolve 'react-spring/renderprops'

Can't confirm if it is working. node_modules/react-spring is missing renderprops folder

aleclarson commented 5 years ago

You can now use react-spring for the renderprops API:

import { Transition } from 'react-spring'
arvigeus commented 5 years ago

It works with some caveats:

index.js:1375 TypeError: Cannot read property 'map' of null
    at stringInterpolation.js:79
    at Array.map (<anonymous>)
    at Object.push../node_modules/@react-spring/shared/stringInterpolation.js.exports.createStringInterpolator (stringInterpolation.js:78)
    at push../node_modules/@react-spring/shared/createInterpolator.js.exports.createInterpolator (createInterpolator.js:33)
    at new AnimatedInterpolation (index.js:128)
    at createAnimatedInterpolation (index.js:184)
    at AnimatedValue.to (index.js:224)
    at Controller._animate (index.js:759)
    at Controller._run (index.js:532)
    at index.js:522
    at Array.forEach (<anonymous>)
    at push../node_modules/@react-spring/shared/helpers.js.exports.each (helpers.js:34)
    at Controller._flush (index.js:513)
    at Controller.start (index.js:284)
    at index.js:1289
    at Array.forEach (<anonymous>)
    at useTransition (index.js:1247)
    at Transition (index.js:1480)
    at renderWithHooks (react-dom.development.js:13449)
    at updateFunctionComponent (react-dom.development.js:15199)
    at beginWork (react-dom.development.js:16252)
    at performUnitOfWork (react-dom.development.js:20279)
    at workLoop (react-dom.development.js:20320)
    at renderRoot (react-dom.development.js:20400)
    at performWorkOnRoot (react-dom.development.js:21357)
    at performWork (react-dom.development.js:21267)
    at performSyncWork (react-dom.development.js:21241)
    at interactiveUpdates$1 (react-dom.development.js:21526)
    at interactiveUpdates (react-dom.development.js:2268)
    at dispatchInteractiveEvent (react-dom.development.js:5085)

TypeScript is also complaining about this line (style):

<animated.div style={style}>{children(location)}</animated.div>
Type '{ [x: string]: SpringValue<any>; opacity: SpringValue<number>; transform: SpringValue<string>; immediate: SpringValue<true>; position: SpringValue<string>; pointerEvents: SpringValue<string>; }' is not assignable to type '{ alignContent?: string | SpringValue<string> | undefined; alignItems?: string | SpringValue<string> | undefined; alignSelf?: string | SpringValue<string> | undefined; animationDelay?: string | ... 1 more ... | undefined; ... 738 more ...; vectorEffect?: "-moz-initial" | ... 7 more ... | undefined; }'.
  Types of property 'pointerEvents' are incompatible.
    Type 'SpringValue<string>' is not assignable to type '"-moz-initial" | "inherit" | "initial" | "revert" | "unset" | "none" | "fill" | "stroke" | "all" | "auto" | "painted" | "visible" | "visibleFill" | "visiblePainted" | "visibleStroke" | SpringValue<...> | undefined'.
      Type 'SpringValue<string>' is not assignable to type 'SpringValue<PointerEventsProperty>'.
        Type 'string' is not assignable to type 'PointerEventsProperty'
arvigeus commented 5 years ago

Is this out of the scope of this bug? Should I close?

aleclarson commented 5 years ago
  1. The immediate prop should not be wrapped with config object.
  2. You need to upgrade TypeScript to v3.5+

The following error is what I expected to happen, but it's not the intended behavior:

TypeError: Cannot read property 'map' of null

I'll try to fix that before v9 beta is over. You can keep this issue open. πŸ‘

arvigeus commented 5 years ago

immediate is without config (TS caught that, hehe)

Thank you very very much! :)

aleclarson commented 5 years ago

Can you provide a Code Sandbox so I can test my fix. Thanks πŸ‘

arvigeus commented 5 years ago

There: https://codesandbox.io/s/react-spring-v7211-747-o9yvi For some reason I cannot switch to react-spring above 7, sorry. What is curious is that transform: none works on initial render, but it breaks when switching routes.

deleterepo commented 5 years ago

A fix in the meantime for me was to put elements that require position: fixed e.g. modals in a Portal: https://reactjs.org/docs/portals.html. That way the element is rendered outside the parent element with the transform applied to it.

aleclarson commented 4 years ago

This will definitely be fixed in the next canary version (9.0.0-canary.808.18), but the example needs a small change to its AnimatedRoute component.

 const AnimatedRoute = ({ children }) => (
   <Route
     render={({ location }) => (
       <Transition
-        native
         items={location}
         keys={location => location.pathname}
         from={{
           opacity: 0,
           transform: 'perspective(900px) rotateY(180deg)',
         }}
         enter={[
           { opacity: 1, transform: 'perspective(1000px) rotateY(0deg)' },
           { transform: 'none', immediate: true },
         ]}
-        leave={{ opacity: 0, transform: 'perspective(900px) rotateY(-180deg)', pointerEvents: 'none' }}>
-        {location => style => <Container style={style}>{children(location)}</Container>}
+        leave={[
+          { transform: 'perspective(1000px) rotateY(0deg)', immediate: true },
+          {
+            opacity: 0,
+            transform: 'perspective(900px) rotateY(-180deg)',
+            pointerEvents: 'none',
+          },
+        ]}>
+        {(style, location) => (
+          <Container style={style}>{children(location)}</Container>
+        )}
       </Transition>
     )}
   />
 )

The first update of the leave animation helps avoid trying to animate from none to perspective(900px) rotateY(-180deg), which is impossible because none doesn't contain 2 numeric strings like the other does.

Also, the render prop has a new signature now, in accordance with #809.

aleclarson commented 4 years ago

Now available in v9.0.0-rc.2 #985

Harjot1Singh commented 4 years ago

For anyone else on v8, I could only get this to work with interpolation (I'm using the hooks API):

  const transitions = useTransition( mount, null, {
    from: { transform: 100 },
    enter: { transform: 0 },
    leave: { transform: 100 },
  } )

  return transitions.map( ( { item, props: { transform, ...props } } ) => item && (
    <animated.div
      key
      className={classes.root}
      style={{
        ...props,
        transform: transform.interpolate( ( t ) => ( t ? `translateX(${t}%)` : 'none' ) ),
      }}
    >
      <Component />
    </animated.div>
  ) )