Why carousel images are flickering in React - css

I have added carousel functionality but the images are flickering whenever I click on next icon. It seems like I get the reflection of the previous image or the next one as I have only two images. I'm using tailwind with react. I'm adding a gif below so one can see the problem. I'm getting flickering every time. But my screen recorder shows it only the first time.
Link to Gif
Index.css
.slide-in-from-right {
animation: slideInFromRight 0.5s ease-in;
}
.slide-out-to-left {
animation: slideOutToLeft 0.5s ease-in;
}
#keyframes slideInFromRight {
from {
transform: translateX(100%);
}
to {
transform: translateX(0%);
}
}
#keyframes slideOutToLeft {
from {
transform: translateX(0%);
}
to {
transform: translateX(-100%);
}
}
Hero SlideShow
let count = 0;
export default function HeroSlideshow() {
const [slide, setSlide] = useState({});
const [clonedSlide, setClonedSlide] = useState({});
const [slides, setSlides] = useState([]);
const [currentIndex, setCurrentIndex] = useState(0);
const slideRef = useRef();
const clonedSlideRef = useRef();
const { updateNotification } = useNotification();
const fetchLatestUploads = async () => {
const { error, movies } = await getLatestUploads();
if (error) return updateNotification("error", error);
setSlides([...movies]);
setSlide(movies[0]);
};
//0,1,2,3,4
const handleOnNextClick = () => {
setClonedSlide(slides[count]);
clonedSlideRef.current.classList.add("slide-out-to-left");
count = (count + 1) % slides.length;
setSlide(slides[count]);
setCurrentIndex(count);
slideRef.current.classList.add("slide-in-from-right");
};
const handleAnimationEnd = () => {
slideRef.current.classList.remove("slide-in-from-right");
clonedSlideRef.current.classList.remove("slide-out-to-left");
setClonedSlide({});
};
useEffect(() => {
fetchLatestUploads();
}, []);
return (
<div className="w-full flex">
{/* Slide show section */}
<div className="w-4/5 aspect-video relative overflow-hidden">
<img
ref={slideRef}
onAnimationEnd={handleAnimationEnd}
className="aspect-video object-cover"
src={slide.poster}
alt=""
/>
<img
ref={clonedSlideRef}
onAnimationEnd={handleAnimationEnd}
className="aspect-video object-cover absolute inset-0"
src={clonedSlide.poster}
alt=""
/>
<SlideShowController onNextClick={handleOnNextClick} />
</div>
{/* Up Next Section */}
<div className="w-1/5 aspect-video bg-red-300"></div>
</div>
);
}
const SlideShowController = ({ onNextClick, onPrevClick }) => {
const btnClass =
"bg-primary rounded border-2 text-white text-xl p-2 outline-none";
return (
<div className="absolute top-1/2 -translate-y-1/2 w-full flex items-center justify-between px-2">
<button onClick={onPrevClick} className={btnClass} type="button">
<AiOutlineDoubleLeft />
</button>
<button onClick={onNextClick} className={btnClass} type="button">
<AiOutlineDoubleRight />
</button>
</div>
);
};

It's difficult to answer if I cannot reproduce the issue but I have a clue of what's going on. When you call the method to handleOnNextClick you must take on mind that the slide's index (which is actually showing it as selected)'ll change almost instantly but the animation slideInFromRight and slideOutToLeft is taking .5 seconds to complete, so you can try to trigger the slide's index change with a setTimeout.
const handleOnNextClick = () => {
clonedSlideRef.current.classList.add("slide-out-to-left");
slideRef.current.classList.add("slide-in-from-right");
count = (count + 1) % slides.length;
setTimeout(() => {
setClonedSlide(slides[count]);
setSlide(slides[count]);
setCurrentIndex(count);
}, 500)
};
Hope it helps!

Related

React prevent css animation restarting every state change

I have two child components, an image gallery and a hidden components which will display the clicked image of the gallery at full size.
const [selectedIndex, setSelectedtIndex] = useState(0);
const [galleryVisible, setGalleryVisible] = useState(false);
const FirstComponent = () => {
return (
<div className={'gallery-container'}>
<div class='img fade-1' onClick={() => handleClick(1)}>Image 1</div>
<div class='img fade-2' onClick={() => handleClick(2)}>Image 2</div>
<div class='img fade-3' onClick={() => handleClick(3)}>Image 3</div>
[...]
</div>
)
}
const handleClick = (index) => {
setSelectedtIndex(index)
setGalleryVisible(true)
}
const SecondComponent = ({ index }) => {
return (
<div className={`selected-img`}>Selected : {index} (But the fading animation shouldn't restart è_é)</div>
)
}
return (
<>
<FirstComponent/>
{galleryVisible &&
<SecondComponent index={selectedIndex} />
}
</>
)
The issue is that I also have a fade-in animation on the first component, and every time I click on an image to display the second component, that animation resets due to the rerender of react on state change.
&.fade-1 {
animation-delay: 0s
}
&.fade-2 {
animation-delay: 0.5s
}
&.fade-3 {
animation-delay: 1s
}
I don't know how can I only change the second component when the state is changed from a click on the first one... I tried to play with useMemo but couldn't get it to work.
Here is a codepen reproducing the issue : https://codepen.io/disgallion/pen/zYjqPBE
Move the first component outside of App and pass handleClick to its props:
const FirstComponent = ({handleClick}) => {
return (
<div className={'gallery-container'}>
<div class='img fade-1' onClick={() => handleClick(1)}>1</div>
<div class='img fade-2' onClick={() => handleClick(2)}>2</div>
<div class='img fade-3' onClick={() => handleClick(3)}>3</div>
</div>
)
}
const App = () => {
const [selectedIndex, setSelectedtIndex] = useState(0);
const [galleryVisible, setGalleryVisible] = useState(false);
const handleClick = (index) => {
console.log('click')
setSelectedtIndex(index)
setGalleryVisible(true)
}
const SecondComponent = ({ index }) => {
return (
<div className={`selected-img`}>Selected : {index} (But the fading animation shouldn't restart è_é)</div>
)
}
return (
<>
Click on an image to open the fullsize gallery:
<FirstComponent handleClick={handleClick} />
{galleryVisible &&
<SecondComponent index={selectedIndex} />
}
</>
)
}
ReactDOM.render(<App />,
document.getElementById("root"))
Now the state change of App will not trigger a re-render in FirstComponent

CSS : Text is overflowing from div

I'm using tailwind with react. The problem is that my paragraph text is quite long. I have applied overflow-hidden to the div. But it is still flowing outside div. But it is not visible. But it is ruining my UI. As the pic is also getting stretched out due to the text overflowing. How do I stop the text from overflowing. So I only get the text that is visible inside div. I am using subString(0,50). It will fix the issue, if I use a smaller number. But I want to know is there any other way to fix it. So that the text does not go outside the div and occupies full space of div only. I tried many other properties to fix this issue. But no success so far.
I have uploaded a gif of my problem on imgur
Code
import React, { useEffect, useState } from "react";
import { BsBoxArrowUpRight, BsPencilSquare, BsTrash } from "react-icons/bs";
import { getActors } from "../api/actor";
let currentPageNo = 0;
let limit = 20;
export default function Actors() {
const [actors, setActors] = useState([]);
const [reachedToEnd, setReachedToEnd] = useState(false);
const fetchActors = async (pageNo) => {
const { profiles, error } = await getActors(pageNo, limit);
if (!profiles.length) {
currentPageNo = pageNo - 1;
return setReachedToEnd(true);
}
setActors([...profiles]);
};
const handleOnNextClick = () => {
if (reachedToEnd) return;
currentPageNo += 1;
fetchActors(currentPageNo);
};
const handleOnPrevClick = () => {
if (currentPageNo <= 0) return;
currentPageNo -= 1;
fetchActors(currentPageNo);
};
useEffect(() => {
fetchActors(currentPageNo);
}, []);
return (
<div className="p-5">
<div className="grid grid-cols-4 gap-5">
{actors.map((actor) => {
return <ActorProfile profile={actor} key={actor.id} />;
})}
</div>
<div className="flex justify-end items-center space-x-3 mt-5">
<button
type="button"
className="text-primary dark:text-white hover:underline"
onClick={handleOnPrevClick}
>
Prev
</button>
<button
type="button"
className="text-primary dark:text-white hover:underline"
onClick={handleOnNextClick}
>
Next
</button>
</div>
</div>
);
}
const ActorProfile = ({ profile }) => {
if (!profile) return null;
const [showOptions, setShowOptions] = useState(false);
const handleOnMouseEnter = () => {
setShowOptions(true);
};
const handleOnMouseLeave = () => {
setShowOptions(false);
};
let acceptedNameLength = 15;
const getName = (name) => {
if (name.length <= acceptedNameLength) return name;
return name.substring(0, acceptedNameLength) + "...";
};
const { name, avatar, about = "" } = profile;
return (
<div className="bg-white dark:bg-secondary shadow dark:shadow rounded h-20 overflow-hidden">
<div
onMouseEnter={handleOnMouseEnter}
onMouseLeave={handleOnMouseLeave}
className="flex cursor-pointer relative"
>
<img
src={avatar}
alt={name}
className="w-20 aspect-square object-cover"
/>
<div className="px-2 flex-1">
<h1 className="text-xl text-primary dark:text-white whitespace-nowrap">
{getName(name)}
</h1>
<p className="text-primary dark:text-white opacity-75">
{about.substring(0, 30)}
</p>
</div>
<Options visible={showOptions} />
</div>
</div>
);
};
const Options = ({ visible, onEditClick, onDeleteClick }) => {
if (!visible) return null;
return (
<div className="absolute inset-0 bg-primary bg-opacity-25 backdrop-blur-sm flex justify-center items-center space-x-5">
{" "}
<button
onClick={onDeleteClick}
type="button"
className="text-primary bg-white p-2 rounded-full hover:opacity-80 transition"
>
<BsTrash />
</button>
<button
onClick={onEditClick}
type="button"
className="text-primary p-2 rounded-full bg-white hover:opacity-80 transition"
>
<BsPencilSquare />
</button>
</div>
);
};

ReactTransitionGroup animation not playing on state change

I am trying to create an auto image slider with React and ReactTransitionGroup but I can't get animations to work. The image will change, but the transition effect won't play. I'm assuming it's the CSS because the class names do get added/removed.
Component
const ImgSlideshow = (props) => {
const images = [
'https://images.unsplash.com/photo-1593642634315-48f5414c3ad9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80',
'https://images.unsplash.com/photo-1593642702821-c8da6771f0c6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80',
'https://images.unsplash.com/photo-1593642532781-03e79bf5bec2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=634&q=80',
'https://images.unsplash.com/photo-1593642634443-44adaa06623a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=925&q=80',
'https://images.unsplash.com/photo-1593642634524-b40b5baae6bb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80'
]
const [slide, setSlide] = useState(0);
useEffect(() => {
const slideshowTimer = setInterval(() => {
((slide + 1) > images.length - 1) ? setSlide(0) : setSlide(slide + 1)
}, 3000)
return () => clearInterval(slideshowTimer)
})
return (
<div className={styles.slideshow}>
<SwitchTransition>
<CSSTransition key={slide} timeout={200} classNames='slideshowImg'>
<img src={images[slide]} className="slideshowImg"></img>
</CSSTransition>
</SwitchTransition>
{props.children}
</div>
)
}
CSS
.slideshowImg-enter {
opacity: 0;
transform: translateX(-100%);
}
.slideshowImg-enter-active {
opacity: 1;
transform: translateX(0%);
}
.slideshowImg-exit {
opacity: 1;
transform: translateX(0%);
}
.slideshowImg-exit-active {
opacity: 0;
transform: translateX(100%);
}
.slideshowImg-enter-active,
.slideshowImg-exit-active {
transition: opacity 500ms, transform 500ms;
}
There are a couple of issues that I see.
You don't need to specify the className from the classNames prop on the child element that is being transitioned. If you have styles attached to this class then I would recommend you change it to something else to avoid confusion, but I don't think that is affecting your transitions.
You are setting transitions to 500ms but only allowing the transition group 200ms to complete the transition.
It also looks like you are just changing the image source instead of using a new element each time.
I have a working code sandbox here
I think you can achieve the desired result with this refactor:
const ImgSlideshow = (props) => {
const images = [
"https://images.unsplash.com/photo-1593642634315-48f5414c3ad9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80",
"https://images.unsplash.com/photo-1593642702821-c8da6771f0c6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80",
"https://images.unsplash.com/photo-1593642532781-03e79bf5bec2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=634&q=80",
"https://images.unsplash.com/photo-1593642634443-44adaa06623a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=925&q=80",
"https://images.unsplash.com/photo-1593642634524-b40b5baae6bb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80"
];
const [slide, setSlide] = useState(0);
const imageElements = images.map((img, i) => {
return <img src={img} key={i} alt={`slideshow ${i+1}`} />;
});
useEffect(() => {
const slideshowTimer = setInterval(() => {
slide + 1 > images.length - 1 ? setSlide(0) : setSlide(slide + 1);
}, 3000);
return () => clearInterval(slideshowTimer);
});
return (
<div>
<SwitchTransition>
<CSSTransition key={slide} timeout={500} classNames="slideshowImg">
{imageElements[slide]}
</CSSTransition>
</SwitchTransition>
{props.children}
</div>
);
};
My snippet uses the map function to create an array of img elements, each with a src that is correspondent to the index of the images array. This way you will have multiple elements to transition in between. Right now you only have one image element, and the react-transition-group can't put an animation on the changing of the image src (afaik).

React Carousel not showing all images

I'm trying to build a carousel but can't figure out the problem. So the first image loads fine, but every following image isn't shown. I'm guessing the component isn't rerendering, but I'm not 100%
const [curImage, setCurImage] = useState (0)
const images = ['/images/slideshow/slideshow1.webp', '/images/slideshow/slideshow2.webp','/images/slideshow/slideshow3.webp', '/images/slideshow/slideshow4.webp', '/images/slideshow/slideshow5.webp', '/images/slideshow/slideshow6.webp']
const goToNext = () => {
setCurImage(curImage === images.length - 1 ? 0 : curImage + 1);
}
useEffect(() => {
setTimeout(goToNext, 2000);
return function() {
clearTimeout(goToNext);
}
})
return (
<div className='Slideshow'>
{images.map((image, key) => (
<div
className={key === curImage ? 'Slideshow__Images Active':'Slideshow__Images'}
key={key}
aria-hidden={key !== curImage}
>
{key === curImage && (
<img src={`${image}`} alt='Gulfport Votes Images'/>
)}
</div>
))}
</div>
)
CSS
.Slideshow__Images {
opacity: 0;
transition: 0.3s ease;
}
.Slideshow .Active {
opacity: 1;
transition-duration: 0.2s;
}

First time using React-Transition-Group

I have a image viewer component that as one large image and 3 smaller ones below it. When you hover over one of the bottom images it changes the large image to the corresponding image. I would like to add a fade-in/fade-out transition when the larger image changes. So far I can get a fade-in and a fade-out on the mouse completely leaving the smaller image but, not hovering between the images. Here is my code:
import React, { Component } from 'react';
import Transition from 'react-transition-group/Transition'
import Image0 from './Image1.jpg'
import Image1 from './Image2.jpg'
import Image2 from './Image3.jpg'
import './ImageViewer.css';
const duration = 300;
const Images = [Image0, Image1, Image2]
const defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
}
const transitionStyles = {
entering: { opacity: 1 },
entered: { opacity: 1 },
exiting: { opacity: .9 },
exited: { opacity: 0.01 }
};
const Fade = ({ in: inProp, currentImage }) => (
<Transition in={inProp} timeout={duration}>
{(state) => (
<div style={{
...defaultStyle,
...transitionStyles[state]
}}>
<img src={currentImage}/>
</div>
)}
</Transition>
);
class App extends Component {
state = { show: false, currentImg: Images[0] }
handleClick = e => {
this.setState({
show: !this.state.show,
currentImg: Images[Number(e.currentTarget.dataset.id)]
})
}
render() {
return (
<div className="App">
<Fade in={this.state.show} currentImage={this.state.currentImg}/>
<div className="small_image_wrapper">
<img className="small_image"
src={Image0}
alt="A wonderful bed"
data-id={0}
onMouseOver={this.handleClick}
onMouseOut={this.handleClick}
/>
<img className="small_image"
src={Image1}
alt="A wonderful bed"
data-id={1}
onMouseOver={this.handleClick}
onMouseOut={this.handleClick}
/>
<img className="small_image"
src={Image2}
alt="A wonderful bed"
data-id={2}
onMouseOver={this.handleClick}
onMouseOut={this.handleClick}
/>
</div>
</div>
);
}
}
export default App;
Also here's a link to what I have so far with transitions:
https://peaceful-jones-ee2ca2.netlify.com/
And without transitions:
https://practical-lamport-9cc94e.netlify.com/
I am not sure but you have to wrap all your images into TransitionGroup
import {TransitionGroup}, {Transition} from 'react-transition-group';
...
render() {
return (
<div className="App">
<TransitionGroup>
<Fade in={this.state.show} currentImage={this.state.currentImg}/>
</TransitionGroup>
<div className="small_image_wrapper">
/* here comes small images */
</div>
</div>
)};

Resources