useBlurhash hook
import React, { useState, useCallback } from "react"; | |
import { useBlurhash } from "./use-blurhash"; | |
import { useInView } from "react-intersection-observer"; | |
type Props = React.DetailedHTMLProps< | |
React.ImgHTMLAttributes<HTMLImageElement>, | |
HTMLImageElement | |
> & { blurhash?: string | null }; | |
// Uses browser-native `loading="lazy"` to lazy load images | |
// Renders a blurhash value to a blob when it about to appear on screen. | |
// Only renders the blurhash when the image hasn't loaded yet. | |
// Removes the blob once the image has finished loading. | |
export function BlurImg(allProps: Props) { | |
const { loading = "lazy", blurhash, style, ...props } = allProps; | |
const [imgLoaded, setImgLoaded] = useState(false); | |
const [ref, inView] = useInView({ rootMargin: "110%" }); | |
const blurUrl = useBlurhash(!imgLoaded && inView ? blurhash : null); | |
const handleOnLoad = useCallback(() => { | |
setImgLoaded(true); | |
}, []); | |
const newStyle = blurUrl | |
? { | |
...style, | |
backgroundImage: `url("${blurUrl}")`, | |
backgroundSize: | |
props.width && props.height | |
? `${props.width}px ${props.height}px` | |
: "100% 100%" | |
} | |
: style; | |
return ( | |
<img | |
ref={ref} | |
{...props} | |
loading={loading} | |
onLoad={handleOnLoad} | |
style={newStyle} | |
/> | |
); | |
} |
import { useEffect, useState } from "react"; | |
import { decode } from "blurhash"; | |
// modified from https://gist.github.com/WorldMaker/a3cbe0059acd827edee568198376b95a | |
// https://github.com/woltapp/react-blurhash/issues/3 | |
export function useBlurhash( | |
blurhash: string | undefined | null, | |
width: number = 32, | |
height: number = 32, | |
punch: number = 1 | |
) { | |
punch = punch || 1; | |
const [url, setUrl] = useState(null as string | null); | |
useEffect(() => { | |
let isCancelled = false; | |
if (!blurhash) return; | |
// decode hash | |
const pixels = decode(blurhash, width, height, punch); | |
// temporary canvas to create a blob from decoded ImageData | |
const canvas = document.createElement("canvas"); | |
canvas.width = width; | |
canvas.height = height; | |
const context = canvas.getContext("2d"); | |
const imageData = context!.createImageData(width, height); | |
imageData.data.set(pixels); | |
context!.putImageData(imageData, 0, 0); | |
canvas.toBlob(blob => { | |
if (!isCancelled) { | |
setUrl(oldUrl => { | |
if (oldUrl) { | |
URL.revokeObjectURL(oldUrl); | |
} | |
return URL.createObjectURL(blob); | |
}); | |
} | |
}); | |
return function cleanupBlurhash() { | |
isCancelled = true; | |
setUrl(oldUrl => { | |
if (oldUrl) { | |
URL.revokeObjectURL(oldUrl); | |
} | |
return null; | |
}); | |
}; | |
}, [blurhash, height, width, punch]); | |
return url; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment