I built simple Modal component which will slide from bottom when opened. Animations are working fine when Modal trigger button clicked and backdrop clicked. But i am seeing slide-down animation at initial render of page. How can i prevent initial animation ?? I am specifically looking how to solve with react hooks.
Modal.js
import React, { useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';
import './Modal.css';
const Modal = ({ isOpen, onClose, children }) => {
const modalEl = useRef(null);
const handleCoverClick = (e) => {
if (e.target.hasAttribute('modal')) {
onClose();
}
}
useEffect(() => {
const handleAnimationEnd = (event) => {
if (!isOpen) {
event.target.classList.remove('show');
event.target.classList.add('hide');
} else {
event.target.classList.remove('hide');
event.target.classList.add('show');
}
};
modalEl.current.addEventListener('animationend', handleAnimationEnd);
return () => modalEl.current.removeEventListener('animationend', handleAnimationEnd);
}, [isOpen]);
return createPortal(
<>
<div className={`ModalCover ${isOpen ? 'show' : 'hide'}`} onClick={handleCoverClick} modal="true"></div>
<div className={`ModalContainer ${isOpen ? 'slide-up' : 'slide-down'}`} ref={modalEl}>
{children}
</div>
</>,
document.body);
};
export default Modal;
Modal.css
.show {
display: block;
}
.hide {
display: none;
}
.slide-up {
transform: translateY(0%);
animation: slide-up 0.5s forwards;
}
.slide-down {
transform: translateY(100%);
animation: slide-down 0.5s forwards;
}
#keyframes slide-up {
0% { transform: translateY(100%); }
100% { transform: translateY(0%); }
}
#keyframes slide-down {
0% { transform: translateY(0%); }
100% { transform: translateY(100%); }
}
.ModalCover {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
background-color: rgba(0, 0, 0, 0.15);
}
.ModalContainer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 400px;
margin-top: calc(100vh - 400px);
z-index: 20;
}
demo (codesandbox) : https://codesandbox.io/s/l7x5p4k82m
Thanks!
A simpler way is to do this with classNames since direct DOM access is discouraged with DOM. modalEl.current ref is assigned after initial render, it can be used as a flag that a component was mounted:
<div className={`
ModalContainer
${isOpen ? 'slide-up' : 'slide-down'}
${!modalEl.current && 'hide'}
`} ref={modalEl}>
Applying hide class on component mount in useEffect may result in briefly shown modal animation.
Related
I want to make smth like clock with react.I hope to do it with help of ref, but it doesn't work sometimes. Is there another solution of this task, or what i do wrong?
import React, { useRef } from 'react';
import classes from "./Synchroniser.module.css";
const Synchroniser = () => {
const point:any = useRef(null);
var k = 0;
if (point.current !== null) {
setInterval(()=>{
if(k<360){
k++;
console.log(k);
}else{k=0}
point.current.style.transform = `rotate(${k}deg)`;
},100);
}else{
console.log(0)
}
return(
<div>
<h1 ref={point}>RLY?</h1>
</div>
);
};
export default Synchroniser
Go to bottom for the pure CSS solution
React Solution
This solves your problem but the k variable adds twice as much as it needs to, I don't know why. For the k variable to persist between states and not reset the clock every rerender you have to use it with useRef too.
const point = useRef(null);
const k = useRef(0);
useEffect(() => {
if (point.current !== null) {
setInterval(() => {
if (k.current < 360) {
k.current = k.current + 1;
} else {
k.current = 0;
}
point.current.style.transform = `rotate(${k.current}deg)`;
}, 1000);
} else {
console.log("0");
}
},[]);
CSS Solution CodeSandbox Link
JSX
<div className="clock">
<div className="spin"></div>
</div>
CSS
.clock {
height: 250px;
width: 250px;
border-radius: 9999px;
display: flex;
justify-content: center;
align-items: center;
background: #eeeeee;
}
.spin {
width: 90%;
height: 2px;
position: relative;
background: white;
-webkit-animation:spin 60s linear infinite;
-moz-animation:spin 60s linear infinite;
animation:spin 60s linear infinite;
}
.spin::after,
.spin::before {
height: 2px;
content: " ";
position: absolute;
top: 0;
width: 50%;
}
.spin::after {
right: 0;
background-color: black;
}
.spin::before {
left: 0;
background-color: #eeeeee;
}
#-moz-keyframes spin {
100% {
-moz-transform: rotate(360deg);
}
}
#-webkit-keyframes spin {
100% {
-webkit-transform: rotate(360deg);
}
}
#keyframes spin {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
I have one boolean to control two components (fullPage/halfPage) into one div. when boolean is true fullPage component applied and false halfPage component applied. how can I apply the fadeIn, fadeOut effect on that div. so when fullPage classname applies, full page fade in and half page fade out. when halfPage class name applies, full page fade out and half page fade In?
codesandox.io
// css
.App {
font-family: sans-serif;
text-align: center;
}
.fullPage {
background-color: aquamarine;
animation: fadeIn 2s linear;
width: 490px;
}
.halfPage {
background-color: bisque;
animation: fadeIn 2s linear;
width: 290px;
}
#keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
#keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
// App.tsx
import "./styles.css";
import { useState } from "react";
import classNames from "classnames";
export const HalfPage = () => {
return (
...
)}
export const FullPage = () => {
return (
...
);
};
export default function App() {
const [cnt, setCnt] = useState(0);
const isFullPage = cnt % 2 === 0;
const appClass = classNames({
fullPage: isFullPage,
halfPage: !isFullPage
});
const handleOnClick = () => {
setCnt(cnt + 1);
};
return (
<div className="App">
<button onClick={handleOnClick}>Change background</button>
<div className={appClass}>{isFullPage ? <FullPage /> : <HalfPage />}</div>
</div>
);
}
You can achieve the fade in / fade out effect in both elements, but it requires some refactoring in your HTML code as well as some more CSS to make it work. All we do is wrapping these page elements in divs and place them in a parent element whose class name is inner-container, in order to keep them in the same position as they should be.
So, your new styles.css would look like this (I have pointed out the changes):
.App {
font-family: sans-serif;
text-align: center;
}
/* Added opacity, position and transition time in both elements. */
.fullPage {
background-color: aquamarine;
animation: fadeIn 2s linear;
width: 490px;
opacity: 0;
transition: 0.4s;
position: absolute;
}
.halfPage {
background-color: bisque;
animation: fadeIn 2s linear;
width: 290px;
opacity: 0;
transition: 0.4s;
position: absolute;
}
/* Added flag class for active element and class for inner container. */
.active {
opacity: 1;
}
.inner-container {
position: relative;
}
/* Keyframes are not necessary. */
/* #keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
#keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
} */
Your return function in App.tsx would look like this:
return (
<div className="App">
<button onClick={handleOnClick}>Change background</button>
<div className="inner-container">
<div className={isFullPage ? "fullPage active" : "fullPage"}>
<FullPage />
</div>
<div className={isFullPage ? "halfPage" : "halfPage active"}>
<HalfPage />
</div>
</div>
</div>
);
I'm using react.
I want to display the modal from the bottom when I press the btn button.
I want to show the modal from the bottom, like the modal on this site.
https://codepen.io/takapen/pen/jdwwWQ
I created a modal with the same structure as the modal on this site.
However, when I press the button, the modal is not displayed.
import React, { useState } from "react";
const App: React.FunctionComponent = () => {
const Spin = (
<style>
{`
#keyframes SlideUp {
0% {
opacity: 0;
transform: translateY(-1%);
}
100% {
opacity: 1;
transform: translateY(-80%);
}
}
`}
</style>
);
const [isCheck, setIsCheck] = useState(false);
console.log(isCheck);
return (
<>
<div
class="js-modal__btn"
onClick={() => {
setIsCheck(true);
}}
>
btn
</div>
<div
class="js-modal__bg"
style={{
width: "100%",
height: "100%",
backgroundColor: "rgba(0,0,0,0.6)",
position: "fixed",
top: 0,
left: 0,
zIndex: 10,
display: "none"
}}
></div>
<div
class="js-modal__main"
style={{
width: "calc(100% - 32px)",
height: "80%",
padding: "16px",
bottom: "-81%",
left: "0",
background: "#fff",
borderRadius: "4px 4px 0 0",
position: "fixed",
zIndex: 11,
opacity: "0",
animation: isCheck ? `${Spin} .5s ease-in-out forwards` : undefined
}}
>
<p>コンテンツ</p>
<p class="js-modal__btn--close">close</p>
<p class="js-modal__btn--close--fix"></p>
</div>
</>
);
};
export default App;
First, you need to render your style to the document.
Second, you need to use the correct name for the animation.
So you need to fix those lines:
return (
<>
// Render your style
{Spin}
// Use the keyframe's name
animation: isCheck ? `SlideUp .5s ease-in-out forwards` : undefined
</>
)
But I recommend you should:
Create the CSS file then import it
Toggle animation based on className will be better.
My solution
styles.css
.modal__bg {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
position: fixed;
top: 0; left: 0;
z-index: 10;
display: none;
}
.modal__main {
width: calc(100% - 32px);
height: 80%;
padding: 16px;
bottom: -81%;
left: 0;
background: white;
border-radius: 4px 4px 0 0;
position: fixed;
z-index: 11;
opacity: 0;
}
.modal__main-show {
animation: SlideUp .5s ease-in-out forwards
}
.modal__main-hide {
// or animation configs for slide out
animation: none
}
#keyframes SlideUp {
0% {
opacity: 0;
transform: translateY(-1%);
}
100% {
opacity: 1;
transform: translateY(-80%);
}
}
App.tsx
import React, { useState } from "react";
import "./styles.css";
const App: React.FunctionComponent = () => {
const [isCheck, setIsCheck] = useState(false);
return (
<>
<div
className="js-modal__btn"
onClick={() => {
setIsCheck((prev) => !prev);
}}
>
btn
</div>
<div className="modal__bg" />
<div
className={`modal__main ${
isCheck ? "modal__main-show" : "modal__main-hide"
}`}
>
<p>コンテンツ</p>
<p className="js-modal__btn--close">close</p>
<p className="js-modal__btn--close--fix"></p>
</div>
</>
);
};
export default App;
You can use isCheck to show modal like this:
{isCheck && <div class="js-modal__main">...</div>}
Currently I have a table with a question for each cell. It models a Jeopardy game. When the user clicks on one of the cells, a question modal pops up and then when he hits submit, a feedback modal pops up. I want to change the background of the question and feedback modals to be one consistent animated background. But currently the background only changes for that one column of the cell I click on. I need to somehow temporarily override the table and display the animated background rather than the table when the modals pop up.
Here is what I have:
Question Modal (that also renders Feedback modal)
import React, { Component } from 'react';
import Modal from "react-bootstrap/Modal";
import Button from "react-bootstrap/Button";
import UserInput from './userInput.js';
import Feedback from "./feedback/feedback";
import Backdrop from "./backdrop";
import styles from '../scss/backdrop.scss';
class SampleQ extends Component {
static getInitialProps({query: {amount, question, answer}}) {
return {specAmt: amount, specQ: question, specA: answer}
}
constructor(props) {
super(props);
this.state = {
showQuestion: false, // tracks visibility of first modal (the question modal)
showFeedback: false, // tracks visibility of second modal (the feedback modal)
showBackdrop: false // tracks visibility of backdrop when question pops up
};
this.handleShow = () => {
// show question and the backdrop when the user clicks a cell on the table
this.setState({ showQuestion: true, showBackdrop: true });
};
this.handleHide = () => {
this.setState({ showQuestion: false });
};
this.submitForm = event => {
event.preventDefault();
this.setState({
showQuestion: false, // close question modal
showFeedback: true, // should open Feedback modal
});
};
this.closeFeedback = () => {
// close Feedback modal and close backdrop so that it goes back to the table
this.setState( { showFeedback: false, showBackdrop: false });
};
}
render() {
let backdrop;
// only shows if either the question or feedback modal is up. if neither are open, then don't show stars
if (this.state.showBackdrop && (this.state.showQuestion || this.state.showFeedback) ) {
backdrop = <Backdrop/>
} else {}
return (
<>
<Button variant="outline-danger" size = "lg" onClick={this.handleShow}>
$ {this.props.amount}00
</Button>
{backdrop}
<Modal
show={this.state.showQuestion}
onHide={this.handleHide}
dialogClassName="modal-90w"
aria-labelledby="example-custom-modal-styling-title"
>
<Modal.Header closeButton>
<Modal.Title id="example-custom-modal-styling-title">
Question
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
{this.props.question}
</p>
<div>
<UserInput
answer={this.props.specA}
handleClick={this.submitForm}
/>
</div>
</Modal.Body>
</Modal>
<Feedback
showModal={this.state.showFeedback}
handleHide={this.closeFeedback}
/>
</>
);
}
}
export default SampleQ;
Animated Backdrop/Background for the modals
import React, { Component } from 'react';
import styles from '../scss/backdrop.scss';
export default class Backdrop extends Component {
render() {
return (
<div className={styles.body}>
<div className={styles.night}>
<div className={styles.shooting_star}></div>
<div className={styles.shooting_star}></div>
<div className={styles.shooting_star}></div>
<div className={styles.shooting_star}></div>
</div>
</div>
);
}
}
CSS/SCSS for the backdrop/background
.body {
background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
height: 100vh;
overflow: hidden;
display: flex;
font-family: 'Anton', sans-serif;
justify-content: center;
align-items: center;
}
$shooting-time: 3000ms;
.night {
position: relative;
width: 100%;
height: 100%;
transform: rotateZ(45deg);
// animation: sky 200000ms linear infinite;
}
.shooting_star {
position: absolute;
left: 50%;
top: 50%;
// width: 100px;
height: 2px;
background: linear-gradient(-45deg, rgba(95, 145, 255, 1), rgba(0, 0, 255, 0));
border-radius: 999px;
filter: drop-shadow(0 0 6px rgba(105, 155, 255, 1));
animation:
tail $shooting-time ease-in-out infinite,
shooting $shooting-time ease-in-out infinite;
&::before {
content: '';
position: absolute;
top: calc(50% - 1px);
right: 0;
// width: 30px;
height: 2px;
background: linear-gradient(-45deg, rgba(0, 0, 255, 0), rgba(95, 145, 255, 1), rgba(0, 0, 255, 0));
transform: translateX(50%) rotateZ(45deg);
border-radius: 100%;
animation: shining $shooting-time ease-in-out infinite;
}
&::after {
#extend .shooting_star::before;
transform: translateX(50%) rotateZ(-45deg);
}
#for $i from 1 through 20 {
&:nth-child(#{$i}) {
$delay: random(9999) + 0ms;
top: calc(50% - #{random(400) - 200px});
left: calc(50% - #{random(300) + 0px});
animation-delay: $delay;
// opacity: random(50) / 100 + 0.5;
&::before,
&::after {
animation-delay: $delay;
}
}
}
}
#keyframes tail {
0% {
width: 0;
}
30% {
width: 100px;
}
100% {
width: 0;
}
}
#keyframes shining {
0% {
width: 0;
}
50% {
width: 30px;
}
100% {
width: 0;
}
}
#keyframes shooting {
0% {
transform: translateX(0);
}
100% {
transform: translateX(300px);
}
}
#keyframes sky {
0% {
transform: rotate(45deg);
}
100% {
transform: rotate(45 + 360deg);
}
}
Screenshots:
table
question
I created slide animation for my drop out menu, now I need to change the keyframes to make this menu not just disappear but smoothly hide in the same way it appears on the screen.
The issue is that I'm missing something or using the incorrect syntax for some of my approaches.
link to the repo:
https://github.com/kkdima/barva
For styling, I'm using styled-components, because it has these ThemeProvider features and variables options.
I was trying to fix this with the ThemeProvider but no success so far. It gives messages that something wrong with the props.
Also, I have 2 components in the same .js file, one of them is sharing state with another.
this.state = { menuTriggered: false };
this state is the reference to which keyframe should be animated, either bgAnimationOpen or bgAnimationClose.
In both of them, I have elements (Background and Ul) which need to be changed for the smooth backward animation.
The next code example has needed keyframes in the definition of Background div. The rest are stored in the separate KeyFrames.js file which I import.
Pardon me for such a long code, I just have styles inside my js files.
import Link from "next/link";
import React, { Component } from "react";
import styled, { ThemeProvider } from "styled-components";
import Logo from "./Logo";
import { menuAnimationOpen } from "../styles/KeyFrames";
import { menuToClose } from "../styles/KeyFrames";
import { closeToMenu } from "../styles/KeyFrames";
import { MenuToCloseBackwards } from "../styles/KeyFrames";
import { bgAnimationOpen } from "../styles/KeyFrames";
import { bgAnimationClose } from "../styles/KeyFrames";
class Menu extends Component {
constructor(props) {
super(props);
}
render() {
const { menuTriggered } = this.props;
return (
<div>
<ThemeProvider theme={theme}>
<Background />
</ThemeProvider>
<UL className="menuOpens">
<StyledLink href="/">
<Li>
<A>Home</A>
</Li>
</StyledLink>
<StyledLink href="/projects">
<Li>
<A>Projects</A>
</Li>
</StyledLink>
<StyledLink href="/about">
<Li>
<A>About</A>
</Li>
</StyledLink>
<StyledLink href="/contact">
<Li>
<A>Contact</A>
</Li>
</StyledLink>
</UL>
</div>
);
}
}
class SlideNav extends Component {
constructor(props) {
super(props);
this.state = { menuTriggered: false };
}
toggleMenu = () => {
this.setState({ menuTriggered: !this.state.menuTriggered });
};
render() {
const { menuTriggered } = this.state;
return (
<MenuWrapper className="menu">
<LogoAndMenuButton>
<Logo />
<MenuTextWrapper>
<Input type="checkbox" onClick={this.toggleMenu} />
<Span className="text-Menu">
<p>Menu</p>
</Span>
<Span className="text-Close">
<p>Close</p>
</Span>
</MenuTextWrapper>
</LogoAndMenuButton>
{menuTriggered ? (
<Menu
menuTriggered={this.state.menuTriggered}
>
{/* {this.props.children} */}
</Menu>
) : (
<div />
)}
</MenuWrapper>
);
}
}
export default SlideNav;
const StyledLink = styled(Link)``;
const MenuWrapper = styled.div`
width: 100vw;
box-sizing: border-box;
padding-left: 40;
background: grey;
`;
const LogoAndMenuButton = styled.div`
display: flex;
justify-content: space-between;
flex-direction: row;
margin: 0;
width: 100%;
`;
// MENU/CLOSE BUTTON STYLES:
// MENU/CLOSE BUTTON STYLES:
// MENU/CLOSE BUTTON STYLES:
const MenuTextWrapper = styled.div`
background: none;
border: none;
overflow: hidden;
width: 65px;
height: 26px;
padding: 0;
z-index: 1;
margin: 40px 40px;
/* Menu to Close text animation 1st span */
input[type="checkbox"]:checked ~ .text-Menu {
animation: ${menuToClose} 300ms ease-in-out;
animation-fill-mode: forwards;
}
/* Menu to Close text animation 2nd span */
input[type="checkbox"]:checked ~ .text-Close {
color: #fff;
animation: ${closeToMenu} 300ms ease-in-out;
animation-fill-mode: forwards;
}
/* Close to Menu text animation 2nd span */
input[type="checkbox"]:not(:checked) ~ .text-Menu {
animation: ${MenuToCloseBackwards} 300ms ease-in-out;
animation-fill-mode: forwards;
}
`;
const Input = styled.input`
position: absolute;
width: 65px;
height: 26px;
z-index: 2;
cursor: pointer;
margin: 0;
opacity: 0;
`;
const Span = styled.span`
justify-content: center;
display: flex;
color: black;
padding-bottom: 10px;
z-index: -1;
p {
font-size: 1.2rem;
margin: 0;
}
`;
//END OF MENU/CLOSE BUTTON STYLES;
//END OF MENU/CLOSE BUTTON STYLES;
//END OF MENU/CLOSE BUTTON STYLES;
const menuOpen = bgAnimationOpen, menuClose = bgAnimationClose;
const Background = styled.div`
position: absolute;
top: 0;
background-color: #222222;
box-sizing: border-box;
height: 100vh;
width: 100vw;
#keyframes bgAnimationOpen {
from {
transform: translateY(-900px);
}
to {
visibility: block;
}
}
#keyframes bgAnimationClose {
from {
transform: translateY(900px);
}
to {
visibility: block;
}
}
animation: ${bgAnimationOpen} 0.8s
cubic-bezier(0.215, 0.61, 0.355, 1);
animation-fill-mode: forwards;
`;
Background.defaultProps = {
theme: {
open: "bgAnimationOpen"
}
};
const theme = {
close: "bgAnimationClose",
open: "bgAnimationOpen"
};
// UL NAVIGATION STYLES:
// UL NAVIGATION STYLES:
// UL NAVIGATION STYLES:
const UL = styled.ul`
position: absolute;
top: 140px;
font-size: 32px;
font-weight: bold;
max-width: 200px;
margin: 0px 0px 0px 40px;
padding: 0px;
li:nth-of-type(1) {
animation: ${menuAnimationOpen} 0.8s 0.5s cubic-bezier(0.35, 0.25, 0, 1.28);
animation-fill-mode: forwards;
}
li:nth-of-type(2) {
animation: ${menuAnimationOpen} 0.8s 0.6s cubic-bezier(0.35, 0.25, 0, 1.22);
animation-fill-mode: forwards;
}
li:nth-of-type(3) {
animation: ${menuAnimationOpen} 0.8s 0.7s cubic-bezier(0.35, 0.25, 0, 1.15);
animation-fill-mode: forwards;
}
li:nth-of-type(4) {
animation: ${menuAnimationOpen} 0.8s 0.8s cubic-bezier(0.35, 0.25, 0, 1.1);
animation-fill-mode: forwards;
}
`;
const Li = styled.li`
opacity: 0;
list-style: none;
color: pink;
text-align: left;
line-height: 45px;
transition: transform 400ms ease-in;
&:hover {
transform: scale(1.1);
}
`;
const A = styled.a`
cursor: pointer;
text-decoration: none;
`;
The needed outcome is working animation back and force just as on the attached gif. Much love if you can help with any clue to resolving this!
PS. It's slightly out of topic but maybe somebody knows how to fix the gif in the logo background? In the end of the cycle or in the very beggining it dissapears and creates delay in couple sec where there's no gif background. How to keep it in the loop all the time with no dissapearence? even though it's on the loop by default
animation: gifs 60s infinite running;