Skip to content

Instantly share code, notes, and snippets.

@iankit3
Created February 27, 2024 06:52
Show Gist options
  • Save iankit3/3063dcce7121b9305ec024d77fa03c74 to your computer and use it in GitHub Desktop.
Save iankit3/3063dcce7121b9305ec024d77fa03c74 to your computer and use it in GitHub Desktop.
Lazy loading images with useInView hook
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="script.js" type="text/babel"></script>
const { useEffect, useRef, useState } = React;
const [WIDTH, HEIGHT] = [250, 300];
const DEFAULT_IMAGE =
"https://images.unsplash.com/photo-1708176469286-366e7affd24a?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=300&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTcwODk3NTM2OA&ixlib=rb-4.0.3&q=80&w=250";
function BasicCard({
title,
width,
urlOrgetImageUrl,
height,
caption,
description,
footerActionBar
}) {
const [url, setUrl] = useState("");
const { ref, inView } = useMyInView({
threshold: 0.4
});
useEffect(() => {
if (typeof urlOrgetImageUrl === "string") {
setUrl(urlOrgetImageUrl);
} else {
(async () => {
inView && !url && setUrl(await urlOrgetImageUrl());
})();
}
}, [urlOrgetImageUrl, inView]);
return (
<div className="card" style={{ width }} ref={ref}>
<div className="card-title">{title}</div>
<div className="card-body">
<div className="card-body-image" style={{ width, height }}>
{url ? (
<img loading="lazy" src={url} width={width} height={height} />
) : (
<>Loading...</>
)}
</div>
<div className="card-body-footer">{caption}</div>
</div>
<div className="card-footer">
<p>{description}</p>
</div>
</div>
);
}
function getImageUrl() {
const url = `https://source.unsplash.com/random/${WIDTH}x${HEIGHT}`;
return new Promise((resolve, reject) => {
fetch(url, {
// manual tells fetch not to automatically follow redirects
redirect: "follow"
})
.then(async (response) => {
if (response.redirected) {
const url = new URL(response.url);
const urlSearchParams = new URLSearchParams(url.search);
// set the quality from 0-100
urlSearchParams.set("q", "2");
url.search = urlSearchParams.toString();
// const res = await fetch(url.toString());
// resolve(res.url)
resolve(url.toString());
} else {
resolve(resolve.url);
}
})
.catch((e) => {
console.error(e);
resolve(url);
});
});
}
function useMyInView({ threshold }) {
const ref = useRef(null);
const [inView, setInView] = useState(false);
function registerObserver() {
const intersectionObserver = new IntersectionObserver(
(entries) => {
// If intersectionRatio is 0, the target is out of view
// and we do not need to do anything.
if (entries[0].intersectionRatio <= 0) return;
setInView(true);
},
{ threshold }
);
// start observing
intersectionObserver.observe(ref.current);
return () => {
intersectionObserver.disconnect();
};
}
useEffect(() => {
const deRegisterObserver = registerObserver();
return () => deRegisterObserver();
}, []);
return {
ref,
inView
};
}
function App() {
const items = [
{ id: 0, title: "", getUrl: async () => await getImageUrl() },
{ id: 1, title: "", getUrl: async () => await getImageUrl() },
{ id: 2, title: "", getUrl: async () => await getImageUrl() },
{ id: 3, title: "", getUrl: async () => await getImageUrl() },
{ id: 4, title: "", getUrl: async () => await getImageUrl() },
{ id: 5, title: "", getUrl: async () => await getImageUrl() },
{ id: 6, title: "", getUrl: async () => await getImageUrl() },
{ id: 7, title: "", getUrl: async () => await getImageUrl() },
{ id: 8, title: "", getUrl: async () => await getImageUrl() },
{ id: 9, title: "", getUrl: async () => await getImageUrl() }
];
return (
<div className="container">
<BasicCard
title="Mountains"
width={WIDTH}
height={HEIGHT - 100}
urlOrgetImageUrl={DEFAULT_IMAGE}
caption="Body with image"
description="Lorem ipsum describing the above above"
footerActionBar={""}
/>
{items.map((item) => {
return (
<BasicCard
title="Mountains"
width={WIDTH}
height={HEIGHT}
urlOrgetImageUrl={item.getUrl}
caption="Body with image"
description="Lorem ipsum describing the above above"
footerActionBar={""}
key={item.id}
/>
);
})}
</div>
);
}
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(<App />);
:root {
--grey: rgba(200, 200, 200);
--lightgrey: rgba(200, 200, 200, 0.4);
}
.App {
font-family: sans-serif;
}
.container {
display: flex;
gap: 2em;
flex-wrap: wrap;
}
.card {
display: flex;
flex-direction: column;
border: 1px solid;
box-shadow: 2px 4px 4px 0px #515151;
align-self: flex-start;
.card-title {
background: var(--grey);
padding: 1em 0.2em;
color: #222;
font-size: 1.2em;
text-align: center;
}
.card-body {
position: relative;
display: flex;
justify-content: center;
.card-body-image {
border-radius: 2px;
img {
border-radius: 2px;
}
}
.card-body-footer {
position: absolute;
bottom: 5px;
z-index: 1;
color: #fefefe;
background: var(--lightgrey);
padding: 2px 10px;
border-radius: 2px;
}
}
.card-footer {
border-top: 2px solid;
padding: 5px 10px;
}
}
@iankit3
Copy link
Author

iankit3 commented Feb 27, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment