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>}
Related
I have a navigation bar that has a logo and a burgerbar that changes from a burger bar to an "x" when opened.
The problem is that the dropdown should go down when you click on the burger and go back up when you press the "x". This functions smoothly and perfectly so far, but the problem I noticed is that when I resize the browser window from >700px to <700px, the dropdown menu animation going up occurs.
import React, { useState, useRef, useEffect } from "react";
import { BurgerBar } from "../../styles/Navbar.style";
import Navlinks from "./Navlinks";
function Burger() {
const [open, setOpen] = useState(false);
const [width, setWidth] = useState(window.innerWidth);
const prevWidth = useRef();
useEffect(() => {
prevWidth.current = width;
}, [width]);
function checkResize() {
setWidth(window.innerWidth);
if (prevWidth.current <= 700 && width > 700) {
setOpen(false);
};
};
window.addEventListener("resize", checkResize);
return (
<>
<BurgerBar open={open} onClick={() => {setOpen(!open);}}>
<span />
<span />
<span />
</BurgerBar>
<Navlinks open={open} />
</>
)
};
export default Burger;
This is my styled-components file with the relevant styles:
export const BurgerBar = styled.span`
z-index: 1;
width: 2rem;
position: fixed;
display: flex;
justify-content: space-around;
flex-flow: column;
top: ${({open}) => open ? 22 : 24}px;
height: ${({open}) => open ? 2.3 : 2}rem;
right: ${({open}) => open ? 17 : 20}px;
span {
width: 2rem;
height: 0.25rem;
background-color: rgb(46, 203, 64);
border-radius: 10px;
transform-origin: 1px;
&:nth-child(1) {
transform: ${({open}) => open ? 'rotate(45deg)' : 'rotate(0)'};
width: ${({open}) => open ? 2.3 : 2}rem;
}
&:nth-child(2) {
transform: ${({open}) => open ? 'translateX(100%)' : 'translateX(0)'};
opacity: ${({open}) => open ? 0 : 1};
}
&:nth-child(3) {
transform: ${({open}) => open ? 'rotate(-45deg)' : 'rotate(0)'};
width: ${({open}) => open ? 2.3 : 2}rem;
}
}
&:hover {
opacity: 0.7;
cursor: pointer;
}
#media (min-width: 701px) {
visibility: hidden;
}
`;
export const Dropdown = styled.ul`
z-index: 1;
position: fixed;
list-style: none;
display: flex;
flex-flow: row nowrap;
padding: 0;
margin: 0;
top: 0;
li {
padding: 20px 20px;
&:hover {
opacity: 0.7;
cursor: pointer;
}
}
#media (max-width: 700px) {
position: fixed;
z-index: 0;
flex-flow: column nowrap;
background-color: rgb(32, 80, 36);
transform: ${({ open }) => (open) ? 'translateY(0)' : 'translateY(-100%)'};
vertical-align: middle;
transition: transform 0.3s ease-in-out;
top: 85px;
align-items: center;
border-bottom-left-radius: 4%;
li {
padding-top: 10px;
padding-bottom: 4px;
}
}
`;
I recognize that the problem lies in these two lines:
transform: ${({ open }) => (open) ? 'translateY(0)' : 'translateY(-100%)'};
and
transition: transform 0.3s ease-in-out;
I'm new to Javascript and React though and I cannot think of an efficient way to block the transition animation from going off upon browser resizing.
I can link my repo if more code/reference is necessary.
My custom modal window opens up but doesn't close when I click on the darkened area. I investigated a bit and found out that the setActive function in the modal component doesn't set the active to false for some reason. How can I fix this?
The modal file
import React from 'react'
import './style.css'
const Modal = ({active, setActive, children}) => {
return (
<div className={active?'modal_main active':'modal_main'} onClick={()=>{setActive(false)}}>
<div className={active?'modal_content active':'modal_content'} onClick={e=>e.stopPropagation()}>
{children}
</div>
</div>
)
}
export default Modal
Where I use the modal window
import React, { useState } from 'react'
import Modal from '../../modal'
import './style.css'
function TagItem(props) {
const [tagActive, setTagActive] = useState(false);
return (
<div className='tag-item' onClick={()=>setTagActive(true)}>
{props.tag}
<Modal active = {tagActive} setActive = {setTagActive}>
<div >{props.tag}</div>
</Modal>
</div>
)
}
export default TagItem
modal's css
.modal_main{
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
transition: 0.5s;
z-index:1;
}
.modal_main.active{
opacity: 1;
pointer-events: all;
}
.modal_content{
padding: 20px;
border-radius: 12px;
background-color: white;
height: fit-content;
width: fit-content;
transform: scale(0.5);
transition: 0.4s all;
}
.modal_content.active{
transform: scale(1);
}
tag-item's css
.tag-item{
border: 1px limegreen solid;
border-radius: 8px;
background-color: rgb(178, 246, 119);
width: fit-content;
padding: 2px;
margin: 2px;
cursor: default;
}
.tag-item:hover{
cursor: default;
background-color: rgb(1, 152, 1) ;
}
Issue
The click event from the modals's outer div elementis triggering the state update, but it's also propagated out of theModalcomponent to theTagItemcomponent'sdivelement and this enqueues atagActivestate update totrue`. The state update to close the modal is overwritten.
Solution
Stop the propagation of the outer div element's click event.
const Modal = ({ active, setActive, children }) => {
return (
<div
className={active ? "modal_main active" : "modal_main"}
onClick={(e) => {
e.stopPropagation(); // <-- stop propagation to parent component
setActive(false);
}}
>
<div
className={active ? "modal_content active" : "modal_content"}
onClick={(e) => {
e.stopPropagation();
}}
>
{children}
</div>
</div>
);
};
Banner.component.jsx
import "./banner.styles.scss";
import ImgComp from "./banner.image";
import i1 from "../../assets/banner-pics/1.jpeg";
import i2 from "../../assets/banner-pics/2.jpeg";
function Banner() {
let bannerArr = [<ImgComp src={i1} />, <ImgComp src={i2} />];
const [x, setX] = useState(0);
const goLeft = () => {
x === 0 ? setX(-100 * (bannerArr.length - 1)) : setX(x + 100);
};
const goRight = () => {
x === -100 * (bannerArr.length - 1) ? setX(0) : setX(x - 100);
};
return (
<div className="banner">
{bannerArr.map((item, index) => {
return (
<div
key={index}
className="slide"
style={{ transform: `translateX(${x}%)` }}
>
{item}
</div>
);
})}
<button id="goLeft" onClick={goLeft}>
<i class="fas fa-chevron-left"></i>
</button>
<button id="goRight" onClick={goRight}>
<i class="fas fa-chevron-right"></i>
</button>
</div>
);
}
export default Banner;
Banner.styles.scss
.banner {
position: relative;
width: 100%;
height: 800px;
box-sizing: border-box;
margin-bottom: 15px;
padding: 0;
display: flex;
align-items: center;
overflow: hidden;
i {
font-size: 2vw;
}
}
.slide {
position: relative;
min-width: 100%;
height: 100%;
transition: 0.5s;
overflow: hidden;
}
%btn-styles {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 10%;
height: 100%;
background: none;
border: none;
outline: none;
transition: 0.5s;
&:hover {
background: rgba(0, 0, 0, 0.356);
cursor: pointer;
i {
color: whitesmoke;
}
}
}
#goLeft {
left: 0;
#extend %btn-styles;
}
#goRight {
right: 0;
#extend %btn-styles;
}
I am trying to add text over the top of this slider and for it to be different writing on each image, how would I go about putting a different text overlay on each image? I just want to put a paragraph with a button on the first image and just text on the second image.
You can add the texts on array and you can add some styles.
import "./banner.styles.scss";
import ImgComp from "./banner.image";
import i1 from "../../assets/banner-pics/1.jpeg";
import i2 from "../../assets/banner-pics/2.jpeg";
function Banner() {
let bannerArr = [
<div className="Item"><ImgComp src={i1} /><p className="custom-text">Text1</p></div>,
<div className="Item"><ImgComp src={i1} /><p className="custom-text">Text2</p></div>
];
const [x, setX] = useState(0);
const goLeft = () => {
x === 0 ? setX(-100 * (bannerArr.length - 1)) : setX(x + 100);
};
const goRight = () => {
x === -100 * (bannerArr.length - 1) ? setX(0) : setX(x - 100);
};
return (
<div className="banner">
{bannerArr.map((item, index) => {
return (
<div
key={index}
className="slide"
style={{ transform: `translateX(${x}%)` }}
>
{item}
</div>
);
})}
<button id="goLeft" onClick={goLeft}>
<i class="fas fa-chevron-left"></i>
</button>
<button id="goRight" onClick={goRight}>
<i class="fas fa-chevron-right"></i>
</button>
</div>
);
}
export default Banner;
on global style
.item{
position: relative;
.custom-text{
position: abolute;
bottom: 15px;
}
}
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 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.