One material card, Multiple animations using react-addons-transition-group - reactjs

I have a <Card> component from material-ui. It has a CardHeader, CardText and other pieces that come together to make the whole card.
I am able to use react-addons-transition-group to animate this card as it enters the screen (componentWillEnter). This works great with:
export class MyCard extends React.Component {
constructor(props) {
super(props);
}
componentWillEnter (callback) {
const el = findDOMNode(this);
TweenMax.fromTo(el, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
}
render(){
return <Card>
<h1> Blah Blah Title <h1>
<CardText> Blah Blah </CardText>
</Card>
}
}
Then the above gets rendered in another component like so:
<ReactTransitionGroup> <MyCard> </ReactTransitionGroup>
Wrapping it in ReactTransitionGroup makes sure it calls the componentWillEnter gets called, and the animation runs. This works as expected.
What I would like to do is multiple animations. So, the Card comes in from the left, the CardText comes in from the top, and the h1 comes in from the right-- or something to that affect.
Ideally, I would just do something like:
componentWillEnter (callback) {
const el = findDOMNode(Card);
const el = findDOMNode(h1);
const el = findDOMNode(CardText);
TweenMax.fromTo(Card, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
TweenMax.fromTo(h1, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
TweenMax.fromTo(CardText, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
}
Of course, this doesn't work.
So my question is, how do I go about parsing the this instance of the component so I can animate each individual part?
I couldn't really find much on findDOMNode. It doesn't seem I can add id tags to the different parts and use those in findDOMNode. Or maybe I'm going about it all wrong.

Found the answer! Simply use refs and access them in the this object:
So:
render(){
return <Card ref="card">
<h1 ref="title"> Blah Blah Title <h1>
<CardText ref="text"> Blah Blah </CardText>
</Card>
}
Then you reference them in the animation portion like:
export class MyCard extends React.Component {
constructor(props) {
super(props);
}
componentWillEnter (callback) {
const card = findDOMNode(this);
const title = findDOMNode(this.refs.title);
const description = findDOMNode(this.refs.description);
TweenMax.fromTo(card, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
TweenMax.fromTo(title, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
TweenMax.fromTo(description, 0.4, {y: 100, opacity: 0}, {y: 0, opacity: 1, onComplete: callback});
}
render(){
return <Card>
<h1 ref="title"> Blah Blah Title <h1>
<CardText ref="text"> Blah Blah </CardText>
</Card
}
}
You'll of course want to differ the timing and animations in each TweenMax.fromTo call but that should do it!
I guess it is worth adding some further reading, as string refs may get deprecated? Hard to tell:
https://facebook.github.io/react/blog/2015/02/24/streamlining-react-elements.html#pending-change-the-refs-semantics
https://twitter.com/dan_abramov/status/664212109056729088

Related

whileInView and whileHover conflicting

I have a grid component and a card component that goes inside this. I wanted to animate each child of the grid whileInView so I added this prop to the parent and was working properly.
The problem comes when I try to add whileHover prop to the childs, it causes the whileInView prop to be disabled. I have also tried to add it on the parent but it makes the grid to anime as a whole.
Grid component:
<SmallGridSection key={`div-${title}`}>
<SmallGrid
key={`grid-${title}`}
variants={gridVariants}
initial={"hidden"}
whileInView={"visible"}
viewport={{ once: true }}
>
{array.map(
technology =>
<Card technology={technology} key={technology.name}/>
)}
</SmallGrid>
</SmallGridSection>
using this variants:
const gridVariants = {
visible: {
transition: {
when: "beforeChildren",
delayChildren: 1,
staggerChildren: 0.1,
},
},
hidden: {
transition: {
when: "afterChildren",
duration: 1,
type: "spring",
},
},
}
Card component:
const Card = ({ technology, ...props }) => {
return(
<CardStyled
color={technology.color}
variants={cardVariants}
title={technology.name}
( *this is where i tried to add "whileHover={"hovered"}*)
{...props}
>
<a>
<TechnologyIcon technology={technology}/>
</a>
</CardStyled>
)
}
with this variants:
const cardVariants = {
hidden: {
opacity: 0,
rotate: "100deg"
},
visible: {
opacity: 1,
rotate: 0,
},
animated: {
translateY: "-5px",
transition: {
type: "spring",
delay: 3,
duration: 0.5,
repeat: "infinity",
},
},
hovered: {
scale: 1.08,
transition: {
duration: 0.2,
ease: "easeInOut",
},
}
}
I stumbled upon this problem as well and managed to solve it by applying whileInView and whileHover to different levels, not to one element. So you need to wrap your card content into another div (or whatever you want) and apply whileHover to it as well as variants with the hover property (actually, the whole cardVariants will work as well):
<CardStyled
color={technology.color}
variants={cardVariants}
title={technology.name}
>
<motion.div
whileHover="hovered"
variants={{
hover: cardVariants.hovered,
}}
>
<a>
<TechnologyIcon technology={technology}/>
</a>
</motion.div>
</CardStyled>
Of course, it would make sense to move the new div into your CardStyled component, but the overall structure should look this way.

framer-motion custom hover variant is affected by a delay from another variant

I have a set of variants defined as such:
const letterVariants: Variants = {
visible: (custom: number) => ({
opacity: 1,
y: 0,
transition: {
delay: custom * 0.1,
},
}),
hidden: {
opacity: 0,
y: 100,
},
hover: {
y: -30,
transition: {
delay: 0,
},
},
};
which are used for the hero title:
<h1 className="font-peralta">
{letters.map((letter, i) => (
<motion.div
className="inline-block"
variants={letterVariants}
custom={i}
initial="hidden"
animate="visible"
whileHover="hover"
key={nanoid()}
>
{letter}
</motion.div>
))}
</h1>
On initial render, each letter of the hero title fades in from the bottom. I am trying to make it also that when I hover (before or after animation end), the letters slide up a bit. The problem I am facing is that the custom transition delay that allows for the letters to pop up one-by-one is also applied to the hover variant. So when I hover over the first letter, it moves up and down immediately, if I hover over the last letter, it goes up but takes x * 0.1 seconds to return to its normal position.
It's almost exactly the same as in this post except that solution has not worked for me.

React TypeScript document.getElementById always selects the first instance

I've got 2 of the same components being rendered
<div><Modal title='Join'/></div>
<div><Modal title='Login'/></div>
the modal components is like this
import React, {useState} from 'react';
import {Join} from './join';
import {Login} from './login';
interface propsInterface {
title: string;
}
const Modal: React.FC<propsInterface> = (props) => {
const [state, setState] = useState({showLogin: props.title === "login" ? false : true});
let modalState = false;
function toggleLogin(event: any) {
setState({...state, showLogin: !state.showLogin});
}
function toggleModal(event: any) {
if (event.target.id !== 'modal') return;
modalState = !modalState;
const modal = document.getElementById('modal'); <<==this always selects the first one
const card = document.getElementById('card'); <<==this always selects the first one
if (modal && card && modalState === true) {
modal.style.display = "flex";
modal.animate([{backgroundColor: 'rgba(0, 0, 0, 0)'}, {backgroundColor: 'rgba(0, 0, 0, 0.6)'}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
card.animate([{opacity: 0}, {opacity: 1}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
card.animate([{transform: 'translateY(-200px)'}, {transform: 'translateY(0)'}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
}
if (modal && card && modalState === false) {
modal.animate([{backgroundColor: 'rgba(0, 0, 0, 0.6)'}, {backgroundColor: 'rgba(0, 0, 0, 0)'}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
card.animate([{opacity: 1}, {opacity: 0}],{duration: 200, easing: 'ease-in-out', fill: 'forwards'});
card.animate([{transform: 'translateY(0)'}, {transform: 'translateY(-200px)'}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
setTimeout(() => modal.style.display = "none", 200);
}
}
return (
<div>
<div className='modal' id='modal' onClick={toggleModal}>
<div className='card' id='card'>
{props.title}
{state.showLogin
? <Login toggleLogin={toggleLogin} toggleModal={toggleModal}/>
: <Join toggleLogin={toggleLogin} toggleModal={toggleModal}/>}
</div>
</div>
<div onClick={toggleModal} className='modal-title' id='modal'> {props.title}</div>
</div>
);
}
export {Modal};
Because there are 2 of this component now in the dom when I use
const modal = document.getElementById('modal');
the first instance of the component works as expected but the second instance is selecting the first instance not the second instance.
Is there a way to getElementById but only in this component?
You should only ever have one element with a given id in the same page, in your case you may want to use classes and use document.getElementsByClassName("classname") which is going to return an array of elements with the given class name.
Hope this helps
Instead of setting the Id as 'modal', you can pass id of your field as props and then set its id attribute.Then use getElementById for them.
Your can do like this:
<Modal title='Join' UniqId="modal1"/>
<Modal title='Login' UniqId="modal2"/>
Then in your component, you can do something like this:
<div className='modal' id={props.UniqId} onClick={toggleModal}>
After that, in your JS file, you can use this id:
const modal1 = document.getElementById('modal1');
const modal2 = document.getElementById('modal2');
The way that you are doing it looks like you are still using the mindset of using vanilla JavaScript to manipulate DOM elements after events.
Generally in the React world, you should be linking everything into your render() methods which are raised every time the state or the properties change within a react component.
So what I would do in your situation is remove all your code which is grabbing DOM elements, e.g. your document.getElementById code, and purely work from your state change. I am sorry that this won't be complete code, as I am not sure what your UI is supposed to look like.
const Modal: React.FC<propsInterface> = (props) => {
const [state, setState] = useState({ showLogin: props.title === "login" ? false : true, showModal: false, showCard: false });
function toggleLogin() {
setState({ showLogin: !state.showLogin });
}
function toggleModal() {
setState({ showModal: !state.showModal, showCard: !state.showCard });
}
const modalClassName = state.showModal ? 'modal show' : 'modal';
const cardClassName = state.showLogin ? 'card show' : 'card';
return (
<div>
<div className={modalClassName} onClick={toggleModal}>
<div className={cardClassName}>
{props.title}
{state.showLogin
? <Login toggleLogin={toggleLogin} toggleModal={toggleModal}/>
: <Join toggleLogin={toggleLogin} toggleModal={toggleModal}/>}
</div>
</div>
<div onClick={toggleModal} className='modal-title' id='modal'> {props.title}</div>
</div>
);
}
Effectively what happens is that when the toggleModal method is called, it just toggles the two state properties between true/false. This causes the component to re-render, and the class names change on the div elements.
From this, you will need to move all your animations into your css file and probably make use of animations in there (css animations are not my greatest skill). It's not a simple change to what you have, but I would always keep my styling and animations outside of a component and in a css file, and always work from rendering rather than trying to manipulate react nodes after render.

How to use react-spring from react component classes

I am trying to import react-spring animation library to a reactjs application which is based on react component classes.
It seems that new (as of 2019) React Hooks made some integration messier.
So that is why I am asking how to use react-spring which in turn uses react hooks, in a ReactJS application what uses classes.
The code that does not work properly looks like:
import React from 'react';
import { useSpring, animated, interpolate } from 'react-spring'
export default class TestAnimation extends React.Component {
constructor(props) {
super(props);
const { o, xyz, color } = useSpring({
from: { o: 0, xyz: [0, 0, 0], color: 'red' },
o: 1,
xyz: [10, 20, 5],
color: 'green'
});
this.aniText = <animated.div
style={{
// If you can, use plain animated values like always, ...
// You would do that in all cases where values "just fit"
color,
// Unless you need to interpolate them
background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
// Which works with arrays as well
transform: xyz.interpolate((x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`),
// If you want to combine multiple values use the "interpolate" helper
border: interpolate([o, color], (o, c) => `${o * 10}px solid ${c}`),
// You can also form ranges, even chain multiple interpolations
padding: o.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] }).interpolate(o => `${o}%`),
// Interpolating strings (like up-front) through ranges is allowed ...
borderColor: o.interpolate({ range: [0, 1], output: ['red', '#ffaabb'] }),
// There's also a shortcut for plain, optionless ranges ...
opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
}}
>
{o.interpolate(n => n.toFixed(2)) /* innerText interpolation ... */}
</animated.div>
};
render() {
return <div>
{this.aniText}
</div>;
}
}
which results this 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
You can't use hooks inside class components. So, you could either split out the animated component into its own functional component, which would look like this:
import React from 'react';
import { useSpring, animated, interpolate } from 'react-spring'
const AniText = ()=> {
const { o, xyz, color } = useSpring({
from: { o: 0, xyz: [0, 0, 0], color: 'red' },
o: 1,
xyz: [10, 20, 5],
color: 'green'
});
return (<animated.div
style={{
// If you can, use plain animated values like always, ...
// You would do that in all cases where values "just fit"
color,
// Unless you need to interpolate them
background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
// Which works with arrays as well
transform: xyz.interpolate((x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`),
// If you want to combine multiple values use the "interpolate" helper
border: interpolate([o, color], (o, c) => `${o * 10}px solid ${c}`),
// You can also form ranges, even chain multiple interpolations
padding: o.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] }).interpolate(o => `${o}%`),
// Interpolating strings (like up-front) through ranges is allowed ...
borderColor: o.interpolate({ range: [0, 1], output: ['red', '#ffaabb'] }),
// There's also a shortcut for plain, optionless ranges ...
opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
}}
>
{o.interpolate(n => n.toFixed(2)) /* innerText interpolation ... */}
</animated.div>)
}
export default class TestAnimation extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<AniText />
</div>;
}
}
OR, if you want to stick with a class component, react-spring exports a render-props API as well, which is completely valid inside any React component, class or otherwise:
import React from "react";
import { Spring, animated, interpolate } from "react-spring/renderprops";
export default class TestAnimation extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Spring
native
from={{ o: 0, xyz: [0, 0, 0], color: "red" }}
to={{ o: 1, xyz: [10, 20, 5], color: "green" }}
>
{({ o, xyz, color }) => (
<animated.div
style={{
// If you can, use plain animated values like always, ...
// You would do that in all cases where values "just fit"
color,
// Unless you need to interpolate them
background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
// Which works with arrays as well
transform: xyz.interpolate(
(x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`
),
// If you want to combine multiple values use the "interpolate" helper
border: interpolate(
[o, color],
(o, c) => `${o * 10}px solid ${c}`
),
// You can also form ranges, even chain multiple interpolations
padding: o
.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] })
.interpolate(o => `${o}%`),
// There's also a shortcut for plain, optionless ranges ...
opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
}}
>
{// Finally, this is how you interpolate innerText
o.interpolate(n => n.toFixed(2))}
</animated.div>
)}
</Spring>
</div>
);
}
}
Here is a codesandbox with the two solutions side-by-side:
https://codesandbox.io/s/8ynxyowzk0

error: Attempted to assign to read only property on using Animated react native

For some reason I'm not able to see what I'm doing wrong with my code. I seem to be using Animated just as the documentation shows but this error keeps coming.
The code snippet:
import React, {
Component
} from 'react';
import {
StyleSheet,
Image,
Animated,
} from 'react-native'
import Header from './../components/Header'
export default class DrawerShell extends Component {
constructor(props) {
super(props)
this.state = {
showNav: false,
}
this.anim = new Animated.Value(0)
this.openDrawer = this.openDrawer.bind(this)
}
openDrawer() {
let toValue
this.setState({
showNav: !this.state.showNav
}) !this.state.showNav ? toValue = 1 : toValue = 0
Animated.timing( // Animate value over time
this.anim, // The value to drive
{
toValue: 1, // Animate to final value of 1
duration: 300,
}
).start()
}
render() {
let {
showNav
} = this.state
return ( <
Animated.View style = {
[
styles.appContainer,
{
transform: [{
translate: [
this.anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 200],
}),
this.anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 80],
}),
0
]
},
{
scale: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.7]
})
}
]
},
]
} >
<
Image source = {
{
uri: "splash_bg"
}
}
style = {
styles.bgImage
} >
<
Header title = "hi there"
onPress = {
this.openDrawer
}
/> <
/Image>
</Animated.View>
);
}
}
Might be useful for others coming from Google. Be sure you're using animated values within animated components like Animated.View. Often overlooked when 'upgrading' a view to be animated.
Figured. Two reasons why this error was being thrown.
I was interpolating the same value multiple times. Which is not allowed.
Setting state would call interpolate once more. Which was not required.
Once I Stopped doing interpolate multiple times on the same value and made it independent of state, the error went away.
For those who come here, you need to have animated values inside <Animated.View>!
Refer to this issue:
https://github.com/facebook/react-native/issues/10716#issuecomment-258098396
i think this might work you assign value directly to the state object in returnary operator and that case the error
openDrawer() {
let toValue
this.setState({
showNav: !this.state.showNav
})
toValue = (!this.state.showNav) ? 1 : 0 // change this this line case error
Animated.timing( // Animate value over time
this.anim, // The value to drive
{
toValue: 1, // Animate to final value of 1
duration: 300,
}
).start()
}
In my case I found the animated transform: [{ scale: ...}] value needs to be applied through a style property rather than directly to the view.
This is the working, not-animated code I started from:
<View transform={[{ scale: 0.8 }]}>
...
</View>
But this throws the attempted to assign to read-only property exception:
const animVal = useRef(new Animated.Value(0.8)).current
<Animated.View transform: { scale: animVal }>
...
</Animated.View>
This works!:
const animVal = useRef(new Animated.Value(0.8)).current
<Animated.View style={{ transform: [{ scale: animVal }]}}>
...
</Animated.View>
(This is not exactly the problem the question holder had but it may help others who stumble upon this through Google)

Resources