React: fading loaded image in, but not the cached one - javascript

There are a lot of topics about animating the loaded image, but I haven't seen a great example of this in React yet. I came with this component myself:
import { useState } from 'react';
export default function FadingImage({ src, ...props }) {
const [loaded, setLoaded] = useState(false);
return (
<img
src={src}
onLoad={() => setLoaded(true)}
className={!loaded ? 'loading' : ''}
{...props}
/>
);
};
img {
transition: opacity .25s;
}
.loading {
opacity: 0;
}
It works fine in the beginning, but then it's annoying that the same images get faded-in every single time. I'd like the cached images to appear instantly.
In vanilla JS it just works, because it's all done in the same render cycle. I'm not sure how it can be achieved in React.
Package suggestions are appreciated, but I'd also like to know how to do it for educational purpose.

Related

Image is loading double time in React due to useState?

I have the following logic that renders an image, the current flow is like the following: if the state is false a spinner shows then when the image loads the spinner disappears.
The core problem here is the state is re-rendering the component causing the image to load again I kind of ran out of options, on how to make an instant switch after the image loads.
It is not a loop but rather the image reloads again even if it is already loaded due to the render caused by setLoading(true).
How to prevent this reloading from happening. The useEffect logic is just a simulator for how it might take to load the image, but my real image coms from the icons variable.
export const iconsImg: React.FC<Props> = ({ img: string }) => {
useEffect(() => {
let newImg = new Image();
newImg.src = icons[img].props.src;
newImg.onload = () => {
setLoading(true);
};
}, []);
const icons: iconsInterface = {
a: <img className={classes.imgStyle} alt="a" src={link} />,
b: <img className={classes.imgStyle} alt="b" src={link} />,
}
const [loading, setLoading] = useState(false);
return (
<React.Fragment>
{!loading ? (
<Spinner>
) : (
icons[img]
)}
</React.Fragment>
)}
Issue
The problem with your last attempt is that you are starting with loading showing the Spinner, and you are trying to switch the state after the image is loaded, except that will never be the case, because the image is not mounted in the DOM.
Creating icons is not equivalent to creating img until it's added in the return and mounted into the DOM. And I think it's loading twice cause at some point you added the two of them in the DOM either directly or after loading changes.
Solution
You can simplify your component as below. Notice I removed icons and setting alt attribute (the one difference between the two) while creating the image. That setTimeout is so we see the loader. You can remove it later.
const IconsImg = ({ img }) => {
const [loading, setLoding] = React.useState(true);
return <div >{loading && "Loading..."} <img alt={img} style={{display: loading ? "none" : "block"}} onLoad= {()=> setTimeout(()=> setLoding(false), 1000)} src = "https://images.unsplash.com/photo-1657664042206-1a98fa4d153d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&auto=format&fit=crop&w=500&q=60"/></div>;
};
/* The below code is to have a working example here at Stack Overflow */
ReactDOM.render(
<IconsImg img= "car" />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
newImg is a different object from icons[img]. When loading set
to true, browser loads same src this time for icons[img].
You can try this:
First set display:none for icons[img] if loading is false;
And:
<React.Fragment>
{loading || (
<Spinner>
)}
icons[img]
</React.Fragment>
Then:
useEffect(() => {
icons[img].onload = () => {
setLoading(true);
};
}, []);
or better:
<img className={classes.imgStyle} alt="a" src={link} onload={() => setLoading(true)}/>

Trying to not fire a transition event when reloading the page

import { IconButton } from "#mui/material";
import React, { useState, useEffect } from "react";
import { Brightness4, Brightness7 } from "#mui/icons-material";
const ThemeChanger = () => {
const [themeState, setThemeState] = useState(false);
useEffect(() => {
const getTheme = localStorage.getItem("Theme");
if (getTheme === "dark") {
setThemeState(true);
} else {
}
}, []);
useEffect(() => {
if (themeState) {
localStorage.setItem("Theme", "dark");
document.body.classList.add("dark-mode");
} else {
localStorage.setItem("Theme", "light");
document.body.classList.remove("dark-mode");
}
}, [themeState]);
return (
<div>
<IconButton
className="icon-button"
style={{ marginTop: "16px" }}
onClick={() => {
setThemeState(!themeState);
document.body.classList.add("dark-light-toggle-transition");
}}
>
{themeState ? <Brightness4 /> : <Brightness7 />}
</IconButton>
</div>
);
};
export default ThemeChanger;
I am trying to make the former component to not fire the event of the transition that I have in "dark-mode" when I reload the page. The component is capable of toggling a dark mode.
I tried a bit of jQuery and the window.performance event but I could not make it work. I think the solution is not that hard but I am really loaded and my brain is not functioning anymore. Is there anyone who could help me?
P.S. I used a "preload" class with jQuery and set the class of the body into transition: none, however the problem is the useEffect as when the state is true it always adds the "dark-mode" class.
Thanks in advance!
EDIT: Actually I made it work. I just made a class called "dark-light-toggle-transition" which essentially is doing the work for me. I put it inside the jsx ThemeChanger component so it is only applied when I press the toggle button! If anyone needs more information, you can send me here!

How would I achieve this scroll background colour change effect?

Basically, assume I have 10 sections. Each have a different colour assigned to them for background colour.
When the user scrolls down from sections 1 through 10, I would like the tag background colour to change accordingly, depending which section is on screen.
Assuming the height of the viewport is 1000px, I would like the function to find out which section is currently at 800px out of 1000px, so the bottom 20%, then find the background color of that section in the bottom 20% and apply it to the tag until the user either scrolls to the next section, or scrolls up and another component takes over the background colour.
I have tried to use IntersectionObservor for this but I don't think it is the best approach for what I want.
Currently, my setup is, I am rendering multiple components after each other, each of them has a data attribute of "data-background={background}"
Then, the observer loops through, adds them all to the observer, and watches to find which one is on screen, but it isn't working completely for what I need.
Is there an easier way to achieve what I am looking for?
Here is the code I have so far
import Page from "../components/common/Page";
import Hero from "../components/molecules/Hero";
import TechStack from "#/components/organisms/TechStack";
import { useEffect } from "react";
const Home = () => {
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
console.log("entry", entry);
if (entry.isIntersecting) {
document.body.style.backgroundColor =
entry.target.dataset.background;
}
});
},
{ threshold: [0.20] }
);
// create an array of all the components to be watched
const components = [...document.querySelectorAll("[data-background]")];
components.forEach((component) => {
observer.observe(component);
});
}, []);
return (
<Page seo={{ title: "Starter Kit" }}>
<Hero />
<TechStack background="white"/>
<TechStack background="grey" />
<TechStack background="blue"/>
<TechStack background="green"/>
<TechStack background="grey"/>
<TechStack background="white"/>
</Page>
);
};
export default Home;
You can dynamically add the element to the observer when it mounted, like this
<div ref={(r) => r && observer.observe(r)} />
Here is the example: https://codesandbox.io/s/sleepy-margulis-1f7hz7

React w Gatsby: implemented sequential fade-in animation for gallery but it doesn't work right after being deployed

The source code is here: https://codesandbox.io/s/gatsby-starter-default-nvhl7
And the deployed site is here: https://csb-nvhl7-24q4bchuz.now.sh/
The effect I am trying to achieve is simple and straightforward.
First I used this query to load all the image files from the images folder
const data = useStaticQuery(graphql`
query {
allFile(
filter: {
extension: { regex: "/(jpg)|(jpeg)|(png)/" }
sourceInstanceName: { eq: "images" }
}
) {
edges {
node {
childImageSharp {
fluid(maxWidth: 800, quality: 95) {
aspectRatio
src
srcSet
originalName
srcWebp
srcSetWebp
sizes
}
}
}
}
}
}
Then I have a gallery component to display them by dividing these images into three groups, and we can use project1, project2 and project3 to navigate between them.
const Gallery = ({ minWidth }) => {
let refs = {}
const allPics = Image().map(({ childImageSharp }, i) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
refs[i] = useRef(null)
childImageSharp.index = i
return childImageSharp
})
const firsload = allPics.slice(0, 5)
const secload = allPics.slice(5, 10)
const third = allPics.slice(10)
const [imgs, setImgs] = useState(firsload)
const thumbnails = imgs.map(img => img.fluid.srcSet.split(" ")[0])
return (
<>
<ProjectsContainer>
<Project
onClick={() => {
setImgs(firsload)
}}
>
Project 1.
</Project>
<Project
onClick={() => {
setImgs(secload)
}}
>
Project 2.
</Project>
<Project
onClick={() => {
setImgs(third)
}}
>
Project 3.
</Project>
</ProjectsContainer>
<Mansory gap={"0em"} minWidth={minWidth}>
{imgs.map((img, i) => {
return (
<PicContainer key={img.index}>
<Enlarger
src={thumbnails[i]}
enlargedSrc={img.fluid.src}
index={img.index}
orderIndex={i}
onLoad={() => {
refs[img.index].current.toggleOpacity(1) <-- use ref to keep track of every Enlarger
}}
ref={refs[img.index]}
/>
</PicContainer>
)
})}
</Mansory>
</>
)
}
For every Enlarger that gets rendered by the Gallery, they are a zoom image component
import Img from "react-image-enlarger"
class Enlarger extends React.Component {
state = { zoomed: false, opacity: 0 } <--- initially every image's opacity is 0, then it shows up by being toggled opacity 1
toggleOpacity = o => {
this.setState({ opacity: o })
}
render() {
const { index, orderIndex, src, enlargedSrc, onLoad } = this.props
return (
<div style={{ margin: "0.25rem" }} onLoad={onLoad}> <-- where we toggle the opacity when the element is onloaded
<Img
style={{
opacity: this.state.opacity,
transition: "opacity 0.5s cubic-bezier(0.25,0.46,0.45,0.94)",
transitionDelay: `${orderIndex * 0.07}s`,
}}
zoomed={this.state.zoomed}
src={src}
enlargedSrc={enlargedSrc}
onClick={() => {
this.setState({ zoomed: true })
}}
onRequestClose={() => {
this.setState({ zoomed: false })
}}
/>
</div>
)
}
}
And I have made it clear in the code snippet where I implemented the sequential fade-in animation by using ref to control every Enlarger's toggleOpacity method.
This code works great on localhost, i.e. during development. You can try the codesandbox link above to see it. However the bug appears only when the page is deployed.. Click on the deployed version (https://csb-nvhl7-24q4bchuz.now.sh/) you can see that when browser first loading the page, some pictures are missing, because their opacity are still 0, which I suspect is because refs[img.index].current.toggleOpacity(1) somehow didn't get called on the image when they are onloaded. However the weird thing is, when you navigate between the projects, there's no problems at all with this animation and opacity change, they are normal. And when you refresh the page, the problem shows up again.
I been struggling with this problem for days and couldn't figure out why. Also although here I used ZEIT Now to deploy the site, the problem didn't go away when I used Netlify to deploy.
Its not because of deployment, in Production Your images taking more time to load. See the image network call in the browser
In addition to Kishore's answer, you may want make your query after the page loaded (maybe in componentDidMount) and use setState to trigger react to re-render once that content has been fully loaded.

How i play and pause React-gif image using ReactJS code

Long time I am tring to react-gif animate with play and pause.Like facebook gif image exactly.
I am using react-gif npm module.But does not working.Please anyone review my code.
coding.......
import Markdown from 'react-markdown';
import Gif from 'react-gif';
const linkRenderer = (linkNode) => {
return <a href={linkNode.href} title={linkNode.title} target="_blank" children={linkNode.children} />;
};
const imgixBase = 'https://chalees-min.imgix.net';
const imgixParameters = 'w=800&fit=max&auto=format,compress';
const imageRenderer = (imageNode) => {
const imageSource = imageNode.src.startsWith('/')
? `${imgixBase}${imageNode.src}?${imgixParameters}`
: imageNode.src;
return <Gif src={imageSource} alt={imageNode.alt} title={imageNode.title} />
};
const MarkdownCustomized = (props) => (
<Markdown
className={'markdown ' + (props.className || '')}
source={props.source || ''}
renderers={{
'Link': linkRenderer,
'Image': imageRenderer,
}} />
);
export default MarkdownCustomized;
You can't control (play-pause) an animated gif. Gifs in facebook are disguised videos.
There are some workarounds here, providing some kind of limited control.
I don't see any logic that makes the Gif component play or pause in your code.
Looking at the Gif react component code, I see two ways of making the Gif play and pause.
Set the prop playing to false to pause, and true to play. E.g Set gif to play
<Gif src={imageSource} alt={imageNode.alt} title={imageNode.title} playing={true} />
The Gif component has methods play and pause that can be accessed by using refs. So, on the component that uses Gif component, you can have something like the following:
class Parent extends Component {
handlePlayGif() {
this.currentGif.play();
}
handlePauseGif() {
this.currentGif.pause();
}
render() {
return ( //... rest of code
<Gif ref={(gif) => this.currentGif = gif} />
// ... rest of jsx
// onClick play gif
<div onClick={() => handlePlayGif()}>play gif</div>
);
}
}

Categories

Resources