I created a sidebar (hidden by default) in a web store that contains a list of items you like. When clicked on a button the likes-list will show sliding in from the right smoothly. However on page refresh/reload the sidebar slides in and out rapidly. I'm using css animation to do the trick.
It works fine but somehow on page reload the animation is triggered without clicking the button. How to prevent this from happening?
App is built in React (create-react-app).
React component
import React from 'react';
import { useGlobalContext } from '../Context'
import './likes.css';
const Likes = ({ showLikes, setShowLikes }) => {
let { liked, setLiked, paintings, setPaintings } = useGlobalContext()
//Remove item from likes list
const removeItemFromLikeslist = (id) => {
liked = liked.filter(item => {
return item.id !== id;
});
//Change likes-heart to green
paintings = paintings.map(el => {
if (el.id === id) {
el.like = false
}
return el
})
//remove sidebar overlay
if (liked.length === 0) {
setShowLikes(false)
}
setPaintings(paintings)
setLiked(liked)
localStorage.setItem("PAINTINGS", JSON.stringify(paintings))
localStorage.setItem('LIKES', JSON.stringify(liked))
}
return (
<div className={showLikes ? "likesContainer show" : "likesContainer"} >
<div className="likesHeader">You like these paintings</div>
{liked.map(item => {
const { id, name, imgSrc } = item;
return (
<div className="like-item" onClick={() => removeItemFromLikeslist(id)} key={id}>
<div className="like-info">
<img src={imgSrc} style={{ width: "100px" }} alt={name} className="picInCart" />
</div>
<div className="like-name">
<h5>{name}</h5>
</div>
</div>
)
})}
</div>
)
}
export default Likes
CSS file
.likesContainer {
position: fixed;
top: 83px;
right: -328px;
width: 320px;
background-color: rgb(70, 70, 70);
height:auto;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
z-index: 2;
box-shadow: 0px 4px 10px black;
animation: animateHide .5s linear forwards;
}
#keyframes animateHide {
from {
right: 0;
}
to {
right: -328px;
}
}
.likesContainer.show {
right: 0;
animation: animateShow .5s linear forwards;
}
#keyframes animateShow {
from {
right: -328px;
}
to {
right: 0;
}
}
Problem solved, changed from animation to transition like so:
.likesContainer {
position: fixed;
top: 83px;
right: -328px;
width: 320px;
background-color: rgb(70, 70, 70);
height:auto;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
z-index: 2;
box-shadow: 0px 4px 10px black;
transition: .5s ease-in-out;
}
.likesContainer.show {
right: 0;
}
Related
I'm building out a custom Tooltip component in React that allows for rendering both to the right of the element as well as below it. I've decided to handle the functionality by having the Tooltip accept children which it then renders, and when those children are hovered the tip itself, positioned absolutely, will become visible through CSS classes. The effect works exactly how I want when the elements are stacked in a column and the tooltip is set to render to the right, but when the elements are laid out in a row with the tip rendering on the bottom, the tip itself becomes misaligned from the child its rendering -- the length of the tooltip text seems to offset it to the right.
Here's a codesandbox displaying the problem. You'll see that in the first state, when the elements are in a column, the tip is aligned perfectly. Click the button to toggle them into a row format and hover again to see that they are now misaligned:
https://codesandbox.io/s/busted-tooltip-obf0h1?file=/src/Tooltip.scss
How can I get the tips to align centered with their child element when I've chosen to have them render below it?
// Tooltip.jsx
import React, { useState } from 'react';
import './Tooltip.scss';
const Tooltip = ({ children, label, orientation = 'right' }) => {
const [isHovered, setIsHovered] = useState(false);
return (
<div className={`Tooltip__Container ${orientation}`}>
<div
onMouseOver={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{children}
</div>
<div className={`Tooltip__Inner ${orientation} ${isHovered ? 'Visible' : ''}`}>
{orientation === 'right' && <div className={`Tooltip__PointLeft ${isHovered ? 'Visible' : ''}`} />}
{orientation === 'bottom' && <div className={`Tooltip__PointUp ${isHovered ? 'Visible' : ''}`} />}
<div className={`Tooltip ${orientation} ${isHovered ? 'Visible' : ''}`}>
{label}
</div>
</div>
</div>
);
};
export default Tooltip;
/* Tooltip.scss */
#import 'src/styles/colors.scss', 'src/styles/typography.scss', 'src/styles/breakpoints.scss';
.Tooltip__Container {
display: flex;
position: relative;
&.right {
flex-direction: row;
}
&.bottom {
flex-direction: column;
}
}
.Tooltip__Inner {
display: flex;
z-index: -1;
position: absolute;
transition: all .25s ease;
opacity: 0;
left: 0;
top: 0;
&.right {
flex-direction: row;
}
&.bottom {
flex-direction: column;
}
}
.Tooltip__Inner.right.Visible {
opacity: 1;
left: 3.5rem;
}
.Tooltip__Inner.bottom.Visible {
opacity: 1;
top: 3.5rem;
}
.Tooltip__PointLeft.Visible {
opacity: 1;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 10px solid $white-transparent;
align-self: center;
justify-self: center;
}
.Tooltip__PointUp.Visible {
opacity: 1;
width: 0;
height: 0;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid $white-transparent;
align-self: center;
justify-self: center;
}
.Tooltip {
background-color: $white-transparent;
padding: .75rem;
border-radius: 3px;
#include MainFont;
color: white;
}
For quick solution, you can update the bottom class inside .Tooltip__Inner
with this:
&.bottom {
flex-direction: column;
left: 50%;
-ms-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
I have a modal which appears on the press of a button and is animated as it is displayed.
This works fine, BUT only if the modal code is already in the DOM when the button is pressed.
You can see the example here:
https://codesandbox.io/s/loving-dan-7fwrkr
This is the problem: if the button adds the modal code to the DOM then the modal simply appears with no animation.
I've spent many hours trying various ways to make this work and the best I can come up with is to use window.setTimeout to trigger the animation 200 milliseconds after the modal code is added to the DOM. I don't like such a solution because it seems like a hack - I don't have any clear understanding why such a hack would work.
The example below demonstrates both cases.
Without the commented code, the animation works.
With the commented code, the modal simply appears without animation.
If anyone has any idea how to fix this it would be much appreciated.
My specific goal is to NOT have the modal code in the DOM prior to pressing a button to make it appear.
I've worked pretty hard to make the minimum possible example below, but it is still fairly large I apologise. If you have suggesting for cutting it further whilst still being relevant please let me know.
import ReactDOM from 'react-dom';
import React, {useState} from 'react';
const theStyle = `
.md-modal {
position: fixed;
top: 50%;
left: 50%;
width: 50%;
height: auto;
z-index: 2000;
visibility: hidden;
transform: translateX(-50%) translateY(-50%);
}
.md-show {
visibility: visible;
}
.md-overlay {
position: fixed;
width: 100%;
height: 100%;
visibility: hidden;
top: 0;
left: 0;
z-index: 1000;
opacity: 0;
background: rgba(143, 27, 15, 0.8);
transition: all 0.3s;
}
.md-show ~ .md-overlay {
opacity: 1;
visibility: visible;
}
.md-content {
color: #fff;
background: #e74c3c;
position: relative;
border-radius: 3px;
margin: 0 auto;
}
.md-content h3 {
opacity: 0.8;
}
.md-effect-1 .md-content {
transform: scale(0.7);
opacity: 0;
transition: all 0.3s;
}
.md-show.md-effect-1 .md-content {
transform: scale(1);
opacity: 1;
}
`
function App() {
const [getVisible, setVisible] = useState(false);
/*
THE MODAL APPEAR ANIMATION DOES NOT WORK WHEN THIS IS UNCOMMENTED
if (!getVisible) {
return (
<button onClick={() => setVisible(true)}>
show modal
</button>)
}
*/
return (
<>
<style>
{theStyle}
</style>
<div className={`md-modal md-effect-1 ${(getVisible) && "md-show"}`}>
<div className="md-content">
This is a modal window.<br/>
<button onClick={() => setVisible(false)} className="md-close">close</button>
</div>
</div>
<div onClick={() => setVisible(false)} className="md-overlay"/>
<button onClick={() => setVisible(true)} className="md-trigger">
show modal
</button>
</>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
I've had similar issues, the reason was that the transition does not trigger if the modal immediately gets the end value of being visible when you add it to the DOM.
I solved it by putting the transition into an #keyframes animation. Then, after adding the modal to the DOM, you use classList.add() to trigger the animation.
Something like this
.modal {
opacity:0
}
.animated {
animation: showModal 1s forwards easeOut
}
#keyframes showModal {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
JS after the modal is added to the DOM:
myModel.classList.add("animated")
Self answer to my future self.
With the answer of #Kokodoko as my start point, I gained a better understanding of how animation works in CSS/JS and rewrote my modal entirely so it now does what I want.
Here's the code:
import ReactDOM from 'react-dom';
import React, {useState} from 'react';
const theStyle = `
.animated {
animation: showModal .2s forwards
}
#keyframes showModal {
from {
opacity: 0;
transform: scale(0.7);
}
to {
opacity: 1;
transform: scale(1);
}
}
.modalOverlay {
z-index: 1500;
background: rgba(40,91,218,0.5); /* you must use this and not opacity because opacity changes the front color */
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-content: stretch;
align-items: center;
}
.modalContainer {
z-index: 1600;
order: 0;
flex: 0 1 auto;
align-self: auto;
}
#modalContent {
z-index: 1700;
opacity: 0;
color: #fff;
width: 500px;
height: 200px;
background: #e74c3c;
position: relative;
border-radius: 3px;
margin: 0 auto;
}
`
function Button() {
const [getVisible, setVisible] = useState(false);
return (
<div>
<button onClick={() => setVisible(true)}>
show modal
</button>
{(getVisible) && <Modal setVisible={setVisible}/>}
</div>
)
}
function Modal({setVisible}) {
React.useEffect(
//() => window.setTimeout(document.getElementById("modalContent").classList.add("animated"))
() => document.getElementById("modalContent").classList.add("animated")
, [])
const handleClickOnOverlay = (e) => {
// clicks on the are sent through to the background so we must prevent that
e.stopPropagation()
setVisible(false)
}
const handleClickOnContainer = (e) => {
// clicks on the modal are sent through to the background so we must prevent that
e.stopPropagation()
}
const handleClickOnModal = (e) => {
console.log('clicked on modal')
}
return (
<>
<style>
{theStyle}
</style>
<div onClick={handleClickOnOverlay} className="modalOverlay">
<div className={`modalContainer`} onClick={handleClickOnContainer}>
<div id="modalContent" onClick={handleClickOnModal}>
This is a modal window.<br/>
<button onClick={() => setVisible(false)} className="md-close">close</button>
</div>
</div>
</div>
</>
);
}
ReactDOM.render(<Button/>, document.getElementById('root'));
I'm learning React - and even though I don't think this is a React problem - I have created an onClick animation for a menu button, which works, but the animation is running when the page first loads. How can I prevent the animation from running the page initially loads.
I've tried most of the css solutions found on SE, like this one, but none of them are working. Which makes me believe that there may be an issue with my react code.
class NavButton extends Component{
constructor(props) {
super(props);
this.state = {
value: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState( { value : !this.state.value } );
}
render(){
return (
<div>
<div id="mobile-nav-menu" onClick={this.handleClick}>
<div id="nav-toggle" className="nav-toggle-white"><span></span></div>
</div>
<div id="index-mobile-navigation-container" className={this.state.value ? "width" :"width2"}>
<div id="index-mobile-navigation">
Blog
</div>
</div>
</div>
);
}
}
export default NavButton
#index-mobile-navigation-container {
height: 100%;
background-color: #87c0c4;
z-index: 1;
position: absolute;
right: 0%;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
width: 0px;
}
#index-mobile-navigation {
position: absolute;
top: 100px;
left: 40px;
font-size: 25px;
font-family: "Rubik";
color: #FFFFFF;
}
#index-mobile-navigation:hover {
cursor: pointer;
}
.width {
animation: 1s in-out forwards;
-webkit-animation: 1s in-out forwards;
}
.width2 {
animation: 1s out forwards;
-webkit-animation: 1s out forwards;
}
#keyframes in-out {
0% {
width: 0;
}
100% {
width: 400px;
}
}
#keyframes out {
0% {
width: 400px;
}
100% {
width: 0px;
}
}
You can set the animation class after the action is performed.
In the current problem you are setting width2 animation onload, Because you are evaluating against this.state.value onload. A simple solution in your case is you can introduce an optional state where the value is undefined or null and then toggle the class based on action.
this.state = {
value: ""
};
getToggleClass = () => {
if (this.state.value === "") {
return "";
} else {
return this.state.value ? "width" : "width2";
}
};
<div
id="index-mobile-navigation-container"
className={this.getToggleClass()}
>
I don't know what is the animation you are trying to achieve.
But I have attached a code-sandbox link, with the above snippet. see if it helps
https://codesandbox.io/s/funny-stonebraker-o0sl1
I have an array of URLs that I am passing in as a prop to a slide component. Only one URL is passed at a time using a stateful index. Whenever the right button is clicked, the index is incremented by 1 and vice versa for the left. This is working to display the next URL in the array; however, the images seem to repeat even though I set no-repeat on the background. This is due to the images being different widths and some being smaller than the last. I've tried setting the parent container to a set width so that the slide class is 100% the width of the parent but this doesn't seem to be working for me. Below are the two components that I'm using and the styles file.
//Parent carousel component
const HeroImage = ({state}) => {
const [index, setIndex] = useState(0);
var imgUrls = [];
const goToPrevSlide = () => {
setIndex(index - 1);
}
const goToNextSlide = () => {
setIndex(index + 1);
}
return(
<React.Fragment>
<StyledHeroImage>
<div className="slider-wrapper">
{state.heroImage.map(image => {
imgUrls.push(`${IMAGE_BASE_URL}${BACKDROP_SIZE}${image.backdrop_path}`);
})}
<Slide title={state.heroImage.original_title} text={state.heroImage.overview} image={imgUrls[index]}/>
</div>
<LeftArrow goToPrevSlide={goToPrevSlide}/>
<RightArrow goToNextSlide={goToNextSlide}/>
</StyledHeroImage>
</React.Fragment>
)
}
//child Slide Component
const Slide = ({image, text, title}) => {
const styles = {
background: `linear-gradient(to bottom, rgba(0,0,0,0) 39%, rgba(0,0,0,0) 41%, rgba(0,0,0,0.65) 100%), url(${image})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
}
return(
<div className="slide" style={styles}>
<div className="heroimage-content">
<div className="heroimage-text">
<h1>{title}</h1>
<p>{text}</p>
</div>
</div>
</div>
)
}
//Styles file
export const StyledHeroImage = styled.div`
height: 650px;
width: 100%;
margin: 0 auto;
.slider-wrapper{
height: 100%;
width: 100%;
}
.slide{
width: 100%;
height: 100%;
}
.arrow{
height: 50px;
width: 50px;
display: flex;
align-items: center;
justify-content: center;
background: #f9f9f9;
border-radius: 50%;
cursor: pointer;
transition: transform ease-in .1s;
}
.nextArrow {
position: absolute;
top: 50%;
right: 25px;
z-index: 999;
color: #fff;
}
.backArrow {
position: absolute;
top: 50%;
left: 25px;
z-index: 999;
color: #fff;
}
.fa-arrow-right:before, .fa-arrow-left:before {
color: #222
}
`;
I am creating a carousel and there are 2 buttons Next and Previous. on clicking Next it should slide from left to right and on clicking Previous it should slide from right to left. (I don't want to use any plugin)
My React container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { prevItem, nextItem, initItem } from '../actions/index';
import { bindActionCreators } from 'redux';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
class Carousel extends Component{
previous(){
this.props.prevItem();
}
next(){
this.props.nextItem();
}
componentDidMount(){
this.props.initItem();
}
renderItem(){
const {item} = this.props;
const webLink = `http://${item.link}`;
const transitionOptions = {
transitionName: 'slide',
transitionEnterTimeout: 1000,
transitionLeaveTimeout: 1000
};
return(
<CSSTransitionGroup {...transitionOptions}>
<div className="carousel__item" key={item.id}>
<img className="carousel__image" src={item.imageurl}/>
<div className="carousel__text">
<h3>{item.title}</h3>
<p>{item.synopsis}</p>
{item.link}
</div>
</div>
</CSSTransitionGroup>
)
}
render(){
return(
<div className="carousel">
{this.renderItem()}
<div className="carousel__prev" onClick={this.previous.bind(this)}>
<i className="fa fa-chevron-left"></i>
</div>
<div className="carousel__next" onClick={this.next.bind(this)}>
<i className="fa fa-chevron-right"></i>
</div>
</div>
)
}
}
function mapStateToProps(state){
return {item: state.item};
}
function mapDispatchToProps(dispatch){
return bindActionCreators({
initItem: initItem,
nextItem: nextItem,
prevItem: prevItem
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Carousel);
Any my Style.css is:
.carousel{
width: 100%;
border: 1px solid #ccc;
margin-top: 100px;
position: relative;
}
.carousel__item{
width: 100%;
color: #fff;
position: relative;
}
.carousel__item img{
width: 100%;
background: rgba(0,0,0,.6);
}
.carousel__text{
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.carousel__text a:hover,
.carousel__text a{
color: #fff;
}
.carousel__prev{
position: absolute;
left: 0;
top: 50%;
color: #fff;
margin-left: 10px;
cursor: pointer;
font-size: 25px;
}
.carousel__next{
position: absolute;
right: 0;
top: 50%;
color: #fff;
margin-right: 10px;
cursor: pointer;
font-size: 25px;
}
.slide-enter{
transform: translate(100%);
}
.slide-enter-active{
transform: translate(0%);
transition: transform 1000ms ease-in-out;
}
.slide-leave{
transform: translate(0%);
}
.slide-leave-active{
transform: translate(-100%);
transition: transform 1000ms ease-in-out;
}
Apart from the direction, the current slide animation is not correct. when it slides the previous slide is showing below. So basically 2 slides are showing on screen on transition. Do you know how to fix this problem and also address the direction of slide?
I just worked through this problem.
In order to slide in the correct direction, you'll need to maintain some state and apply this state to your items via className. Something like this:
render () {
<CSSTransitionGroup
component={MenuWrapper}
transitionName="slide"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}>
{this._renderMenu(this.props.selectedSubMenus[menuIdx], menuIdx)}
</CSSTransitionGroup>
}
_renderMenu = (menu, idx) => {
return (
<Menu key={idx} className={this.state.animationDirection} order={idx}>
content
</Menu>
)
}
Your css should look something like this (note that I'm using styled-components for a few elements):
const menuWidth = "256px";
const MenuWrapper = styled.div`
display: flex;
overflow: hidden;
max-width: ${menuWidth};
`
const Menu = styled.div`
min-width: ${menuWidth};
order: ${props => props.order};
`
.slide-enter.left {
transform: translate(-100%);
}
.slide-enter.slide-enter-active.left {
transform: translate(0%);
transition: transform 500ms ease-in-out;
}
.slide-leave.left {
transform: translate(-100%);
}
.slide-leave.slide-leave-active.left {
transform: translate(0%);
transition: transform 500ms ease-in-out;
}
.slide-enter.right {
transform: translate(0%);
}
.slide-enter.slide-enter-active.right {
transform: translate(-100%);
transition: transform 500ms ease-in-out;
}
.slide-leave.right {
transform: translate(0%);
}
.slide-leave.slide-leave-active.right {
transform: translate(-100%);
transition: transform 500ms ease-in-out;
}
Note that I used flexbox in order to fix the display problem (as well as some hard-coded widths)
Hope this helps
We can create slide animation in the following way using slide for Dialog
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction='up' ref={ref} {...props} />
})
<Dialog
open={open}
TransitionComponent={Transition} // Transition fade
keepMounted
onClose={handleClose}
aria-labelledby='alert-dialog-slide-title'
aria-describedby='alert-dialog-slide-description'
fullWidth
></Dialog>