Start animation in react-spring when component is in view - javascript

I am using react-spring for animation and all the animations start once the page is loaded. I want to control the start of the animation. The desired outcome is to let the components down in the screen start the animation once they are in view (i.e the user scrolled down). The code follows something like this :
const cols = [
/*Components here that will be animated ..*/
{component: <div><p>A<p></div> , key:1},
{component: <div><p>B<p></div> , key:2},
{component: <div><p>C<p></div> , key:3},
]
export default function foocomponent(){
const [items, setItems] = React.useState(cols);
const [appear, setAppear] = React.useState(false); // Should trigger when the component is in view
const transitions = useTransition(items, (item) => item.key, {
from: { opacity: 0, transform: 'translateY(70px) scale(0.5)', borderRadius: '0px' },
enter: { opacity: 1, transform: 'translateY(0px) scale(1)', borderRadius: '20px', border: '1px solid #00b8d8' },
// leave: { opacity: 1, },
delay: 200,
config: config.molasses,
})
React.useEffect(() => {
if (items.length === 0) {
setTimeout(() => {
setItems(cols)
}, 2000)
}
}, [cols]);
return (
<Container>
<Row>
{appear && transitions.map(({ item, props, key }) => (
<Col className="feature-item">
<animated.div key={key} style={props} >
{item.component}
</animated.div>
</Col>
))}
</Row>
</Container>
);
}
I tried using appear && transitions.map(...) but unfortunately that doesn't work. Any idea how should I control the start of the animation based on a condition?

I use https://github.com/civiccc/react-waypoint for this type of problems.
If you place this hidden component just before your animation. You can switch the appear state with it. Something like this:
<Waypoint
onEnter={() => setAppear(true) }
/>
You can even specify an offset with it. To finetune the experience.

If you wish to have various sections fade in, scroll in, whatever on enter, it's actually very simple to create a custom wrapper. Since this question is regarding React Spring, here's an example but you could also refactor this a little to use pure CSS.
// React
import { useState } from "react";
// Libs
import { Waypoint } from "react-waypoint";
import { useSpring, animated } from "react-spring";
const FadeIn = ({ children }) => {
const [inView, setInview] = useState(false);
const transition = useSpring({
delay: 500,
to: {
y: !inView ? 24 : 0,
opacity: !inView ? 0 : 1,
},
});
return (
<Waypoint onEnter={() => setInview(true)}>
<animated.div style={transition}>
{children}
</animated.div>
</Waypoint>
);
};
export default FadeIn;
You can then wrap any component you want to fade in on view in this FadeIn component as such:
<FadeIn>
<Clients />
</FadeIn>
Or write your own html:
<FadeIn>
<div>
<h1>I will fade in on enter</h1>
</div>
</FadeIn>

Related

Adding a div to the bottom of a list view pushes all view upward out of viewport

I am implementing a chat view in React,and the desired behavior is that whenever new data is pushed into the dataSource, the chat view (an infinite list) scrolls to the bottom, there are many implementations, some found here: How to scroll to bottom in react?. However when I try to implement it, I am getting this strange behavior where all the views in the window are "pushed upward" out of sight by some 300px, as if to accomadate this new <div> that is at the bottom of the list view. My implementation below:
import React, {useEffect, useRef} from "react";
import { createStyles, makeStyles, Theme } from "#material-ui/core/styles";
import InfiniteScroll from 'react-infinite-scroll-component';
const row_1 = 2.5;
const row_chat = 4
const useStyles = makeStyles((theme: Theme) => createStyles({
container: {
width: '40vw',
height: `calc(100vh - 240px)`,
position: 'relative',
padding: theme.spacing(3),
},
}));
const chat_container_style = {
width: '40vw',
height: `calc(100vh - 240px - ${row_chat}vh - ${row_1}vh)`,
}
function ChatView(props) {
const classes = useStyles();
const { _dataSource } = props;
// scroll to bottom
const messagesEndRef = useRef(null)
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
}
useEffect(() => {
scrollToBottom()
}, [_dataSource]);
return (
<div className={classes.container}>
{/* chat window */}
<InfiniteScroll
dataLength={_dataSource.length}
next={() => { return }}
hasMore={true}
loader={<></>}
style={chat_container_style}
>
{_dataSource.map((item, index) => {
return (
<div {...props} key={index} item={item}>
{`item: ${index}`}
</div>
)
})}
{/* putting an item here push all divs upward */}
<div ref={messagesEndRef} />
</InfiniteScroll>
</div>
)
}
Note the use of <InfiniteScroll/> is not the cause of the behavior, Really if I put the ref={messagesEndRef} into any view, it pushes all the views up in the viewport.
The issue has been resolved. The source of the issue is the scrollIntoView function, it's scrolling the entire page instead of just the listView, here's the correct scrollIntoView function with the correct parameters:
const scrollDivRef = createRef();
useEffect(() => {
scrollDivRef.current?.scrollIntoView({
block : 'nearest',
inline : 'start',
behavior: 'smooth',
})
}, [_dataSource.length]);
Here's how the ref is nested inside the DOM:
<InfiniteScroll
next={() => { return }}
hasMore={true}
loader={<></>}
style={chat_container_style}
dataLength={_dataSource.length}
>
{_dataSource.map((item, index) => (
<BubbleView {...props} key={index} item={item}/>
))}
<div style={refDivStyle} ref={scrollDivRef}/>
</InfiniteScroll>
This problem has nothing to do w/ how I layout the style sheet.

How to animate UI in react?

const animate_boxes = () => {
inner_ref.current.style.transform = "scale(0)";
setTimeout(() => {
if (inner_ref && inner_ref.current) {
inner_ref.current.style.transform = "scale(1)";
}
}, 200);
};
useEffect(() => {
animate_boxes();
}, [trigger])
Currently, that is how I do it.
Is is this the standard/good practice way?
If not, how can I re write the code above to simplify?
There's an excellent hooks based animation library called react-spring, you can use it by itself or alongside a gesture library to create nice, physics based animations that look natural. It has a bit of a learning curve though, here's the library's website
Another way you can animate is by utilizing the CSS transition attribute with react state and inline styles:
https://codesandbox.io/s/react-playground-forked-3t3p6?file=/Hello.js
import React, { useEffect, useState } from "react";
const blueSquare = {
width: "25px",
height: "25px",
backgroundColor: "blue",
transition: "opacity 0.5s, transform 3s",
margin: 20
};
const Hello = () => {
const [opacity, setOpacity] = useState(1);
const [transform, setTransform] = useState("translate(0,0) scale(1)");
useEffect(() => {
setTransform("translate(100px, 150px) scale(8)");
setTimeout(() => {
setTransform("translate(300px, 150px) scale(8)");
}, 3000);
}, []);
return (
<div>
<button
onClick={() => setOpacity((prevState) => (prevState === 1 ? 0 : 1))}
>
Animate Opacity
</button>
<div style={{ ...blueSquare, transform, opacity }} />
</div>
);
};
export default Hello;
But as Brandon mentioned if you want to do complex animations it'd most likely be easier looking into react-spring.

How to add Custom buttons group outside slider React.js

I work with react-multi-carousel, and i try to create cusstom button outside slider, but when i create custom buttons, i have some errors:
I use this code:
import React from "react";
import "./Cards.css";
import Carousel from "react-multi-carousel";
import classes from "./CarouselCards.module.css"
import ProductionCards from "./ProductionCards/ProductionCards.jsx"
const responsive = {
desktop: {
breakpoint: { max: 3000, min: 1024 },
items: 4,
paritialVisibilityGutter: 140
},
tablet: {
breakpoint: { max: 1024, min: 464 },
items: 2,
paritialVisibilityGutter: 50
}
};
const ButtonGroup = ({ next, previous, goToSlide, ...rest }) => {
const { carouselState: { currentSlide } } = rest;
return (
<div className="carousel-button-group"> // remember to give it position:absolute
<ButtonOne className={currentSlide === 0 ? 'disable' : ''} onClick={() => previous()} />
<ButtonTwo onClick={() => next()} />
<ButtonThree onClick={() => goToSlide(currentSlide + 1)}> Go to any slide </ButtonThree>
</div>
);
};
const CarouselTabs = ({ data }) => {
return (
<Carousel
ssr
itemClass="image-item"
responsive={responsive}
className={classes.card}
minimumTouchDrag={80}
infinite={true}
rrows={false} renderButtonGroupOutside={true} customButtonGroup={<ButtonGroup />}
>
{data.map(item => <ProductionCards product={item} />)}
</Carousel>
);
};
export default CarouselTabs;
How i can fix this issues and have buttons outside slider?
From the error it is evident that you haven't defined ButtonOne, ButtonTwo & ButtonThree anywhere but they are being used.
From the documentation of react-multi-carousel we can customise the dots or arrows and can be customised by providing a custom component as a prop customButtonGroup.
In your implementation a custom button group is provided but the components(ButtonOne, ButtonTwo & ButtonThree) used inside are never declared.
So, you can define a custom button group something like below
const CustomButtonGroup = ({ next, previous, goToSlide, carouselState }) => {
const { totalItems, currentSlide } = carouselState;
return (
<div className="custom-button-group">
<div>Current slide is {currentSlide}</div>
<button onClick={() => previous()}>Previous slide</button>
<button onClick={() => next()}>Next slide</button>
<button
onClick={() => goToSlide(Math.floor(Math.random() * totalItems + 1))}
>
Go to a random slide
</button>
</div>
);
};
The above example is directly taken from react-multi-carousel stories, and can be modified as per the need.
I have created a sample sandbox below is the link

Update React Native slider on initial load without affecting animation

I'm creating a React Native checkbox/switch component (similar to a generic iOS switch).
The animation and functionality is working as required, but I'm having an issue with the initial view state.
When rendering the component, it's set in the "off" state initially. I can set an explicit left value to resolve this (e.g setting a dynamic left value based on the state of checked), but that will remove the Animated effect that's applied when the checkbox is changed.
Is there a way to update the checkbox with an initial state (true/false) without affecting the animation?
The component is below. It received two props presently (checked = a bool, handleChange = function to change the status of checked from the parent component).
const CheckboxToggle = ({ checked, handleChange }) => {
const xAnimation = useRef(new Animated.Value(0)).current;
const startThumbAnimation = () => {
Animated.timing(xAnimation, {
toValue: checked ? 0 : 50,
duration: 300,
useNativeDriver: false,
}).start();
};
const animatedThumbStyles = {
transform: [
{
translateX: xAnimation,
}
]
}
return (
<TouchableWithoutFeedback
onPress={() => {
handleChange();
startThumbAnimation();
}}
>
<View>
<Track />
<AnimatedThumb
checked={checked}
style={animatedThumbStyles}
>
{
checked
? <CheckboxIconTrue source={ic_switch_on} />
: <CheckboxIconFalse source={ic_switch_off} />
}
</AnimatedThumb>
</View>
</TouchableWithoutFeedback>
)
}
export default CheckboxToggle;
You can implement this way:
const CheckboxToggle = ({ defaultCheck, checked, handleChange }) => {
//using defaultCheck to conditionally set the value.
const xAnimation = useRef(new Animated.Value(defaultCheck ? 50 : 0)).current;
const startThumbAnimation = () => {
Animated.timing(xAnimation, {
toValue: checked ? 0 : 50,
duration: 300,
useNativeDriver: false,
}).start();
};
const animatedThumbStyles = {
transform: [
{
translateX: xAnimation,
}
]
}
return (
<TouchableWithoutFeedback
onPress={() => {
handleChange();
startThumbAnimation();
}}
>
<View>
<Track />
<AnimatedThumb
checked={checked}
style={animatedThumbStyles}
>
{
checked
? <CheckboxIconTrue source={ic_switch_on} />
: <CheckboxIconFalse source={ic_switch_off} />
}
</AnimatedThumb>
</View>
</TouchableWithoutFeedback>
)
}
export default CheckboxToggle;
I would like to point out that you are using useNativeDriver = false, this may lead to slow animations.
Also, React-Native has a Switch Component. Why don't you use that?
https://reactnative.dev/docs/switch

React-trancition-group. Transition does not work after migration from .v1 to .v2

I'm trying to migrate my app from old one to the new React-trancition-group API, but it's not so easy in case of using the manual <Transition> mode for transition creation of the particular React component.
My animation logic is:
we have an array of the components, each child of him comes one by one in the <TransitionGroup> API by onClick action. Where every new income component smoothly replace and hide previous one, which is already present in <Transition> API.
I almost finish unleash this tangle in react-trancition-group .v2, but one thing is still not solved - the component, that already been in <Transition> API does not disappear after the new one is overlayed him, which was automatically happen in react-trancition-group .v1 instead. So now they all just stack together...
So, maybe you can look on my code and luckly say where is my problem located...
I'll be grateful for any help. Thanks for your time
My code:
import React, { Component } from 'react'
import { findDOMNode } from 'react-dom'
import { TweenMax, Power1 } from 'gsap'
import { Transition } from 'react-transition-group'
class Slider extends Component {
_onEntering = () => {
const { id, getStateResult, isCurrentRow, removeAfterBorder, calcHeight } = this.props
const el = findDOMNode(this)
const height = calcHeight(el)
TweenMax.set(el.parentNode, {
height: `${height}px`
})
TweenMax.fromTo(
el,
0.5,
{
y: -120,
position: 'absolute',
width: `${100}%`,
zIndex: 0 + id
},
{
y: 0,
width: `${100}%`,
zIndex: 0 + id,
ease: Power1.easeIn
}
)
}
_onEntered = () => {
const { activeButton, removeAfterBorder, getCurrentOutcome } = this.props
findDOMNode(this)
}
_onExiting = () => {
const el = findDOMNode(this)
TweenMax.to(el, 2, {
onComplete: () => {
el.className = ''
}
})
}
_onExited = () => {
const { getStateResult } = this.props
getStateResult(true)
}
render() {
const { children } = this.props
return (
<Transition
in={true}
key={id}
timeout={2000}
onEntering={() => this._onEntering()}
onEntered={() => this._onEntered()}
onExiting={() => this._onExiting()}
onExited={() => this._onExited()}
unmountOnExit
>
{children}
</Transition> || null
)
}
}
export default Slider
```
So, you problem is very typically. As you written here: the component, that already been in <Transition> API does not disappear after the new one is overlayed him - it's happen because you does not changing the status flag in for Transition Component. You set always true for him, it's not a right.
By the way, you need to understand what is you trying to do in your code. There is a big difference between methods <Transition></Transition> and <TransitionGroup><CSSTransition></CSSTransition><TransitionGroup>. You need to use pure <Transition></Transition> API only for very rare cases, when you need explicitly manipulate animation scenes.
As I see in you code you trying to replace one component by the other and in this case you need to use the second method that I have provided above.
So, try this:
import { TransitionGroup, CSSTransition } from 'react-transition-group'
...some code
return (
<TransitionGroup>
<CSSTransition
key={id}
timeout={2000}
onEntering={() => this._onEntering()}
onEntered={() => this._onEntered()}
onExiting={() => this._onExiting()}
onExited={() => this._onExited()}
unmountOnExit
>
{children}
</CSSTransition> || null
</TransitionGroup>
)
It should help you and start to render Compenents by normal overlaying each other.

Categories