trigger animation whenever the <img src={value} /> src value changes - javascript

I'm using React to Create an img element that changes its src attribute after specific time with setInterval function based on an array that contains many images.
so the thing is i want to make an animation whenever the src attribute changes, i was thinking of using React.useEffect() hook to watch the src changes and add some animation but i couldn't go further logically.
my code :
import Canada from "../images/Canada.jpg"
import Tokyo from "../images/Tokyo.jpg"
import NewYork from "../images/NewYork.jpg"
import southKorea from "../images/southKorea.jpg"
function AboutMe() {
const imgArray = [NewYork, Tokyo, Canada, southKorea]
let i = 0;
const maxImgArray = imgArray.length -1 ;
const swap = function() {
let image = imgArray[i];
i = (i === maxImgArray) ? 0 : ++i;
const attr = document.getElementById("moving-image");
attr.src=image;
}
setInterval(swap, 5000)
return (
<section className="About-me">
<h1>About me</h1>
<div className="container">
<div className="cities-images">
<img
src={Tokyo}
alt="NewYork" id="moving-image"></img>
<label class="switch">
<input type="checkbox"/>
<span class="slider round"></span>
</label>
</div>
<div className="info">
</div>
</div>
</section>
)
}

By using useState hook in react, we can re-render the component with updated value. So for src change, we can use useState to update the current src of the image.
By using useEffect hook we can do anything once src state change like set different animations.
Component State
const [image, setImage] = useState(Usa);
const [imageIndex, setImageIndex] = useState(0);
const imgArray = [Usa, Japan, Canada, SouthKorea];
useEffect
useEffect(() => {
let interval = null;
if (i !== maxImgArray) {
interval = setInterval(() => {
let image = imgArray[imageIndex];
const attr = document.getElementById("moving-image");
attr.src = image;
// update the img index to state
setImageIndex((imageIndex) =>
imageIndex === maxImgArray ? 0 : imageIndex + 1
);
// update the src in state.
setImage(attr.src);
}, 3000);
}
// When our code runs and reruns for every render, useEffect also cleans up after itself using the cleanup function.
// Here we clear the interval to remove the effects that could happen with state change while interval.
return () => clearInterval(interval);
}, [image, imageIndex]); // when image, imageIndex gets change, useEffect will be triggered.
Component here I used framer-motion for animation.
<section className="Country-flag">
<h1>Country Flag</h1>
<div className="container">
<motion.div
key={image}
animate={{ x: 100, opacity: 1 }}
transition={{
delay: 1,
x: { type: "spring", stiffness: 100 },
default: { duration: 2 }
}}
>
{/* when state {image} value change, this component will re-render with new src */}
<img alt="NewYork" src={image} id="moving-image"></img>
</motion.div>
<div className="info"></div>
</div>
</section>
Check this sample sandbox for produced code.
If you want different animations for each img, then add another state variable for animation and change the value inside the useEffect for each image state.

Related

How to change/update className of a "nested" grandparent from a button, inside of a child component in react : SOLVED :

As the title says, I have a homepage which uses a map() function to map through an arrayList which holds the information about songs, like title, artist, songURL and img.
When I map() through it I will have many "songs" like you would have with blog posts. many elements which is an article which shares the same classname.
Within this map() function I assign a component to each object mapped which is called , this component renders waveforms etc.. the audiovisualizer component also have a button inside for Play/Pause features. and this is the button I want to interact with the Homepage.
When this button is clicked, I want to change the classname for this specific element.
Through useState and callback functions, I got it almost working, but instead of changing the classname for the specific elements that is related to the button clicked, ALL of the "elements/songs" where changed, and not the one targeted.
I will show you the code down under, so you can see the "Tree" and get an understanding of the code.
My result, would be that the button that is attached to each "song" element, would update that specific parent/grandparent element sit sits inside of. but I can't really seem to wrap my head around the logic.
Im completely new to react, this is my first project ever, so go easy on me.
There is also pictures to see what what the code is doing. My goal is to make the specific play/pause object to have an CSS class that will pull up that song and have a position so you can't scroll while its playing. I want to add an pseudo-element to change background color so you can only see that specific card from the list of cards.. from the picture you can see 2 songs. when I click the play/pause I want that card to get an absolute background that will cover the other song.. Problem is that when I try to onClick={} the button that is nested, I can't reach the event.target... and if I do, all of the are changed, not the specific parent element that holds the button.
EDIT - SOLVED
solved the entire problem by referencing the button using useRef. then I used that element like this let buttonRef = buttonRef.current.closest("my wanted parent by classname").classname, and then I changed that manually.
CODE:
Home.js
** this is where the songs/cards are made swith the map() function // be aware, I removed unrelated code for easier view.. **
const Home = (props) => {
const [songs, setSongs] = useState([]);
return (
<div className='home_wrapper'>
<>
{loading ?
<ClipLoader color="#36d7b7" />
:
<div className='homepage_container'>
<h1 className='homeTitleSongs'>Songs</h1>
<button className='logOutButton' onClick={handleLogout}>Logout</button>
{/* // Map through our song library and display all items on homepage */}
{ songs.map((data) => {
return (
// I WANT TP TARGET THE <Article> element´s classname and edit this when the button from inside <AudioVisualizer> component gets clicked.
// Problem is that this is a map function, so when i did get to change it, it changed ALL of the elements from the map function, not the one specific
// where the button is attached to.
<article key={data.id} className='card'>
<div className='card_content'>
<img className='card_image' src={data.image} alt=""/>
<div className='song_info'>
<h2>{data.title}</h2>
<h4>{data.artist}</h4>
</div>
<div className='audio_wrapper'>
<AudioVisualizer audio={data.audio}/>
</div>
</div>
</article>
)
})}
</div>
}
</>
</div>
)
}
export default Home
audioVisualizer component::
const AudioVisualizer = (props) => {
const [isPlaying, setIsPlaying] = useState(false)
const [volume, setVolume] = useState(0.5);
const playButton = faCirclePlay;
const pauseButton = faCirclePause;
const audioRef = useRef(null);
const audioTrackRef= useRef(undefined);
// Create audio waveform object, and load song from database..
useEffect(()=>{
if (audioRef.current){
audioTrackRef.current = wavesurfer.create({
container: audioRef.current,
progressColor: "#13AEA2",
waveColor: "red",
cursorColor: "OrangeRed",
preload: true,
backend: "WebAudio", // originally = "MediaElement"
barWidth: 2,
barHeight: 1, // the height of the wave
fillParent: true,
hideScrollbar: true,
responsive: true,
});
audioTrackRef.current.load(props.audio);
}
}, [])
// Handle play pause button
const handlePlayPause = (e) => {
// Get a view of what the "click" registers:
// if playing == pause
if ( ! isPlaying ) {
console.log("not playing.. Start playing");
audioTrackRef.current.play()
setIsPlaying(isClicked => true)
return
}
else {
console.log("Is playing.. will pause")
audioTrackRef.current.pause()
setIsPlaying(isClicked => false);
return
}
};
return (
<>
<div className='audio' ref={audioRef}>
</div>
<div className='audioKnobs'>
// This is the button i click for play pause a song inside a specific "song" card. and this card is the one i want to update the classname of..
<button className="playpausewrapper" onClick={handlePlayPause}>
<FontAwesomeIcon className={ isPlaying ? 'playButton activeButton' : 'playButton notActiveButton'} icon={ isPlaying ? pauseButton : playButton} />
</button>
<input type="range" className='VolumeSlider onPhoneRemoveVolumeSlider' id="volume" name="volume" min="0.01" max="1" step=".025" onChange={onVolumeChange} defaultValue={volume}/>
</div>
</>
)
}
export default AudioVisualizer;

React tab-like system using CSS classes

I have 5 div's and 5 buttons. On each button clicked one div become visible. the other four gets hidden. I just want to ask is there any other better way to do it. Give suggestion as much as possible. Thank you!
let id1 = React.createRef()
let id2 = React.createRef()
let id3 = React.createRef()
let id4 = React.createRef()
let id5 = React.createRef()
function iid1() {
id1.current.classList.remove('hidden')
id1.current.classList.add('contents')
id2.current.classList.add('hidden')
id3.current.classList.add('hidden')
id4.current.classList.add('hidden')
id5.current.classList.add('hidden')
}
function iid2() {
id1.current.classList.add('hidden')
id2.current.classList.remove('hidden')
id2.current.classList.add('contents')
id3.current.classList.add('hidden')
id4.current.classList.add('hidden')
id5.current.classList.add('hidden')
}
function iid3() {
id1.current.classList.add('hidden')
id2.current.classList.add('hidden')
id3.current.classList.remove('hidden')
id3.current.classList.add('contents')
id4.current.classList.add('hidden')
id5.current.classList.add('hidden')
}
function iid4() {
id1.current.classList.add('hidden')
id2.current.classList.add('hidden')
id3.current.classList.add('hidden')
id4.current.classList.remove('hidden')
id4.current.classList.add('contents')
id5.current.classList.add('hidden')
}
function iid5() {
id1.current.classList.add('hidden')
id2.current.classList.add('hidden')
id3.current.classList.add('hidden')
id4.current.classList.add('hidden')
id5.current.classList.remove('hidden')
id5.current.classList.add('contents')
}
I just want the above code to be more efficient & readable. I'm looking for best practices for javascript. You can also tell me you would you solve this problem. I'm not looking for answer's. I'm here to seek best practices,
Thank you.
Use state to identify which div is the selected one. Buttons will change the state and your app will re-render adjusting the classNames for the divs.
const App = () => {
const [selected,setSelected] = React.useState(0);
const DIV_IDS = [0,1,2,3,4,5];
const selectItems = DIV_IDS.map((item) => {
return(
<button onClick={() => setSelected(item)}>{item}</button>
);
});
const divItems = DIV_IDS.map((item) => {
return (
<div key={item} className={selected === item ? 'visible' : 'hidden'}>
I am div {item}
</div>
);
});
return(
<div>
<div>{selectItems}</div>
<div>{divItems}</div>
</div>
);
};
ReactDOM.render(<App/>, document.getElementById('root'));
.hidden {
visibility: hidden;
}
.visible {
visibility: visible;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
May be best to just have the class in your JSX element classes. Something like:
<element className={(condition_for_shown) ? 'contents' : 'hidden'}>
...
</element>
and then for each button would be:
<button type="button" onClick={() => setStateConditonToSomething}>
...
</button>
Note that you'll need to store the condition in react state with useState or however you wanna store it.
The way i'd do it is -
const DivHidingComponent = ({ elementCount = 5 }) => { // element count defaults to 5
const [visibilityIndex, setVisibilityIndex] = useState(0);
const onClickCallback = useCallback((index) => () => {
setVisibilityIndex(index);
})
const buttonGroup = useMemo(() => {
const buttonGroup = [];
for (let i = 0; i < elementCount; i++) {
buttonGroup.push(
<button key={`${i}-button`} onClick={onClickCallback(i)} />
)
}
return buttonGroup;
}, [elementCount])
// only re-runs on a button click
const divGroup = useMemo(() => {
const divGroup = [];
for (let i = 0; i < elementCount; i++) {
divGroup.push(
<div key={`${i}-div`} style={{ visibility: visibilityIndex === i ? 'visible' : 'hidden' }} />
);
}
return divGroup;
}, [visibilityIndex]);
return (
<div>
<div>
{buttonGroup}
</div>
<div>
{divGroup}
</div>
</div>
);
}
I set the style directly in the div group loop, but you could assign a class name or go about setting the style however you want.
Div's visibility is set by the visibility index that is driven by the buttons being clicked on.
I passed the elementCount variable in the props so you could scale this to however many elements you want. 5 or a 1000. I assigned elementCount a value of 5 that will act as a default for when no value is passed when the component is initialized.
Also, you could drop the useMemo and useCallback hooks and it would still execute fine. But it would help improve performance if you say, set the element count to 10,000. With those hooks in place it'd only re-build the div group on re-render. That'd be the difference between running the loops 20k times (10k for buttons, 10k for divs).
I added the last paragraph incase you were not aware of React Hooks!
I hope this helps!

Trigger animated div each time is called with React / Next.js

I'm new with React and Next.js, I'm creating a timeline, the timeline has colored bars which if pressed, the page shows a div below with info about each bar.
I managed to make the content below appear and disappear with useState hook so the content of each bar doesn't stack, I'm using an animated tag "Section" and only the first time I press any bar, the content is animated, the rest appears statically, I'm wondering if I can use something like the useEffect hook to maybe re-render each content div so the animation appears every time you click each bar, also to erase the last loaded div so doesn't stack on browser memory, I hope I explained myself I started with React 2 days ago, and thank you for your time.
Example reduced code:
//useState hook
const [content, setContent] = useState(null);
//Timeline section
<div>
<Bar1 onClick={() => setContent(BarContent_1)}/>
<Bar2 onClick={() => setContent(BarContent_2)}/>
</div>
//Content display
<div>
{content}
</div>
//Content of each bar, (<Section> div has the animation)
const BarContent_1 = () => {
return (
<Section>
Content of bar 1
</Section>
)
}
const BarContent_2 = () => {
return (
<Section>
Content of bar 2
</Section>
)
}
you can use useState for that to toggle classList on element here is example below what I do, in your project I don't know what you do but I will come up with card example first as your default value set state to false in card component and when you will click the button toggle that boolean false to true
like that () => setState(!state) it will always set the value of opposite state
when you will change state component always re-renders and checks the state, and you will make like that on more info div, if that state is true show more-info active that means your div will be displayed
and if it is false it will be dissapeared
const Card1 = () => {
const [showMore, setShowMore] = useState(false)
return (
<div className="card">
<div>Daniel</div>
<button onClick={() => setShowMore(!showMore)}>Show More</button>
<div class={showMore ? "more-info active": "more-info"}>This is more info Daniel</div>
</div>
)
}
also here is my css what I do
.more-info{
opacity: 0;
transition: 0.5s ease;
}
.active{
opacity: 1
}
in start thats good to make stuff like that but I would recommend you to use array objects for that to .map() components and make toggling animations on that, good luck
also quickly I made working example on sandbox
#callmenikk gave me an idea to use a conditional to render my styled div every time the condition met.
Solution:
//useState hook
const [content, setContent] = useState(null);
//Conditional function 'Refresh'
const Refresh = ({children}) => {
const active = content != setContent
if (active) {
return (
<Section>
{children}
</Section>
)
}
}
//Timeline section
<div>
<Bar1 onClick={() => setContent(BarContent_1)}/>
<Bar2 onClick={() => setContent(BarContent_2)}/>
</div>
//Content display and wrapping the content in my Refresh function
<Refresh>
{content}
</Refresh>
//Content of each bar, no need to wrap content in my styled (Section) tag since my Refresh button will
const BarContent_1 = () => {
return (
<div>
Content of bar 1
</div>
)
}
const BarContent_2 = () => {
return (
<div>
Content of bar 2
</div>
)
}

TypeError: Cannot set property 'onscroll' of null

I am trying to create scroll effect, where on left side image is changing based on scroll position and right side is changing content while scrolling.
I am getting error when trying to execute onscroll javascript function. I am not sure what am I doing wrong - I am trying to use it in React App - is it possible that React has different rules to call this function?
I created example in here:
https://codesandbox.io/s/scroll-effect-37ecc
I would use useRef for dom manipulation in React. That will be optimized.
import { useLayoutEffect, useState, useRef, useEffect } from "react";
import { render } from "react-dom";
import classnames from "classnames";
import "./index.css";
const App = () => {
// const scrollingDiv = document.getElementById("scrollContainer");
// const img1 = document.getElementById("img1");
// const img2 = document.getElementById("img2");
const scrollingDiv = useRef(null);
const image1 = useRef(null);
const image2 = useRef(null);
useEffect(() => {
scrollingDiv.current.onscroll = function () {
if (scrollingDiv.scrollTop < 250) {
image1.current.src = "https://placeimg.com/250/100/arch";
image2.current.src = "https://placeimg.com/250/100/animals";
}
if (scrollingDiv.current.scrollTop > 500) {
image1.current.src = "https://placeimg.com/250/100/nature";
image2.current.src = "https://placeimg.com/250/100/people";
}
if (scrollingDiv.current.scrollTop > 1000) {
image1.current.src = "https://placeimg.com/250/100/tech";
image2.current.src = "https://placeimg.com/250/100/any";
}
};
}, []);
return (
<>
<div class="container">
<div class="left">
<img ref={image1} id="img1" src="https://placeimg.com/250/100/arch" />
<img ref={image2} id="img2" src="https://placeimg.com/250/100/animals" />
</div>
<div class="middle" ref={scrollingDiv} id="scrollContainer">
<div class="in-middle">
<div class="in-in-middle" id="1"></div>
<div class="in-in-middle" id="2"></div>
<div class="in-in-middle" id="3"></div>
</div>
</div>
</div>
</>
);
};
render(<App />, document.getElementById("root"));
Or you can put direct listeners on the element as well using onSroll prop.
Also, don't forget to clear all the listeners in useEffect cleaning function, that way, you won't have any memory leak issues.
normally, you would use useRef() function from react to get direct access to DOM elements, you can then use them like this:
const scrollingDiv = useRef(null);
<div id="scrollingSomething" ref={scrollingDiv}>
then you can manipulate the element using scollingDiv reference to the element
https://codesandbox.io/s/scroll-effect-forked-3nkbb

glitch from React hooks?

I am trying to build a countdown timer app with start/pause button using React hooks.
However, the interaction does not responds as expected.
As the screenshot of console.log shows here:
In Event 2, when pause button gets clicked, props.setIsPaused(true) should set isPaused true, but console.log shows false. However, the value of isPaused in parent component gets updated to true according to console.log.
In Event 3, when start button gets clicked, props.setIsPaused(false) should set isPaused false, but console.log shows true. However, the value of isPaused in parent component gets updated to false according to console.log.
In this case, countdown timer does not start, I need to click start button for 2nd time to have it finally start the countdown.
I am totally confused by these actions out of my expectations.
Any ideas or thoughts what I missed or mistakes I have?
Thank you.
Here is the code of the child/parent component performing timer countdown:
// Child Component
function TimerPanel(props) {
const launch = React.useRef();
var secLeft = parseInt(props.timerNow.min) * 60 + parseInt(props.timerNow.sec)
const startCountDown = () => {
props.setIsPaused(false) // Once start button is clicked, set isPaused false
console.log('props.isPaused inside startCountDown func: ' + props.isPaused) // This should be false
launch.current = setInterval(decrement, 1000)
}
function decrement() {
if (secLeft > 0 && !props.isPaused) {
console.log('props.isPaused inside decrement func: '+ props.isPaused)
secLeft--;
var minCountDown = Math.floor(secLeft / 60)
var secCountDown = Math.floor((secLeft % 60) * 100) / 100
props.setTimerNow({
...props.timerNow,
min: minCountDown,
sec: secCountDown})
}
}
const pauseCountDown = () => {
props.setIsPaused(true) // Once pause button is clicked, set isPaused true
console.log('props.isPaused inside pauseCountDown func: '+ props.isPaused) // This should be true
clearInterval(launch.current)
}
......
}
// Parent Component
function App() {
const [breakLength, setBreakLength] = React.useState(5)
const [sessionLength, setSessionLength] = React.useState(25)
const [title, setTitle] = React.useState('Session')
const initialTimer = {
min: 25,
sec: 0
}
const [timerNow, setTimerNow] = React.useState(initialTimer)
const [isPaused, setIsPaused] = React.useState(false)
console.log(timerNow)
console.log('isPaused in App: ' + isPaused)
..........
}
Issues
I think what may've been tripping you up (I know it was me) was the split of logic between the app component and the timer panel component. All the state was declared in App, but all the state was mutated/updated in children components. I think some state was also poorly named, which made working with it a little confusing.
I've directly answered your concerns about the reflected values of isPaused being "delayed" in the comments, but I wanted to see how I could help.
Suggested Solution
Move all the logic to manipulate the timer state into the same component where the state is declared, pass functions to children to callback to "inform" the parent to do some "stuff".
In the following example I was able to greatly reduce the burden of TimerPanel trying to compute the time to display and trying to update it and keep in sync with the paused state from the parent.
TimerPanel
Receives timer value, in seconds, and computes the displayed minutes and values since this is easily derived from state. Also receives callbacks to start/stop/reset the timer state.
function TimerPanel({
timerNow,
title,
startTimer,
pauseTimer,
resetTimer,
}) {
const minutes = Number(Math.floor(timerNow / 60))
const seconds = Number(Math.floor((timerNow % 60) * 100) / 100)
return (
<div className="timer-wrapper">
<div className="display-title">{title}</div>
<div className="display-digits">
{minutes.toString().padStart(2, "0")}:
{seconds.toString().padStart(2, "0")}
</div>
<div className="control-keysets">
<i className="fas fa-play-circle" onClick={startTimer}>
Play
</i>
<i className="fas fa-pause-circle" onClick={pauseTimer}>
Pause
</i>
<button className="reset" onClick={resetTimer}>
Reset
</button>
</div>
</div>
);
}
App
Simply timer state to be just time in seconds as this makes time management much simpler. Create handlers to start, pause, and reset the timer state. Use an useEffect hook to run the effect of starting/stopping the time based on the component state. Do correct state logging in an useEffect hook with appropriate dependency.
function App() {
const [breakLength, setBreakLength] = React.useState(5);
const [sessionLength, setSessionLength] = React.useState(25);
const [title, setTitle] = React.useState("Session");
const initialTimer = 60 * 25;
const [timerNow, setTimerNow] = React.useState(initialTimer);
const [isStarted, setIsStarted] = React.useState(false);
const timerRef = React.useRef();
const start = () => setIsStarted(true);
const pause = () => setIsStarted(false);
const reset = () => {
setIsStarted(false);
setTimerNow(initialTimer);
}
React.useEffect(() => {
console.log("isStarted in App: " + isStarted);
if (isStarted) {
timerRef.current = setInterval(() => setTimerNow(t => t - 1), 1000);
} else {
clearInterval(timerRef.current)
}
}, [isStarted]);
React.useEffect(() => {
console.log('timer', timerNow);
}, [timerNow])
return (
<div className="container">
<h1>Pomodoro Timer</h1>
<div className="group">
<div className="block">
<BreakLength
breakLength={breakLength}
setBreakLength={setBreakLength}
isPaused={!isStarted}
/>
</div>
<div className="block">
<SessionLength
sessionLength={sessionLength}
setSessionLength={setSessionLength}
timerNow={timerNow}
setTimerNow={setTimerNow}
isPaused={!isStarted}
/>
</div>
</div>
<div className="bottom-block">
<TimerPanel
title={title}
setSessionLength={setSessionLength}
timerNow={timerNow}
startTimer={start}
pauseTimer={pause}
resetTimer={reset}
/>
</div>
</div>
);
}
I left the BreakLength and SessionLength components intact as they don't appear to be utilized much yet.
Demo

Categories

Resources