#TIL
Today I learned about HTML's lazy loading property. Once again, HTML provides us another simple solution for a once-difficult problem.
So what is "lazy loading"?
According to this sitepoint article:
Lazy loading images means loading images on websites asynchronously — that is, after the above-the-fold content is fully loaded, or even conditionally, only when they appear in the browser’s viewport. This means that if users don’t scroll all the way down, images placed at the bottom of the page won’t even be loaded.
Why Should We Care?
Let's say you visit a site like https://unsplash.com/. They host zillions of high-quality photos, that require a decent amount of resources to load. Without the ability to load them as you need them, you would be saddled with WAY MORE data-usage than you likely would need or want!
In the past, you had a few options to load images on demand, but none of them were intuitive or developer-friendly. Enter the HTML loading="lazy"
property.
It looks like this:
<img src={meme.url} alt={meme.name} loading="lazy" />
That's it. 14 characters is all the code required to achieve image-on-demand functionality.
The Effect in Action!
I created both a CodeSandbox and an small app hosted on vercel, https://html-load-lazy.vercel.app/.
In my CodeSandbox I fetch to the Imgflip API, which gives us their 100 current most popular memes. The embedded CodeSandbox here actually shows the lazy loading behaviour really well! Press the Get Memes
button, and start scrolling down. You will notice that images near the bottom, coming into view, appear to blink. That's the website loading each image on demand!
For whatever reason, it doesn't work as well in full-browser mode. Well, it does work. However, potentially without the visual cue each image is loading, like you do here with the embed. Possibly because the images are all cached from testing the API already.
The Code
import "./styles.css";
import { useState } from "react";
function shuffle(array) {
let currentIndex = array.length,
temporaryValue,
randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
export default function App() {
const [memes, setMemes] = useState([]);
const BASE_URL = "https://api.imgflip.com/get_memes";
const getMemes = () => {
return async () => {
try {
const res = await fetch(BASE_URL);
if (!res.ok) {
throw res;
}
const memeData = await res.json();
setMemes(memeData.data.memes);
} catch (err) {
alert("Failed to load memes");
}
};
};
const clearPhotos = () => {
setMemes([]);
};
const shuffledMemes = shuffle(memes);
return (
<div className="App">
<h1>HTML Lazy Loading</h1>
<div className="btn-wrapper">
<button onClick={getMemes()}>Get Memes</button>
<button onClick={clearPhotos}>Clear Photos</button>
</div>
{memes.length > 0 &&
shuffledMemes.map((meme) => (
<div key={meme.id}>
<p>{meme.name}</p>
<img src={meme.url} alt={meme.name} loading="lazy" />
</div>
))}
</div>
);
}
I fetch to the API and store the array of images in state, setMemes(memeData.data.memes)
. I decided to shuffle them with the shuffle()
function defined at the top of the App.js
component, and map through the shuffledMemes
to render both the name of the meme and its corresponding image!
Thanks, HTML, once again you've solved a difficult problem with such poise and elegance.
Bonus Code
As mentioned above, I hosted a small app on Vercel, https://html-load-lazy.vercel.app/ in which I fetch to both Imgflip and the Unsplash API.
Here is the full App.js
component:
import "./App.css";
import { useState } from "react";
function shuffle(array) {
let currentIndex = array.length,
temporaryValue,
randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
const ACCESS_KEY = process.env.REACT_APP_ACCESS_KEY;
export default function App() {
const [memes, setMemes] = useState([]);
const [photos, setPhotos] = useState([]);
const MEME_URL = "https://api.imgflip.com/get_memes";
const UNSPLASH_URL = `https://api.unsplash.com/photos/?client_id=${ACCESS_KEY}`;
const getMemes = () => {
return async () => {
try {
const res = await fetch(MEME_URL);
if (!res.ok) {
throw res;
}
const memeData = await res.json();
setMemes(memeData.data.memes);
setPhotos([]);
} catch (err) {
alert("Failed to load memes");
}
};
};
const getPhotos = () => {
return async () => {
try {
const res = await fetch(UNSPLASH_URL);
if (!res.ok) {
throw res;
}
const photoData = await res.json();
setPhotos(photoData);
setMemes([]);
} catch (err) {
alert("Failed to load memes");
}
};
};
const clearPhotos = () => {
setMemes([]);
setPhotos([]);
};
const shuffledMemes = shuffle(memes);
const shuffledPhotos = shuffle(photos);
return (
<div className="App">
<h1>HTML LAZY LOADING</h1>
<div className="btn-wrapper">
<button onClick={getMemes()}>Get Memes</button>
<button onClick={getPhotos()}>Get Photos</button>
<button onClick={clearPhotos}>Clear Photos</button>
</div>
{memes.length > 0 &&
shuffledMemes.map((meme) => (
<div key={meme.id}>
<p>{meme.name}</p>
<img src={meme.url} alt={meme.name} loading="lazy" />
</div>
))}
{shuffledPhotos.length > 0 &&
photos.map((photo) => (
<div key={photo.id}>
<img
className="unsplash-img"
src={photo.urls.regular}
alt={photo.alt_description}
loading="lazy"
/>
</div>
))}
</div>
);
}
Conclusion
I hope you learned something new today! I certainly did. Also, I hope this inspires you to dig more into HTML, a seriously underrated programming language (yes I went there 😈😈😈), that provides us developers with a TON of awesome functionality.
As always, let me know in the comments if you have any questions, concerns, corrections, compliments... you get it.
Thanks for reading and I look forward to sharing my next #TIL post with you!
Top comments (0)