Skip to content

Instantly share code, notes, and snippets.

@oauo
Last active February 4, 2024 12:38
Show Gist options
  • Save oauo/9bd7c0d391237874fcc9c8c216c86c4d to your computer and use it in GitHub Desktop.
Save oauo/9bd7c0d391237874fcc9c8c216c86c4d to your computer and use it in GitHub Desktop.
Medium style lazy loading images with react #codepen #react
{
"scripts": [
"react",
"react-dom",
"https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.6/index.min.js"
],
"styles": [
"font-awesome",
"https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
]
}
class LazyImage extends React.Component {
is_cached = src => {
//Cached images don't flash and get shown immediately
let image = new Image();
image.src = src;
return image.complete;
};
constructor(props) {
super(props);
this.element = React.createRef();
this.state = {
try: false,
visible: false,
shown: this.is_cached(this.props.main),
error: false
};
}
componentDidMount() {
this.observer = new IntersectionObserver(this.handleVisibilityChange, {
threshold: 0.5
});
this.observer.observe(this.element.current);
this.setState({ try: true });
}
imageLoaded = () => {
this.setState({ shown: true, error: false });
};
imageError = () => {
if (!this.state.shown) this.setState({ error: true });
};
handleVisibilityChange = e => {
if (e[0].intersectionRatio > 0) {
this.setState({ visible: true });
this.observer.unobserve(this.element.current);
}
};
render() {
return (
<div
ref={this.element}
className="lazyimage"
style={{ "--height": this.props.height * 100 + "%" }}
>
<img
className={classNames("image", "thumb")}
src={this.props.thumb}
alt={this.props.alt}
onError={this.imageError}
/>
{(this.state.shown || (this.state.try && this.state.visible)) && (
<img
className={classNames("image", "main", {
show: this.state.shown
})}
src={this.props.main}
alt={this.props.alt}
onError={this.imageError}
onLoad={this.imageLoaded}
/>
)}
{this.state.error && (
<div
className="error"
title={`Image could not be loaded. ${(this.props.alt || "")}`}
>
<i class="fas fa-exclamation-circle"></i>
</div>
)}
</div>
);
}
}
class LazyImageMod extends LazyImage {
/*
Only used for demo, do not use this for anything proper
With this element, the image cache is ignored and there is a delay until the image unblurs
*/
constructor(props) {
super(props);
this.props.thumb += "?" + Math.floor(Math.random() * 1000000);
this.props.main += "?" + Math.floor(Math.random() * 1000000);
this.state.shown = false; //🙈
}
imageLoaded = () => {
setTimeout(() => {
this.setState({ shown: true });
}, this.props.delay || 0);
};
}
const App = () => (
<div id="container">
<h1>Medium style lazy image loader with react</h1>
<h2>Normal</h2>
<p>
Loads blurred low resolution thumbnail, waits until page loads and image
is on the screen before loading the main image which then unblurs to
reveal the main image.
</p>
<p>
Image will not blur if it is in the device cache. The other examples on
this page have cache check disabled and will flash a blur.
</p>
<LazyImage
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade.jpg"
alt="woman leaning against monitor displaying Pacman game"
height={540 / 1260}
/>
<h2>Delayed loading & skip cache check</h2>
<p>This image is delayed by 1 second</p>
<LazyImageMod
delay={1000}
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade.jpg"
alt="woman leaning against monitor displaying Pacman game"
height={540 / 1260}
/>
<h2>Scroll to start loading, no delay</h2>
<p>
Once image is 50% in view it loads (customisable on line 20), images are{" "}
<b>not</b> delayed.
</p>
<LazyImageMod
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/concrete-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/concrete.jpg"
alt="man and woman sitting on floor"
height={630 / 1260}
/>
<LazyImageMod
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/road-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/road.jpg"
alt="woman walking in the middle of the road"
height={480 / 1260}
/>
<LazyImageMod
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade.jpg"
alt="woman leaning against monitor displaying Pacman game"
height={540 / 1260}
/>
<LazyImageMod
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/concrete-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/concrete.jpg"
alt="man and woman sitting on floor"
height={630 / 1260}
/>
<LazyImageMod
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/road-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/road.jpg"
alt="woman walking in the middle of the road"
height={480 / 1260}
/>
<LazyImageMod
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade.jpg"
alt="woman leaning against monitor displaying Pacman game"
height={540 / 1260}
/>
<LazyImageMod
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/concrete-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/concrete.jpg"
alt="man and woman sitting on floor"
height={630 / 1260}
/>
<LazyImageMod
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/road-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/road.jpg"
alt="woman walking in the middle of the road"
height={480 / 1260}
/>
<LazyImageMod
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/arcade.jpg"
alt="woman leaning against monitor displaying Pacman game"
height={540 / 1260}
/>
<a className="imageCredit" href="https://www.instagram.com/everydaysafilm/">
<i className="fab fa-instagram" /> Images from @everydaysafilm
</a>
<h2>Error</h2>
<p>Oops, the main image path isn't right.</p>
<LazyImage
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/error-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/errror.jpg"
height={540 / 1260}
/>
<LazyImage
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/error-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/errror.jpg"
alt="Red 'ERROR' on white background."
height={540 / 1260}
/>
<p>Main image is ok, but the thumbnail isn't now.</p>
<LazyImage
thumb="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/errror-thumb.jpg"
main="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/error.jpg"
alt="Red 'ERROR' on white background."
height={540 / 1260}
/>
</div>
);
ReactDOM.render(<App />, document.getElementById("app"));
body {
display: flex;
flex-direction: column;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin:2rem 4rem;
color:black;
#container {
max-width:40rem;
margin:auto;
}
.imageCredit {
color:white;
background-color: black;
text-decoration: none;
padding:.4rem .75rem .5rem .75rem;
border-radius: 2rem;
}
h2 {
padding-top:1rem;
&:nth-of-type(n+2) {
border-top: 1px solid #E6E9ED;
}
}
.lazyimage {
position: relative;
background:#E6E9ED;
border-radius: 1rem;
overflow: hidden;
margin-bottom: 2rem;
&.visible {
box-shadow:inset 0 2rem 0 0 black;
}
&:before {
content: '';
display: block;
margin-top: var(--height, 100%);
}
.image {
position: absolute;
top:0;
left:0;
background-size: cover;
background-position: center;
&.thumb, &.main {
filter: blur(.5rem);
width:100%;
}
&.thumb {
z-index: 0;
}
&.main {
z-index: 1;
opacity:0;
transition:opacity .25s, filter .25s .15s;
&.show {
opacity:1;
filter:blur(0.01rem); //Setting to 0 makes it jump a bit sometimes
}
}
}
.error {
z-index:5;
position: absolute;
bottom:.5rem;
right:.5rem;
color:black;
font-size:2rem;
text-shadow: 0 0 1rem white;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment