I'm currently creating my custom implementation of a modal. All works perfectly fine so far but I can't seem to animate it and I can't get my head around it.
This is my Modal component
import React from 'react'
import Slider from './Slider'
import {IoIosCloseCircleOutline} from "react-icons/io"
import styled from "styled-components";
export default function Modal(props) {
const Modal = styled.div `
transform: translateX(${({animateSlideInRight}) => (animateSlideInRight ? "0" : "100vw")});
transition: transform 1s;
width: 1000px;
height: 650px;
z-index: 100;
position: fixed;
background: white;
transition: all 1.1s ease-out;
box-shadow:
-2rem 2rem 2rem rgba(black, 0.2);
visibility: visible;
display: flex;
border-bottom-right-radius: 100px;
`
const closeModal = () => {
props.setShow(false)
}
const data = props.data
if (!props.show) {
return null
}
return (
<div className="modalWrapper">
<Modal className="modal" id="modal" animateSlideInRight = {props.show}>
<div className="modalHeaderWrapper">
<IoIosCloseCircleOutline className="modalCloseCross" onClick={closeModal}/>
<img src={data[0].logo} alt="logo" />
<h2>{data[0].title}</h2>
</div>
<div className="modalRightFlex">
<Slider
images={[data[0].image1Carrousel, data[0].image2Carrousel, data[0].image3Carrousel]}
/>
<div className="modalRightDescription">
<h1>Description</h1>
<p>{data[0].description}</p>
<h1>Technologies</h1>
<div className="modalTechnologiesWrapper">
{data[0].technologiesUsed.map((image) => {
return <img src={image}/>
})}
</div>
</div>
</div>
</Modal>
</div>
)
}
As you see my modal is a styledComponent that defines whether to translate in X or not depending on the show state. In my scenario I had to lift up state since I'm opening this modal from clicking on a card which in itself is a different component, so their ancestor is taking care of the states.
My current CSS for modal is as seen in the styled div.
Things I have tried
1-tried having a regular div and handle the animation through CSS with keyframes --> It works for sliding in but it doesn't when I close (in that instance I had my show state defining a class name for the modal with a different animation for each of them)
2-tried to set a animate state and define the className based on whether that state is true or false. It works the first time when I close (despite having to introduce a timeout of the animation duration between setting animate to false and show to false) but then it goes bonkers and starts flickering everywhere.
Anyway someone can see the issue? Many thanks
edit
Sanbox link: https://codesandbox.io/s/trusting-shape-vxujw
You should define Modal in the outer scope of the component rendering it, the animation does not complete and you resetting it by redefining it on the next render.
Also resetting an animation should be done with none instead of giving an actual length.
Moreover, there might be more CSS bugs related that can hide your modal animation like z-index and position, if your question is focused on an animation problem you should remove all the noise around it.
See working example:
const Animation = styled.div`
transform: ${({ animate }) => (animate ? "none" : "translateX(500px)")};
transition: transform 1s;
`;
function Modal(props) {
return <Animation animate={props.show}>hello</Animation>;
}
function Component() {
const [show, toggle] = useReducer((p) => !p, false);
return (
<>
<Modal show={show} />
<button onClick={toggle}>show</button>
</>
);
}
Also, you shouldn't return null when you don't want to animate, you will lose the close animation.
// remove this code
if (!props.show) {
return null;
}
Related
My project is separated into parts, and some parts got so big I even separated them using composition on sub components.
The problem is, the compose components are rendering, but aren't interactive, for instance, here's a menu component that contains a header with a button:
import React from 'react';
export default function Menu() {
// This is responsible of creating a custom button with bunch of things in it, that button should rotate 180deg when hovered.
function IconButton() {
return (
<div class='rotating_button'>
// Bunch of code...
<button></button>
// Bunch of other code...
</div>
);
}
return (
<div class='menu'>
<IconButton />
// The rest of the Menu Code.
</div>
);
}
Everything till now seems fine, but when I wanna make a compose component interactive through something like CSS or other JavaScript file, it doesn't animate, or it doesn't render as expected.
Here's my CSS my code that in this case should make the custom button rotatable when hovered, and it's so simple it doesn't work:
.rotating_button {
/* All of this works. */
background_color: red;
width: 50px;
height: 50px;
transform: rotate(0deg);
/* But this does not. */
transition: 0.5s;
}
.rotating_button:is(:hover, :focus) {
transform: rotate(180deg);
}
To summarize, it rotates, but with no transition applied on it.
But when it's not a compose component, and it's directly put in the return section of the Menu Component, it works just write.
Suchlike this code:
import React from 'react';
export default function Menu() {
return (
<div class='menu'>
<div class='rotating_button'>
// Bunch of code...
<button></button>
// Bunch of other code...
</div>
// The rest of the Menu Code.
</div>
);
}
In my react app I have a hidden banner that I want to show, when the length of the array reaches to 5. But it looks like that I am trying to get an element before it is rendered. I get the error about getting a style of undefined element.
This function must change css of the banner element and make it visible.
showBanner() {
let banner = document.getElementsByClassName('overlay')[0]
banner.style.cssText = "visibility: visible;opacity: 1;"
}
I want to render my popup component only if the condition is met.
render() {
if (this.props.awarded) {
if (this.props.awarded.length === 5) {
this.showBanner()
return (
<>
<h1 id="awardLabel">5 movies</h1>
<div id="movieList">
{this.props.awarded.map((movie) => {
return (
<div className="awardHolder" key={movie.imdbID}>
<div className="awardImgHolder" >
<img src={movie.Poster} alt={movie.Title}></img>
</div>
<div className="awardMovieInfo">
<p>{movie.Title}</p>
<p>year {movie.Year}</p>
</div>
<div className="withdrawButton" onClick={(e) => this.deleteMovie(e, movie)}> WITHDRAW </div>
</div>
)
})}
</div>
<Popup />
</>
)
} else { ...
This is my banner structure.
<div id="popup1" className="overlay">
<div className="popup">
<h2>Here i am</h2>
<a className="close" href="#">×</a>
<div className="content">
<p>Congratulations. You've nominated 5 movies.</p>
<button onClick={this.closeBanner}>Try again</button>
</div>
</div>
</div>
This is my css for the banner element.
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
transition: opacity 500ms;
visibility: hidden;
opacity: 0;
}
How can I dynamically change element's styles using conditions to render that element?
You're trying to access your Popup component before it gets created. In other words, this.showBanner() is called before <Popup /> is rendered.
One solution is to move your popup to a higher-level component
This might be a good use case for React Context, which will allow you to have some global state that your components can tap into without having to pass the banner state through multiple components as props.
If you are going to do this, you might consider not manually updating the styling with querySelectors; instead, you can have React either render or not render the component based on your global banner state.
Your application will be wrapped in <BannerContext.Provider> tags, and then the component that needs to render or not render the banner can use <BannerContext.Consumer> tags to check the current banner state. You can also store a toggle function in the BannerContext so that other parts of the application (and the banner itself) can toggle the BannerContext as needed.
I have a react component where I want to change the header based on the scroll event. I'm attaching an event handler and based on scroll position and toggling a display class to hide ro show the desired elements.
Where I'm having trouble is, there seems to be some glitchy behavior when my component tries to re render. I have an example in codesandbox below.
import React from "react";
import "./styles.css";
export default function App() {
const [scrollY, setScrollY] = React.useState(0);
React.useEffect(() => {
const handleScroll = () => {
console.log(window.pageYOffset);
setScrollY(window.pageYOffset);
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [setScrollY]);
const scrolled = () => scrollY > 40;
return (
<div className="App">
<div className={`header ${scrolled() ? "d-none" : ""}`}>Header Main</div>
<div>
<div className={`header-secondary ${scrolled() ? "d-none" : ""}`}>
Header Secondary
</div>
<div className={`header-scrolled ${!scrolled() ? "d-none" : ""}`}>
HeaderScrolled
</div>
<div>Scroll Position: {scrollY}</div>
{[...Array(100)].map((e, i) => (
<div>
<div className={scrolled()}>{`SCROLLING`}</div>
</div>
))}
</div>
</div>
);
}
My code sandbox: https://codesandbox.io/s/wizardly-saha-0oklr
If you notice I have my hide/unhide compnent condition set at 40px. When you scroll slowly around 40px the header will snap back and for some reason the window.pageYOffset will reset to 0. I can't figure out why this is?
If you scroll past fast enough it doesn't matter but right around where I toggle the display class there is some odd behavior.
EDIT: Updated Example
Effectively what I need to do is have a smmoth transition from Header Main to Header Secondary. I can't really change styling on Header Main because I don't own that part of the product.
The problem is about your header. When you are at the top header affects the height of the scrolling body because it is position relative. When you start to scroll down it becomes fixed and leaves the body so it doesn't affect. When you scroll back to top it affect scroll height again.
So there are some tricks to resolve this issue. Giving padding-top: 50px; to scrolling element and using header always fixed will save you.
.App {
padding-top: 50px;
}
.header {
width: 100%;
height: 50px;
background-color: teal;
position: fixed;
top: 0;
}
.header-scrolled {
width: 100%;
height: 50px;
background-color: green;
color: white;
position: fixed;
top: 0;
}
https://codesandbox.io/s/lingering-pine-puunf
I was having the exact same issue for two days. The fix I implemented was to render a "sticky-nav" component in addition to the original "nav" component. This way the original nav will still exist on top (but above the visible window so you'd never know) and when you scroll down the "sticky-nav" appears. Since the original nav is always there and doesn't change position, it won't affect the scroll position and cause the glitch when slowly scrolling back up.
I've got a conditional class that is set with React's useState() Somehow the transition is not aimated. It just jumps to the new class values.
In the code example below I've tried to make it as clean as possible to my case.
With the line
className={`collapse my-navbar-collapse ${toggle ? "show" : ""}`} I set the class.
What am I missing here?
header.js
import React, { useState } from "react"
import "./header.sass"
const Header = () => {
const [toggle, setToggle] = useState(false)
return (
<div className={'sticky-top'}>
<button
className="btn navbar-toggler"
type="button"
onClick={ () => setToggle(!toggle) }
>
button stuff
</button>
<div className={`collapse my-navbar-collapse ${toggle ? "show" : ""}`}>
rest of my stuff
</div>
</div>
)
}
export default Header
header.sass
.my-navbar-collapse
background-color: red
transition: all 2s ease
height: 0
&.show
height: 100vh
(when it all works I'll change transition: all 2s ease from all to height. I just want to catch every for this test first :)
I didn't have any issues with the height transition as you can see from this demo.
However, it looks like you were forgetting overflow: hidden in your css. You need this because by default css tries to make sure nothing is lost even when that messes up stylings, so in order for the "rest of my stuff" text to be hidden when your navbar is collapsed, you need to make sure to hide it.
It looks like the issue is in the collapse class. Bootstrap's transitions.css adds display: none via that class. If there's not a display, then the css can't transition properly. Hence the issue.
The example now has 2 buttons, one triggers the div that has the collapse class, the other div doesn't have it.
const Header = () => {
const [toggle, setToggle] = React.useState(false);
const [toggle2, setToggle2] = React.useState(false);
return (
<div className={'sticky-top'}>
<button
className="btn navbar-toggler"
type="button"
onClick={() => setToggle(!toggle)}
>
button stuff - collapse
</button>
<div className={`collapse my-navbar-collapse ${toggle ? 'show' : ''}`}>
rest of my stuff
</div>
<button
className="btn navbar-toggler"
type="button"
onClick={() => setToggle2(!toggle2)}
>
button stuff - no collapse
</button>
<div className={`my-navbar-collapse ${toggle2 ? 'show' : ''}`}>
rest of my stuff
</div>
</div>
);
};
ReactDOM.render(<Header />, document.querySelector('#root'));
.my-navbar-collapse {
background-color: red;
transition: height 2s ease;
height: 0;
overflow: hidden;
}
.my-navbar-collapse.show {
height: 100vh;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root" />
I just recently started getting into using React.js and to make it easier for myself I'm trying to recreate projects I've made in the past, but instead of using jQuery like I did in the past I'm completely avoiding jQuery and using only React.
I tend to do animations where a div would fade in as another fades out like this:
$("#start").click(function() {
$("#h1").fadeOut(750);
$("#h2").delay(500).fadeIn(750);
$("#h1").css("z-index", 0);
$("#h2").css("z-index", 1);
});
I was wondering how can I reproduce this fade in and out effect without jQuery
(I know CSS animations could change the opacity, but the opacity isn't the only thing I'm trying to change, this affects the display property as well).
A simple way is to use CSS transitions. Basically you just add a class to an element, and define a transition in the CSS and it does the rest for you
There is a tutorial here
https://www.w3schools.com/css/css3_transitions.asp
Which does a good job of explaining how it all works with examples and a playground for you to try your own
The CSS Transition group add-on might help, it let's you define transitions like this:
JS:
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
{items}
</ReactCSSTransitionGroup>
CSS:
.example-enter {
opacity: 0.01;
}
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.example-leave {
opacity: 1;
}
.example-leave.example-leave-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
One option would be to use a framework, like react-bootstrap, which includes a lot of the UI components you need for any given project. It includes a Fade component. Documentation can be found here: https://react-bootstrap.github.io/components.html#utilities
class Example extends React.Component {
constructor(...args) {
super(...args);
this.state = {};
}
render() {
return (
<div>
<Button onClick={()=> this.setState({ open: !this.state.open })}>
click
</Button>
<Fade in={this.state.open}>
<div>
<Well>
THIS CONTENT WILL FADE
</Well>
</div>
</Fade>
</div>
);
}
}
ReactDOM.render(<Example/>, mountNode);