Sku
Sku

Reputation: 183

is it possible to use a svg as a placeholder for image in next js?

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

Answers (2)

sultan
sultan

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: enter image description here

Upvotes: 2

Bruno Henrique
Bruno Henrique

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

Related Questions