Implementing transition effects in React JS when state changes - javascript

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;
}

Related

Overlapping images animation effect while scrolling

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;
}

React Slick slider, current active slide overlapped by next slide

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;
}

How to run animation every time onClick in React?

I want an animation that runs on every click.
However, it runs only the first time and not after the second click.
Is there a way?
const inputEl = useRef();
const onButtonClick = () => {
inputEl.current.style.animation = "0.7s ease-in-out 0s 1 testKeyword";
};
return(
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
);
#keyframes testKeyword {
0% {
opacity: 0;
transform: scale(1);
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
I have 2 different solutions depending on your needs:
Solution 1: using useRef
js file
const inputEl = useRef();
const handleButtonClick = () => {
inputEl.current.style.animation = "0.7s ease-in-out 0s 1 testKeyword";
}
const handleAnimationEnd = () => {
inputEl.current.style.animation = "none";
}
<input ref={inputEl} onAnimationEnd={handleAnimationEnd} type="text"/>
<button onClick={handleButtonClick} >Focus the input</button>
css file:
#keyframes testKeyword {
0% {
opacity: 0;
transform: scale(1);
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
Solution 2: using useState
js file:
const [clicked, setClicked] = useState(false);
<input type="text" class={clicked === true ? "animate" : null} onAnimationEnd={() => setClicked(false)}/>
<button onClick={() => setClicked(true)}>Focus the input</button>
css file:
.animate {
animation: 0.7s ease-in-out 0s 1 testKeyword;
}
#keyframes testKeyword {
0% {
opacity: 0;
transform: scale(1);
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
This should help! I am using a class that contains the animation and then I am adding it on button click, then after 700ms I am removing this class using setTimeout
class Demo extends React.Component {
handleClick() {
const inpEl = document.querySelector(".Demo-input");
inpEl.classList.add("Demo-animate");
setTimeout(() => {
inpEl.classList.remove("Demo-animate");
}, 700);
}
render() {
return (
<div className="Demo">
<input className="Demo-input" type="text" />
<button onClick={this.handleClick}> Click Me </button>
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById("root"));
.Demo-animate {
animation: 0.7s ease-in-out 0s 1 testKeyword;
}
#keyframes testKeyword {
0% {
opacity: 0;
transform: scale(1);
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
<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>
<div id="root"></div>
You are just setting the animation style property of the element once and after the animation has completed 100%, it won't start again. What you can do is add a class to the element which changes the style of the element and remove it after some time interval.
const onButtonClick = () => {
const element = document.querySelector("input");
element.classList.add("fade");
setTimeout(() => {
element.classList.remove("fade");
}, 200);
};
return (
<>
<input type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
The CSS for the same would look something like this
.fade {
animation: 0.7s ease-in-out 0s 1 testKeyword;opacity: 0;
}
#keyframes testKeyword {
0% {
opacity: 0;
transform: scale(1);
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
Note:
This implementation uses setTimeout which is an asynchronous API. It may happen at some point in time when the stack is not empty and the callback to remove the fade class is waiting in the task queue, then the code will freeze until the task is cleared. This is a rare case but it is important to know about it.
You can refer to this video on eventloop (What the heck is the event loop anyway? | Philip Roberts | JSConf EU) on youtube to understand this better. Here is the link https://youtu.be/8aGhZQkoFbQ?t=769

positioning side nav over react google maps

Hi I am making a site with google maps in Reactjs using the npm module google-map-react.
I was first using the module npm google-maps-react and have now decided to use google-map-react.
With the first npm package this was working perfectly but now my side navigation appears above the map instead of side by side.
The side nav is using react-transtion-group if that makes any difference.
It looks like this
side nav css
#mySidenav a {
position: absolute;
left: -10px;
transition: 0.3s;
padding: 9px;
text-decoration: none;
font-size: 20px;
color: black;
border-radius: 0 5px 5px 0;
border: 1px solid #000000;
}
#mySidenav a:hover {
left: 0px;
cursor: pointer;
background-color: gray;
}
#contact {
background-color: rgb(228, 225, 225);
}
/* slide enter */
.slide-enter {
opacity: 0;
transform: scale(0.97) translateX(5px);
z-index: 1;
}
.slide-enter.slide-enter-active {
opacity: 1;
transform: scale(1) translateX(0);
transition: opacity 3000ms linear 1000ms, transform 3000ms ease-in-out 1000ms;
}
/* slide exit */
.slide-exit {
opacity: 1;
transform: scale(1) translateX(0);
}
.slide-exit.slide-exit-active {
opacity: 0;
transform: scale(0.97) translateX(5px);
transition: opacity 1500ms linear, transform 1500ms ease-out;
}
.slide-exit-done {
opacity: 0;
}
App.Js
class App extends Component {
render() {
return (
<Switch>
<Route
exact
path="/reactapp"
render={() => (
<div id="wrapper">
<TopNav />
<SideNav />
<MapContainer />
</div>
)}
/>
</Switch>
);
}
}
Map
import React, { Component } from "react";
import GoogleMapReact from "google-map-react";
import { connect } from "react-redux";
import { fetchPosts, fetchItins } from "../../actions/postActions";
import PropTypes from "prop-types";
export class MapContainer extends Component {
static defaultProps = {
center: {
lat: 59.95,
lng: 30.33,
},
zoom: 11,
};
state = {
showingInfoWindow: false,
activeMarker: {},
selectedPlace: {},
};
onMarkerClick = (props, marker, e) =>
this.setState({
selectedPlace: props,
activeMarker: marker,
showingInfoWindow: true,
});
onMapClicked = (props) => {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null,
});
}
};
componentDidMount() {}
toggleWindow() {
if (this.state.showingInfoWindow) {
} else if (!this.state.showingInfoWindow) {
}
}
render() {
console.log(this.props.posts);
return (
<div style={{ height: "100vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{ key: "AIzaSyAfpKoor5CLGg-HbDwdKHq9mGij2JA-YzE" }}
defaultCenter={this.props.center}
defaultZoom={this.props.zoom}
></GoogleMapReact>
</div>
);
}
}
MapContainer.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.object.isRequired,
// newPost: PropTypes.object
};
const mapStateToProps = (state) => ({
posts: state.posts.items,
// newPost: state.posts.item
});
export default connect(mapStateToProps, { fetchPosts })(MapContainer);
Ideally I would just have this behind as like a background image and then everything else could just sit on top.
I resolved this by setting each layer to have a zindex (css). You can then load things on top of others depending on their z index.
e.g map z index = 0
sidenav z index = 1;
sidenav will appear on top

How can I check whether an element is visible with Jest?

I have a straight forward react-comp. which I want to test the styling depending on the react state. The comp looks like the following:
React-comp.
const Backdrop = ({ showBackdrop }) => {
const backdropRef = useRef();
function getBackdropHeight() {
if (typeof window !== 'undefined') {
return `calc(${document.body.clientHeight}px -
${backdropRef.current?.offsetTop || 0}px)`;
}
return 0;
}
return (
<div
data-testid="backdrop"
className={classNames(styles.backdrop, showBackdrop ? styles.show : styles.hide)}
ref={backdropRef}
style={{ height: getBackdropHeight() }}
/>
);
};
Styling
.backdrop {
width: 100%;
position: absolute;
left: 0;
right: 0;
top: 156px;
background-color: rgba(0, 0, 0, 0.7);
z-index: 3;
...
}
.show {
opacity: 0;
visibility: hidden;
transition: visibility 0.25s, opacity 0.25s ease-out;
}
.hide {
opacity: 1;
visibility: hidden;
transition: opacity 0.25s ease-in;
}
And the error that I always get from the test is, that the element is visible:
Received element is visible:
<div class="backdrop hide" data-testid="backdrop" style="height: calc(0px -
0px);" />
21 | const { getByTestId } = renderWithIntl(<Backdrop showBackdrop={false} />);
22 |
> 23 | expect(getByTestId('backdrop')).not.toBeVisible();
| ^
24 | });
25 | });
26 |
Test
it("should not render visible backdrop on falsy state", () => {
const { getByTestId } = render(<Backdrop showBackdrop={false} />);
expect(getByTestId('backdrop')).not.toBeVisible();
});
Any way on how to get the element as not visible without using react inline styling!?
You can use toBeVisible() function from RTL.
Here you have docs:
https://github.com/testing-library/jest-dom#tobevisible
Example:
// element should not be visible on screen
expect(screen.queryByText('1')).not.toBeVisible();

Categories