Reputation: 183
I'm using next Js Image tag (next/image). Is it possible to import a svg and use it as a placeholder until the image finish loading?
I tried to create a use State and it to true on the "onLoad" of the tag as I would do in a img tag, but that doesn't work. The fallback image that should show before the image load never shows, it's like the images are always loaded, even thought they didn't appear on html yet
Here the code I tried:
const [hasImageLoaded, setHasImageLoaded] = useState(false);
function handleImageLoaded(): void {
setHasImageLoaded(true);
}
const displayImgStyle = {
display: hasImageLoaded ? undefined : 'none',
};
const loadingImgStyle = {
display: hasImageLoaded ? 'none' : undefined,
};
return (
<>
<div style={displayImgStyle}>
<Image
width={216}
height={216}
className={styles.pokemon}
src={image}
alt={name}
title={name}
onLoad={handleImageLoaded}
/>
</div>
<MySvg style={loadingImgStyle} />
</>
)
Upvotes: 4
Views: 4210
Reputation: 4739
NextJS v11 and higher
Use blurDataURL
property for placeholders, but before convert your SVG to data-uri.
Read more about blurDataURL
🎯 Solution for NextJS below version v11
To avoid the image loading before the placeholder does you need to convert it to data-uri and deliver it with your code:
const placeholder = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-8 -8 40 40' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-image'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'%3E%3C/circle%3E%3Cpolyline points='21 15 16 10 5 21'%3E%3C/polyline%3E%3C/svg%3E`
<img src={placeholder}/>
Now you need to fetch the real image and for this use Image
class:
const [url, setUrl] = useState(placeholder) // use placeholder as default image, which appears instantly
const img = new Image()
img.src = src
img.onload = () => setUrl(img.src) // callback is called when image is loaded, with setUrl we swap images
Full component:
import './styles.css'
import {useEffect, useState} from 'react'
// to create data url use this service: https://heyallan.github.io/svg-to-data-uri/
const placeholder = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-8 -8 40 40' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-image'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'%3E%3C/circle%3E%3Cpolyline points='21 15 16 10 5 21'%3E%3C/polyline%3E%3C/svg%3E`
const Picture = ({alt, src, ...props}) => {
const [url, setUrl] = useState(placeholder)
useEffect(() => {
if (!src) return
const img = new Image()
img.src = src
img.onload = () => setUrl(img.src)
}, [src])
return <img alt={alt} src={url} {...props} />
}
const App = () => (
<div className='App'>
<h1>Hello CodeSandbox</h1>
<Picture
className='img'
alt='Random image'
src='https://picsum.photos/500/500'
/>
</div>
)
☝️ To avoid name collisions import NextJS Image
component with different name:
import NextImage from 'next/image' // instead of import Image from 'next/image'
Here you can find the demo. To see how it works click refresh icon:
Upvotes: 2
Reputation: 323
Maybe it can help you:
const shimmer = (w: number, h: number) => `
<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="#333" offset="20%" />
<stop stop-color="#222" offset="50%" />
<stop stop-color="#333" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#333" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`
const toBase64 = (str: string) =>
typeof window === 'undefined'
? Buffer.from(str).toString('base64')
: window.btoa(str)
<Image
width={32}
height={32}
alt={Img2}
placeholder="blur"
blurDataURL={`data:image/svg+xml;base64,${toBase64(shimmer(32, 32))}`}
src={`IMG_LINK`}
/>
Upvotes: 1