I got inspired to create this animation effect. What I want to achieve is that the overlapped images get a little bigger when scrolling down and then again smaller when I scroll back.
For the scrolling part I know I need to use Intersection Observer API. I think I managed it to do right but I cant get it to work. I use React Typescript with inline styling.
The original animation - Three overlaping images - getting bigger on scroll down:
Codepen
My React Code - OverlappingImages.tsx :
import React from 'react';
const styles = {
container: {
position: 'relative',
height: '400px',
margin: '0 50px',
div: {
width: '380px',
border: '1px solid #000',
overflow: 'hidden',
lineHeight: 0,
transition: 'transform .4s ease-in-out',
img: {
width: '100%',
fontSize: 0,
},
},
img1: {
left: '5%',
top: 0,
position: 'absolute',
transform: 'rotate(-4deg) translateY(20%)',
transitionDelay: '0s',
},
img2: {
left: '50%',
top: 0,
position: 'absolute',
transform: 'translate(-50%, 0)',
transitionDelay: '.1s',
zIndex: 1,
},
img3: {
right: '5%',
top: 0,
position: 'absolute',
transform: 'rotate(4deg) translateY(20%)',
transitionDelay: '.2s',
},
' &.active': {
img1: {
transform: 'rotate(-6deg) translateY(50%) scale(1.9)',
},
img2: {
transform: 'translate(-50%, -2%) scale(1.2)',
},
img3: {
transform: 'rotate(6deg) translateY(24%) scale(1.2)',
},
},
},
body: {
fontFamily: 'sans-serif',
fontSize: '48px',
fontWeight: 'bold',
letterSpacing: '1px',
margin: 0,
},
section: {
textAlign: 'center',
padding: '500px 0',
'&:nth-child(odd)': {
background: '#eee',
},
},
};
function OverlappingImages() {
const wrapper = document.querySelector('.container');
const className = 'active';
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
wrapper.classList.add(className);
return;
}
wrapper.classList.remove(className);
});
},
{
threshold: 1,
}
);
observer.observe(wrapper);
return (
<>
<section>
<p>(scroll down!)</p>
</section>
<section>
<div style={styles.container}>
<div style={styles.container.img1}>
<img src="https://via.placeholder.com/350x250" alt="img1" />
</div>
<div style={styles.container.img2}>
<img src="https://via.placeholder.com/350x250" alt="img2" />
</div>
<div style={styles.container.img3}>
<img src="https://via.placeholder.com/350x250" alt="img3" />
</div>
</div>
</section>
<section>
<p>(scroll up!)</p>
</section>
</>
);
}
export { OverlappingImages };
Here's the result:
You need to wrap your code above reutrn(), into the window.onload because if you run it in the way your currently doing it, document.querySelector('.container') is going to return nothing but null or undefined
Your container has no class or id and your trying to access it with document.querySelector('.container') again you'll get null
Make sure you assign an id or a class to it
Style.css
#container * {
transition: all .5s ease;
}
.active div:nth-child(1) {
transform: rotate(-4deg) translateY(20%) scale(1.1) !important;
}
.active div:nth-child(2) {
transform: translate(-50%, 0%) scale(1.1) !important;
}
.active div:nth-child(3) {
transform: rotate(4deg) translateY(20%) scale(1.1) !important;
}
OverlappingImages.tsx
const styles = {
container: {
position: "relative",
height: "400px",
margin: "0 50px",
padding: "30px",
transition: "all .5s ease",
img1: {
left: "5%",
top: 0,
position: "absolute",
transform: "rotate(-4deg) translateY(20%)",
transitionDelay: "0s",
},
img2: {
left: "50%",
top: 0,
position: "absolute",
transform: "translate(-50%, 0)",
transitionDelay: ".1s",
zIndex: 1,
},
img3: {
right: "5%",
top: 0,
position: "absolute",
transform: "rotate(4deg) translateY(20%)",
transitionDelay: ".2s",
},
},
whiteSpace: {
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100vh",
},
};
function OverlappingImages() {
window.onload = function () {
const wrapper = document.querySelector("#container");
const className = "active";
let preY = 0, preR = 0;
const observer = new IntersectionObserver(
entries => {
entries.forEach(e => {
const currentY = e.boundingClientRect.y;
const currentR = e.intersectionRatio;
if (currentY < preY || e.isIntersecting) {
wrapper?.classList.add(className);
} else if (currentY > preY && currentR < preR) {
wrapper?.classList.remove(className);
}
preY = currentY;
preR = currentR;
});
},
{ threshold: 0.8 }
);
observer.observe(wrapper);
};
return (
<>
<section>
<div style={styles.whiteSpace}>
<p>(scroll down!)</p>
</div>
</section>
<section>
<div style={styles.container} id="container">
<div style={styles.container.img1}>
<img src="https://via.placeholder.com/350x250" alt="img1" />
</div>
<div style={styles.container.img2}>
<img src="https://via.placeholder.com/350x250" alt="img2" />
</div>
<div style={styles.container.img3}>
<img src="https://via.placeholder.com/350x250" alt="img3" />
</div>
</div>
</section>
<section>
<div style={styles.whiteSpace}>
<p>(scroll up!)</p>
</div>
</section>
</>
);
}
export default OverlappingImages;
Second approach(using ref)
Style.css
.active div:nth-child(1) {
transform: rotate(-4deg) translateY(20%) scale(1.1) !important;
}
.active div:nth-child(2) {
transform: translate(-50%, 0%) scale(1.1) !important;
}
.active div:nth-child(3) {
transform: rotate(4deg) translateY(20%) scale(1.1) !important;
}
OverlappingImages.tsx
import {useRef, useEffect} from 'react';
const styles = {
container: {
position: "relative",
height: "400px",
margin: "0 50px",
padding: "30px",
img1: {
left: "5%",
top: 0,
position: "absolute",
transform: "rotate(-4deg) translateY(20%)",
transition: "all .5s ease",
},
img2: {
left: "50%",
top: 0,
position: "absolute",
transform: "translate(-50%, 0)",
transition: "all .5s ease .1s",
zIndex: 1,
},
img3: {
right: "5%",
top: 0,
position: "absolute",
transform: "rotate(4deg) translateY(20%)",
transition: "all .5s ease .2s",
},
},
whiteSpace: {
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100vh",
},
};
function OverlappingImages() {
const ref = useRef(null);
useEffect(()=>{
const wrapper = ref.current;
const className = "active";
let preY = 0, preR = 0;
const observer = new IntersectionObserver(
entries => {
entries.forEach(e => {
const currentY = e.boundingClientRect.y;
const currentR = e.intersectionRatio;
if (currentY < preY || e.isIntersecting) {
wrapper?.classList.add(className);
} else if (currentY > preY && currentR < preR) {
wrapper?.classList.remove(className);
}
preY = currentY;
preR = currentR;
});
},
{ threshold: 0.8 }
);
observer.observe(wrapper);
},[])
return (
<>
<section>
<div style={styles.whiteSpace}>
<p>(scroll down!)</p>
</div>
</section>
<section>
<div ref={ref} style={styles.container}>
<div style={styles.container.img1}>
<img src="https://via.placeholder.com/350x250" alt="img1" />
</div>
<div style={styles.container.img2}>
<img src="https://via.placeholder.com/350x250" alt="img2" />
</div>
<div style={styles.container.img3}>
<img src="https://via.placeholder.com/350x250" alt="img3" />
</div>
</div>
</section>
<section>
<div style={styles.whiteSpace}>
<p>(scroll up!)</p>
</div>
</section>
</>
);
}
export default OverlappingImages;
The styled-components approach:
I created dummy data for the loop.
Created simple components for section, figure and img. I used figure as a wrapper.
Replaced all necessary style from img to figure and changed styled logic from position: absolute to grid solution. It will allow us to keep the images in the center of the screen if screen size is large and make it flexible for the small screens.
The PictureWrapper (figure) can pass 2 props, position and state.
OverlappingImages.tsx
import { useRef, useEffect, useState, useMemo } from "react";
import styled, { css } from "styled-components";
import data from "./data";
export type TypePosition = "left" | "center" | "right";
interface IProps {
position: TypePosition;
active: boolean;
}
const Image = styled.img`
width: 100%;
height: auto;
`;
// Left image wrapper style with active, inactive state
const left = (active: boolean) => css`
${!active && css`transform: rotate(-4deg) translateX(calc(-1 * clamp(25%, 20vw, 75%)));`}
${active && css`transform: rotate(-6deg) translateX(calc(-1 * clamp(25%, 20vw, 75%))) scale(1.2);`}
transition-delay: 0s;
z-index: 1;
`;
// Center image wrapper style with active, inactive state
const center = (active: boolean) => css`
${active && css`transform: scale(1.2);`}
transition-delay: 0.1s;
z-index: 2;
`;
// Right image wrapper style with active, inactive state
const right = (active: boolean) => css`
${!active && css`transform: rotate(4deg) translateX(clamp(25%, 20vw, 75%));`}
${active && css`transform: rotate(6deg) translateX(clamp(25%, 20vw, 75%)) scale(1.2);`}
transition-delay: 0.2s;
z-index: 1;
`;
// Image wrapper component with 2 props:
// position: left | center | right
// active: true / false
const PictureWrapper = styled.figure<IProps>`
grid-column: 1;
grid-row: 1;
width: clamp(200px, 40vw, 380px);
display: flex;
border: 1px solid #000;
transition: transform 0.4s ease-in-out;
${({ position, active }) => position === "left" && left(active)}
${({ position, active }) => position === "center" && center(active)}
${({ position, active }) => position === "right" && right(active)}
`;
const Container = styled.section`
display: grid;
place-content: center;
position: relative;
margin: 0 50px;
`;
export const OverlappingImages = () => {
const [active, setActive] = useState(false);
const ref = useRef<HTMLElement>(null);
const callback = (entries: IntersectionObserverEntry[]) => {
const [entry] = entries;
if (entry.isIntersecting) {
setActive(entry.isIntersecting);
return;
}
setActive(false);
};
const options = useMemo(() => ({
root: null,
rootMargin: "0px",
threshold: 0.75
}), []);
useEffect(() => {
const container = ref.current;
// Observer with external callback function and options
const observer = new IntersectionObserver(callback, options);
if (container) observer.observe(container);
//cleanup when a component unmounted
return () => {
if (container) observer.unobserve(container);
};
}, [ref, options]);
const images = data.map((img) => {
return (
<PictureWrapper key={img.id} position={img.position} active={active}>
<Image src={img.image} />
</PictureWrapper>
);
});
return <Container ref={ref}>{images}</Container>;
};
data.ts
import { TypePosition } from "./OverlappingImages";
interface IData {
id: string;
image: string;
position: TypePosition;
}
export const data: IData[] = [
{
id: "d4a54w5s1d2sd24",
image: "https://via.placeholder.com/350x250",
position: "left"
},
{
id: "ad4e5qe4545d7ew4",
image: "https://via.placeholder.com/350x250",
position: "center"
},
{
id: "das54w5e1sa2dw5e5",
image: "https://via.placeholder.com/350x250",
position: "right"
}
];
export default data;
App.tsx
import "./styles.css";
import { OverlappingImages } from "./OverlappingImages";
export default function App() {
return (
<div className="App">
<section>
<p>(scroll down!)</p>
</section>
<OverlappingImages />
<section>
<p>(scroll up!)</p>
</section>
</div>
);
}
sections style
section {
display: grid;
place-content: center;
min-height: 100vh;
text-align: center;
}
section:nth-child(odd) {
background: #eee;
}
I'm currently using Slick in order to make a carousel.
I'm having two issues right now, let's start with the first one.
1)
I'm currently using a slider in which i want to show 3 slides: the current image (Spyro), the previous one (Crash) and the next one (Tekken).
As you see, while the current slide correctly overlaps the previous one (Spyro > Crash), the next one overlaps the current slide (Tekken > Spyro).
Of course i want the current slide to be on top of both of them... How can i fix this?
I'm attacching the code below.
App.js
import "./App.css";
import { useEffect, useState } from "react";
import Slider from "react-slick";
import SliderData from "./SliderData";
import { AiOutlineArrowLeft, AiOutlineArrowRight } from "react-icons/ai";
function useWindowSize() {
const [size, setSize] = useState([window.innerHeight, window.innerWidth]);
useEffect(() => {
const handleResize = () => setSize([window.innerHeight, window.innerWidth]);
window.addEventListener("resize", handleResize);
}, [])
return size;
}
const array = SliderData.map((x) => {
return x.image;
})
console.log(array);
function App() {
const NextArrow = ({ onClick }) => {
return (
<div className="arrow next" onClick={onClick}>
<AiOutlineArrowRight />
</div>
);
};
const PrevArrow = ({ onClick }) => {
return (
<div className="arrow prev" onClick={onClick}>
<AiOutlineArrowLeft />
</div>
);
};
const [imageIndex, setImageIndex] = useState(0);
const [height, width] = useWindowSize();
const settings = {
className: "center",
infinite: true,
lazyLoad: true,
speed: 300,
slidesToShow: width > 1000 ? 3: 1,
centerMode: true,
centerPadding: "60px",
nextArrow: <NextArrow />,
prevArrow: <PrevArrow />,
beforeChange: (current, next) => {
console.log(current);
setImageIndex(next);
}
};
return (
<div className="App">
<Slider {...settings}>
{array.map((img, idx) => (
<div className={idx === imageIndex ? "slide activeSlide" : "slide"}>
<img src={img} alt={img} />
</div>
))}
</Slider>
</div>
);
}
export default App;
App.css
#import "~slick-carousel/slick/slick.css";
#import "~slick-carousel/slick/slick-theme.css";
.App {
width: 100%;
margin: 10rem auto;
height: 570px;
}
.slide img {
width: 35rem;
align-items: center;
margin: 0 auto;
z-index: 1;
}
.slide {
transform: scale(0.8);
transition: transform 300ms;
opacity: 0.5;
z-index: -1;
}
.activeSlide {
transform: scale(1.1);
align-items: center;
opacity: 1;
}
.arrow {
background-color: #fff;
position: absolute;
cursor: pointer;
z-index: 10;
}
.arrow svg {
transition: color 300ms;
}
.arrow svg:hover {
color: #68edff;
}
.next {
right: 3%;
top: 50%;
}
.prev {
left: 3%;
top: 50%;
}
SliderData.js
const SliderData = [
{
image:
"https://www.spaziogames.it/wp-content/uploads/2020/06/Crash-4-Pirate_06-29-20.jpg"
},
{
image:
"https://d2skuhm0vrry40.cloudfront.net/2018/articles/2018-07-18-14-24/news-videogiochi-spyro-reignited-trilogy-video-di-gameplay-livello-colossus-1531920251281.jpg/EG11/thumbnail/750x422/format/jpg/quality/60"
},
{
image: "https://i.ytimg.com/vi/OUh82pOFGDU/maxresdefault.jpg"
},
{
image: "https://www.psu.com/wp/wp-content/uploads/2020/07/MetalGearSolidRemake-1024x576.jpg"
}
];
export default SliderData;
2)
As you see, the active slide is not perfectly centered. Since i suck in CSS i did not use the display: flex; command.
What do you suggest? how can i fix this?
Thank you all.
You need to apply a position element to .slide for the z-index to work properly.
Note: z-index only works on positioned elements (position: absolute, position: relative, position: fixed, or position: sticky) and flex items (elements that are direct children of display:flex elements).
You can read more on z-index here
This is my answer for this issue. Basically you should set position and z-index for every item and set higher z-index for current active item
.App .slick-slide {
position: relative;
z-index: 1;
/* your choice, but make sure z-index of active slide is higher than this value */
}
.App .slick-slide.slick-current {
z-index: 10;
}
In REACTJS, I am working with creating a simple App that contains Transitions. I have imported CSSTransitions and Group Transition in my file but when I am trying to apply CSSTransition for some of my news item but I am not getting the animation. It is as if it doesn't even exist.
I can see that my items are wrapped inside the component, but I cannot get them to animate.
Could someone please help me figure out what I'm doing wrong?
import React, { Component } from 'react';
import {CSSTransition, TransitionGroup} from 'react-transition-group';
import {Link} from 'react-router-dom';
import Axios from 'axios';
import {URL} from '../../../Config';
import styles from './NewsList.module.css';
export default class NewsList extends Component {
state={
items:[],
start: this.props.start,
end: this.props.start+this.props.amount,
amount: this.props.amount
}
componentWillMount(){
this.request(this.state.start,this.state.end)
}
request=(start,end)=>{
Axios.get(`${URL}/articles?_start=${start}&_end=${end}`)
.then(response=>{
this.setState({
items:[...this.state.items,...response.data]
})
})
}
loadMore=()=>{
let end = this.state.end + this.state.amount
this.request(this.state.end, end)
}
renderNews=(type)=>{
let template= null;
switch(type){
case('Card'):
template= this.state.items.map((item, i)=>(
<CSSTransition
classNames={{
enter: styles.newList_wrapper,
enterActive: styles.newList_wrapper_enter
}}
timeout= {500}
key={i}
>
<div>
<div className={styles.newslist_item}>
<Link to={`/articles/${item.id}`}>
<h2>{item.title}</h2>
</Link>
</div>
</div>
</CSSTransition>
)
);
break;
default:
template = null;
}
return template;
}
render() {
return (
<div>
<TransitionGroup
component="div"
className="list"
>
{this.renderNews(this.props.type)}
</TransitionGroup>
<div onClick={this.loadMore}>
Load More
</div>
</div>
);
}
}
.newslist_item{
border: 1px solid #f2f2f2;
background: #ffffff;
margin-top: 0px;
padding: 8px 5px 0 5px;
}
.newslist_item h2{
font-size: 13px;
line-height: 21px;
margin: 5px 0;
color: #525252
}
.newslist_item a {
text-decoration:none;
}
.newsList_wrapper{
box-sizing: border-box;
opacity: 0;
transform: translateX(-100%);
transition: all .5s ease-in;
}
.newsList_wrapper_enter{
opacity: 1;
transform: translateX(0%);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
classNames={{
enter: **styles.newsList_wrapper**,
enterActive: **styles.newsList_wrapper_enter**
There was a typo with the classnames. An S was missing.
I am using React Context in order to manage a global state.
So I have defined my Context with its Provider and its Consumer.
I have my videoplaying-context.js
import React from "react";
import { createContext } from 'react';
// set the defaults
const VideoContext = React.createContext({
videoPlaying: false,
setPlayingVideo: () => {}
});
export default VideoContext;
In my _app.js I have:
import App from 'next/app'
import { PageTransition } from 'next-page-transitions'
import VideoContext from '../components/videoplaying-context'
class MyApp extends App {
setPlayingVideo = videoPlaying => {
this.setState({ videoPlaying });
};
state = {
videoPlaying: false,
setPlayingVideo: this.setPlayingVideo
}
render() {
console.log('new _app.js defalt page');
const { Component, pageProps, router, state } = this.props
return (
<React.Fragment>
<VideoContext.Provider value={this.state}>
<PageTransition timeout={300} classNames="page-transition">
<Component {...pageProps} key={router.route} />
</PageTransition>
</VideoContext.Provider>
</React.Fragment>
)
}
}
export default MyApp
and then in one of my file I have put the Consumer:
import Layout from "../components/Layout";
import ReactPlayer from 'react-player
import VideoContext from '../components/videoplaying-context'
class Video extends React.Component {
constructor(props) {
super(props);
this.triggerVideo = this.triggerVideo.bind(this);
}
triggerVideo(event) {
console.log("click");
/* doing other stuff here... */
}
render() {
return (
<VideoContext.Consumer>
{context => (
<Layout>
<h1>Videos</h1>
<div>
<div className="group" id="process-video">
<div
className="poster-image"
onClick={() => {
this.triggerVideo.bind(this);
context.setPlayingVideo(true);
}}
/>
<ReactPlayer
url="https://vimeo.com/169599296"
width="640px"
height="640px"
config={{
vimeo: {
playerOptions: {
thumbnail_url: "http://placehold.it/640x640.jpg",
thumbnail_width: 640,
thumbnail_height: 640
}
}
}}
/>
</div>
</div>
<style jsx global>{`
.group {
position: relative;
height: 0;
overflow: hidden;
height: 640px;
width: 640px;
}
.poster-image {
background: url("http://placehold.it/640x640.jpg") center center;
background-size: cover;
bottom: 0;
left: 0;
opacity: 1;
position: absolute;
right: 0;
top: 0;
z-index: 10;
height: 640px;
width: 640px;
transition: all 0.4s ease-in;
}
.poster-image + div {
position: absolute;
top: 0;
left: 0;
width: 640px;
height: 640px;
}
.poster-image.video--fadeout {
opacity: 0;
}
`}</style>
</Layout>
)}
</VideoContext.Consumer>
);
}
}
export default Video;
So, the function "context.setPlayingVideo(true)" is working fine and it's correctly setting the global state "videoPlaying" to true, but, after the introduction of the Context, "this.triggerVideo.bind(this);" is not working anymore because "this" is undefined.
I tried removing it and other stuff but I'm really stuck and I don't know hot to fix it.
Thanks everyone!
On this line you are not calling the method triggerVideo
onClick={() => { this.triggerVideo.bind(this); context.setPlayingVideo(true); }}
Change to:
onClick={() => { this.triggerVideo(); context.setPlayingVideo(true); }}
or to:
onClick={() => { this.triggerVideo.bind(this)(); context.setPlayingVideo(true); }}
I have an image on a React page. When the state is updated to a new image I want to perform the following transition effect:
The original image should zoom in and fade out
The new image should also zoom in and fade in
The effect should look similar to passing through a wall to a new scene.
How am I able to do this in React?
As #pgsandstrom mentioned, React Transition Group is the way to go. Unfortunately, it's not very developer friendly (pretty steep learning curve).
Here's a working example: https://codesandbox.io/s/6lmv669kz
✔ Original image zooms in while fading out
✔ New image zooms in while fading in
TransitionExample.js
import random from "lodash/random";
import React, { Component } from "react";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import uuid from "uuid/v1";
const arr = [
{
id: uuid(),
url: `https://loremflickr.com/600/100?lock=${random(0, 999)}`
},
{
id: uuid(),
url: `https://loremflickr.com/600/100?lock=${random(0, 999)}`
},
{
id: uuid(),
url: `https://loremflickr.com/600/100?lock=${random(0, 999)}`
}
];
export default class TransitionExample extends Component {
state = {
index: 0,
selected: arr[0]
};
nextImage = () =>
this.setState(prevState => {
const newIndex = prevState.index < arr.length - 1 ? prevState.index + 1 : 0;
return {
index: newIndex,
selected: arr[newIndex]
};
});
render = () => (
<div className="app">
<div style={{ marginBottom: 30, height: 100 }}>
<TransitionGroup>
<CSSTransition
key={this.state.selected.id}
timeout={1000}
classNames="messageout"
>
<div style={{ marginTop: 20 }}>
<img className="centered-image" src={this.state.selected.url} />
</div>
</CSSTransition>
</TransitionGroup>
</div>
<div style={{ textAlign: "center" }}>
<button
className="uk-button uk-button-primary"
onClick={this.nextImage}
>
Next Image
</button>
</div>
</div>
);
}
styles.css
.app {
margin: 0 auto;
overflow: hidden;
width: 700px;
height: 800px;
}
.centered-image {
display: block;
margin: 0 auto;
}
/* starting ENTER animation */
.messageout-enter {
position: absolute;
top: 0;
left: calc(13% + 5px);
right: calc(13% + 5px);
opacity: 0.01;
transform: translateY(0%) scale(0.01);
}
/* ending ENTER animation */
.messageout-enter-active {
opacity: 1;
transform: translateY(0%) scale(1);
transition: all 1000ms ease-in-out;
}
/* starting EXIT animation */
.messageout-exit {
opacity: 1;
transform: scale(1.01);
}
/* ending EXIT animation */
.messageout-exit-active {
opacity: 0;
transform: scale(4);
transition: all 1000ms ease-in-out;
}
It sounds like you are looking for React Transition Group. It is the "official" way of solving these issues. Specifically I think this is what you should use. It can be a bit tricky to get a hang of, but it is really nice and powerful once you understand it.
This worked for me (link):
index.js:
import React from "react";
import { render } from "react-dom";
import "./styles.scss";
const src1 =
"https://www.nba.com/dam/assets/121028030322-james-harden-traded-102712-home-t1.jpg";
const src2 = "https://www.nba.com/rockets/sites/rockets/files/wcwebsite.jpg";
var state = {
toggle: true
};
class App extends React.Component {
render() {
const cn1 = "imgFrame " + (state.toggle ? "toggleOut" : "toggleIn");
const cn2 = "imgFrame " + (state.toggle ? "toggleIn" : "toggleOut");
return (
<div>
<img className={cn1} src={src1} alt={"img1"} />
<img className={cn2} src={src2} alt={"img2"} />
<button
onClick={() => {
state.toggle = !state.toggle;
this.forceUpdate();
}}
>
click me to toggle
</button>
<h1>Hello</h1>
</div>
);
}
}
render(<App />, document.getElementById("app"));
style.scss:
html,
body {
background-color: papayawhip;
font-family: sans-serif;
h1 {
color: tomato;
}
}
#keyframes fadeout {
0% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.9);
}
}
#keyframes fadein {
0% {
opacity: 0;
transform: scale(1.1);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.toggleOut {
animation: fadeout 500ms;
opacity: 0;
}
.toggleIn {
animation: fadein 500ms;
opacity: 1;
}
.imgFrame {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 200px;
}
button {
position: absolute;
top: 220px;
}
Wrap with a simple <Animate on={value} /> component that triggers an animation when value changes and is not undefined.
function Animate({ children, on }) {
return (on === undefined)
? <div>{children}</div>
: <div className="fade-in" key={on}>{children}</div>
}
import { useEffect, useState } from 'react'
function TestAnimate() {
const [value, setValue] = useState() // undefined
// update value every second
useEffect(() => {
setInterval(() => setValue(new Date().toLocaleString()), 1_000)
}, [])
return (
<Animate on={value}>
Value: {value}
</Animate>
)
}
#keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 500ms ease-in-out;
}