React-spring: Remove transform on animation end

Created on 8 Jul 2019  路  12Comments  路  Source: pmndrs/react-spring

馃悰 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

  • react-spring v8.0.27
  • react v16.8.6
bug v9

Most helpful comment

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>
  ) )

All 12 comments

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

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

import { Transition } from 'react-spring'

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'

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

  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. 馃憤

immediate is without config (TS caught that, hehe)

Thank you very very much! :)

Can you provide a Code Sandbox so I can test my fix. Thanks 馃憤

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.

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.

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.

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

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>
  ) )
Was this page helpful?
0 / 5 - 0 ratings