React Carousel not showing all images - css

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;
}

Related

CSS/Vue.js synchronize animation reset with ref value change

I want to create an animation that fades between 3 states. To store these states (that contain an image and some text), I've created an array of objects (uploadSteps). I increment the index of this array at a specific interval to create an animation.
My problem comes with CSS, I would like to create a fade-out/fade-in effect between each step, so that the transition is more smooth. It works most of the time but sometimes, after a refresh and for the first transition, I think the CSS animation is restarted before the state is changed. I wasn't able to reproduce it constantly so far.
What it does => Image fade-in -> Image 1 -> Image 1 fade-Out -> Image 1 -> Image 2 fade-in
What I want => Image fade-in -> Image 1 -> Image 1 fade-Out -> Image 2 fade-in
If you pay attention you can see that there's kind of a blink effect at the transition between image 1 and 2. What could I do to solve this ? Help appreciated !
PS: I've included some videos to illustrate my issue
https://streamable.com/4aigjq - Animation KO
https://streamable.com/umu3nj - Animation OK
<template>
<div class="screenshot-upload-processing-container">
<div class="image-container">
<img
ref="imageRef"
:src="
currentStep ? $requireImage(currentStep.imagePath).toString() : ''
"
:alt="currentStep.text"
class="image"
/>
</div>
<div class="centered-row">
<span ref="textRef" class="text">{{
currentStep ? currentStep.text : ''
}}</span>
</div>
</div>
</template>
<script lang="ts">
import {
computed,
defineComponent,
onBeforeUnmount,
ref,
} from '#nuxtjs/composition-api';
export default defineComponent({
name: 'ScreenshotUploadProcessing',
setup() {
const stepNumber = ref(0);
const timer = ref<NodeJS.Timeout | null>(null);
const imageRef = ref<HTMLImageElement | null>(null);
const textRef = ref<HTMLSpanElement | null>(null);
const uploadSteps = computed(() => {
return [
{
imagePath: 'image1.svg',
text: 'Text 1',
},
{
imagePath: 'image2.svg',
text: 'Text 2',
},
{
imagePath: 'image3.svg',
text: 'Text 3',
},
];
});
const resetAnimations = () => {
const image = imageRef.value;
const text = textRef.value;
if (image) {
image.style.animation = 'none';
void image.offsetHeight;
image.style.animation = '';
}
if (text) {
text.style.animation = 'none';
void text.offsetHeight;
text.style.animation = '';
}
};
const updateStepNumber = () => {
timer.value = setInterval(() => {
if (stepNumber.value === uploadSteps.value.length - 1) {
stepNumber.value = 0;
} else {
stepNumber.value++;
}
resetAnimations();
}, 3000);
};
onBeforeUnmount(() => {
clearInterval(timer.value as NodeJS.Timeout);
});
const currentStep = computed(() => uploadSteps.value[stepNumber.value]);
updateStepNumber();
return {
stepNumber,
currentStep,
imageRef,
textRef,
uploadSteps,
};
},
});
</script>
<style lang="scss" scoped>
#keyframes fade {
0%,
100% {
opacity: 0;
}
40%,
60% {
opacity: 1;
}
}
.screenshot-upload-processing-container {
display: flex;
flex-direction: column;
height: 100%;
}
.image {
animation: fade 3s linear;
}
.image-container {
display: flex;
align-items: center;
justify-content: center;
height: 27rem;
margin-top: 10.3rem;
}
.centered-row {
display: flex;
align-items: center;
justify-content: center;
margin-top: $spacing-xl;
}
.text {
#extend %text-gradient-1;
animation: fade 3s linear;
font-weight: $font-weight-medium;
}
</style>

React component with state triggers animation in subcomponent when the state of parent component is changed

ProjectsSection component renders a document section with header and cards. A header animation should be triggered when the header is visible on the screen for the 1st time and run only once. However when I added a state to the ProjectsSection component with every state change a header animation runs and what is surprising to me only a part of it (letters/ spans that are children to h2 move, but brackets/ pseudoelements on h2 don't). In my project I use css modules for styling.
I have tried to wrap SectionHeader component in React.memo but it does not help.
Main component:
const ProjectsSection = () => {
const [openProjectCardId, setOpenProjectCardId] = useState('');
return (
<section id="projects" className="section">
<div className="container">
<SectionHeader>Projects</SectionHeader>
<div className={styles.content_wrapper}>
{projects.map((project, ind) => (
<Project
key={project.id}
project={project}
ind={ind}
isOpen={openProjectCardId === project.id}
setOpenProjectCardId={setOpenProjectCardId}
/>
))}
</div>
</div>
</section>
);
};
SectionHeader component:
const SectionHeader = ({ children }) => {
const headerRef = useIntersection(
styles.sectionHeader__isVisible,
{ rootMargin: '0px 0px -100px 0px', threshold: 1 },
);
const textToLetters = children.split('').map((letter) => {
const style = !letter ? styles.space : styles.letter;
return (
<span className={style} key={nanoid()}>
{letter}
</span>
);
});
return (
<div className={styles.sectionHeader_wrapper} ref={headerRef}>
<h2 className={styles.sectionHeader}>{textToLetters}</h2>
</div>
);
};
Css
.sectionHeader_wrapper {
position: relative;
// other properties
&::before {
display: block;
content: ' ';
position: absolute;
opacity: 0;
// other properties
}
&::after {
display: block;
content: ' ';
position: absolute;
opacity: 0;
// other properties
}
}
.sectionHeader {
position: relative;
display: inline-block;
overflow: hidden;
// other properties
}
.letter {
position: relative;
display: inline-block;
transform: translateY(100%) skew(0deg, 20deg);
}
.sectionHeader__isVisible .letter {
animation: typeletter .25s ease-out forwards;
}
useIntersection hook
const useIntersection = (
activeClass,
{ root = null, rootMargin = '0px', threshold = 1 },
dependency = [],
unobserveAfterFirstIntersection = true
) => {
const elementRef = useRef(null);
useEffect(() => {
const options: IntersectionObserverInit = {
root,
rootMargin,
threshold,
};
const observer = new IntersectionObserver((entries, observerObj) => {
entries.forEach((entry) => {
if (unobserveAfterFirstIntersection) {
if (entry.isIntersecting) {
entry.target.classList.add(activeClass);
observerObj.unobserve(entry.target);
}
} else if (entry.isIntersecting) {
entry.target.classList.add(activeClass);
} else {
entry.target.classList.remove(activeClass);
}
});
}, options);
// if (!elementRef.current) return;
if (elementRef.current) {
observer.observe(elementRef.current);
}
}, [...dependency]);
return elementRef;
};
I have found a solution to this problem.
This was apparently about textToLetters variable created every time the state of parent component was changing.
I used useMemo to keep this value and now animation is not triggered anymore.
const textToLetters = (text) =>
text.split('').map((letter) => {
const style = !letter ? styles.space : styles.letter;
return (
<span className={style} key={nanoid()}>
{letter}
</span>
);
});
const SectionHeader= ({ children }) => {
const headerRef = useIntersection(
styles.sectionHeader__isVisible,
{ rootMargin: '0px 0px -100px 0px', threshold: 1 },
[children]
);
const letters = React.useMemo(() => textToLetters(children), [children]);
return (
<div className={styles.sectionHeader_wrapper} ref={headerRef}>
<h2 className={styles.sectionHeader}>{letters}</h2>
</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).

Why animation works only for the first slide and not for the others?

I am trying to make a simple slideshow with fade in animation using react hooks. But my animation works only on first slide and not for others.
This is function code:
import React, { useEffect, useRef, useState } from "react";
import './Homepage.css';
const Homepage = () => {
const slides = [
"../../backgroundImages/1a.jpg",
"../../backgroundImages/1b.jpg",
"../../backgroundImages/1.jpg"
];
const playRef = useRef<any>(null);
const [slideIndex, setSlideIndex] = useState<number>(0);
useEffect(() => {
playRef.current = nextSlide;
});
useEffect(() => {
const play = setInterval(() => {
playRef.current();
}, 3000);
return () => {
clearInterval(play);
}
}, []);
const nextSlide = () => {
slideIndex >= slides.length -1
? setSlideIndex(0)
: setSlideIndex((slideIndex) => slideIndex + 1);
}
return(
<img alt='background' src={slides[slideIndex]}
className='homepage-background-div'
/>
);
}
export default Homepage;
and this is css:
.homepage-background-div {
position: absolute;
top: 0;
height: 100vh;
width: 100vw;
z-index: -1;
animation: fadein 2s;
}
#keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
I have red answers for similar questions here on stackoverflow as well as many others articles and tutorials but was not able to solve the issue. Any help will be more than welcomed.
add key={slides[slideIndex]} to tag img
<img
alt="background"
key={slides[slideIndex]}
src={slides[slideIndex]}
className="homepage-background-div"
/>

ReactJs DropDown Menu, increase and decrease height animation

Hello I have the following objective, I'm trying to make a height increase effect and the reverse effect with the keyframes:
like this:
and so far I only got the effect by opening, and I'm not able to do the reverse transition effect, that is, when closing, gradually decrease the height
i get this:
code:
JSX:
const MenuItem = ({ key, index, tag, setVisible, visibleMenu }) => {
const { name, dropdownItems, icon } = tag;
const handleClick = index => {
if (visibleMenu === true) return setVisible("none");
if (visibleMenu === index) return setVisible("none");
return setVisible(index);
};
return (
<ListItem active={visibleMenu}>
<div
onClick={() => {
handleClick(index);
}}
className="li-menuOpen"
>
<a>
<FontAwesomeIcon
className="icon-li"
icon={icon}
size={"lg"}
fixedWidth
color="white"
/>
<span
className="li-name"
onClick={() => {
handleClick(index);
}}
>
{name}
</span>
</a>
<span className="svg-arrow">
{dropdownItems ? (
<FontAwesomeIcon
className="down-up_svg"
icon={visibleMenu ? faAngleUp : faAngleDown}
size="1x"
fixedWidth
color="white"
/>
) : (
""
)}
</span>
</div>
{dropdownItems ? (
<DropDown list={dropdownItems} active={visibleMenu} />
) : (
""
)}
</ListItem>
);
};
export default function App() {
const MenuTags = [
{
name: "Carteira",
link: "../cartcredit",
icon: faWallet,
dropdownItems: [
{ name: "Histórico", link: "/history" },
{ name: "Adicionar Crédito", link: "/add" }
]
}
];
const [visible, setVisible] = useState(null);
return (
<MenuList>
{MenuTags.map((item, index) => (
<MenuItem
key={index}
index={index}
tag={item}
setVisible={setVisible}
visibleMenu={visible === index ? true : false}
/>
))}
</MenuList>
);
}
DropDown JSX and CSS WITH EMOTION
const increaseHeightDrop = keyframes`
from {
max-height: 0;
}
to {
max-height: 500px;
}
`;
const decreaseHeight = keyframes`
from {
max-height: 500;
}
to {
max-height: 0px;
display:none;
}
`;
const OpenedStyled = styled.ul`
${props => {
if (props.active) {
return css`
animation: ${decreaseHeight} 0.8s;
-webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards;
animation-fill-mode: forwards;
`;
}
}}
overflow: hidden;
animation: ${increaseHeightDrop} 0.8s;
-webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards;
animation-fill-mode: forwards;
padding: 7px 15px;
background: #307096;
li {
white-space: nowrap;
padding: 5px 5px 5px 0px;
}
a {
font-family: "Ubuntu";
font-size: 14px;
font-weight: 300;
text-decoration: none;
color: #fff;
}
& .li-open:hover > .icon-li-drop {
color: orange;
}
& .icon-li-drop {
margin-right: 10px;
}
`;
const OpenedSide = ({ open, active, list }) => {
return (
<>
{active ? (
<OpenedStyled open={open} active={active}>
{list.map(item => (
<li className="li-open" key={item.name}>
<FontAwesomeIcon
className="icon-li-drop"
icon={faCircleNotch}
size="1x"
fixedWidth
color="white"
/>
<a>{item.name}</a>
</li>
))}
</OpenedStyled>
) : (
""
)}
</>
);
};
const DropDown = ({ active, list }) => {
console.log(list);
return <OpenedSide active={active} list={list} />;
};
export default DropDown;
example:
https://codesandbox.io/s/purple-silence-ytc5h?file=/src
In my codesand box I have a working example,
basically i just have difficulties in how i can make the transition when closing the dropdown, if anyone has any better idea for the code i would be grateful

Resources