Skip to content

Instantly share code, notes, and snippets.

@ngbrown
Forked from WorldMaker/use-blurhash.ts
Last active December 11, 2023 08:56
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save ngbrown/d62eb518753378eb0a9bf02bb4723235 to your computer and use it in GitHub Desktop.
Save ngbrown/d62eb518753378eb0a9bf02bb4723235 to your computer and use it in GitHub Desktop.
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