Skip to content

Commit

Permalink
Hide the layout shift in gallery under loading bar. Plus simplify the…
Browse files Browse the repository at this point in the history
… ImageMasonary component
  • Loading branch information
bhavberi committed Nov 23, 2024
1 parent 748842e commit 4274cf0
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 83 deletions.
170 changes: 88 additions & 82 deletions src/components/ImageMasonry.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,106 +3,112 @@
import { useState } from "react";
import Image from "next/image";

import { Card, CardActionArea, ImageList, ImageListItem } from "@mui/material";
import {
Box,
Fade,
Card,
CardActionArea,
ImageList,
ImageListItem,
CircularProgress,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";

import ImageModal from "components/ImageModal";
import { useMode } from "contexts/ModeContext";

export default function ImageMasonry({ images, limit = undefined, cols = 4 }) {
const theme = useTheme();
const { isDark } = useMode();
const isDesktop = useMediaQuery(theme.breakpoints.up("lg"));

const [openImage, setOpenImage] = useState(null);
const [loadedImages, setLoadedImages] = useState(0);
const totalImages = limit ? Math.min(images.length, limit) : images.length;

const handleImageLoad = () => {
setLoadedImages((prev) => prev + 1);
};

return (
<>
<ImageList variant="masonry" cols={isDesktop ? cols : 2} gap={10}>
{images.slice(0, limit).map((url, id) => (
<ImageListItem key={id}>
<Card
variant="outlined"
component="div"
sx={{
lineHeight: 0,
display: "block",
overflow: "hidden",
boxShadow: `0px 4px 6px ${theme.palette.primary.dark}80`,
"& .wrapper": {
width: 1,
height: 1,
backgroundSize: "cover !important",
},
margin: "1%",
}}
>
<CardActionArea
onClick={() => {
setOpenImage(id);
}}
sx={{ lineHeight: 0 }}
>
{showImage(url, id, isDark)}
</CardActionArea>
</Card>
</ImageListItem>
))}
</ImageList>
{(loadedImages !== totalImages) && (
<Box py={25} width="100%" display="flex" justifyContent="center">
<Fade in>
<CircularProgress color="primary" />
</Fade>
</Box>
)}

{loadedImages === totalImages && (
<ImageList variant="masonry" cols={isDesktop ? cols : 2} gap={10}>
{images.slice(0, limit).map((url, id) => {
return (
<ImageListItem key={id}>
<Card
variant="outlined"
component="div"
sx={{
lineHeight: 0,
display: "block",
overflow: "hidden",
boxShadow: `0px 4px 6px ${theme.palette.primary.dark}80`,
"& .wrapper": {
width: 1,
height: 1,
backgroundSize: "cover !important",
},
margin: "1%",
}}
>
<CardActionArea
onClick={() => {
setOpenImage(id);
}}
sx={{ lineHeight: 0 }}
>
<Image
src={url}
width={0}
height={0}
sizes="100vw"
alt={`Gallery Image ${id}`}
style={{
width: "100%",
height: "auto",
maxHeight: "100vh",
maxWidth: "100vw",
}}
/>
</CardActionArea>
</Card>
</ImageListItem>
);
})}
</ImageList>
)}

<ImageModal
images={images}
id={openImage}
onClose={() => setOpenImage(null)}
/>
</>
);
}

function showImage(url, id, isDark = true) {
const [isLoaded, setIsLoaded] = useState(false);

return (
<Image
src={
isLoaded
? url
: `data:image/svg+xml;base64,${toBase64(shimmer(700, 475, isDark))}`
}
width={0}
height={0}
sizes="100vw"
alt={`Gallery Image ${id}`}
style={{
width: "100%",
height: "auto",
maxHeight: "100vh",
maxWidth: "100vw",
}}
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475, isDark),
)}`}
priority={!isLoaded}
onLoad={() => setIsLoaded(true)}
/>
{/* Hidden Pre-loading for the images */}
{images.slice(0, limit).map((url, i) => (
<Image
key={i}
src={url}
width={0}
height={0}
sizes="100vw"
alt={`hidden-img-${i}`}
onLoad={() => {
handleImageLoad();
}}
priority={true}
style={{ display: "none" }}
/>
))}
</>
);
}

const shimmer = (w, h, isDark) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="${isDark ? "#333" : "#ccc"}" offset="20%" />
<stop stop-color="${isDark ? "#333" : "#bbb"}" offset="50%" />
<stop stop-color="${isDark ? "#333" : "#ccc"}" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="${isDark ? "#333" : "#ccc"}" />
<rect id="r" width="${w}" height="${h}" fill="${isDark ? "black" : "white"}"/>
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;

const toBase64 = (str) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
2 changes: 1 addition & 1 deletion src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function middleware(req) {
style-src 'self' 'nonce-${nonce}';
style-src-attr 'self' 'unsafe-inline';
style-src-elem 'self' 'unsafe-inline';
img-src 'self' blob: data: https://uptime.betterstack.com;;
img-src 'self' blob: data: https://uptime.betterstack.com;
font-src 'self' data:;
object-src 'none';
base-uri 'self';
Expand Down

0 comments on commit 4274cf0

Please sign in to comment.