|
|
|
|
@ -2,10 +2,10 @@ import { Skeleton } from '@/components/ui/skeleton'
@@ -2,10 +2,10 @@ import { Skeleton } from '@/components/ui/skeleton'
|
|
|
|
|
import { cn } from '@/lib/utils' |
|
|
|
|
import { TImageInfo } from '@/types' |
|
|
|
|
import { decode } from 'blurhash' |
|
|
|
|
import { HTMLAttributes, useEffect, useMemo, useState } from 'react' |
|
|
|
|
import { HTMLAttributes, useEffect, useState } from 'react' |
|
|
|
|
|
|
|
|
|
export default function Image({ |
|
|
|
|
image: { url, blurHash, dim }, |
|
|
|
|
image: { url, blurHash }, |
|
|
|
|
alt, |
|
|
|
|
className = '', |
|
|
|
|
classNames = {}, |
|
|
|
|
@ -20,26 +20,19 @@ export default function Image({
@@ -20,26 +20,19 @@ export default function Image({
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true) |
|
|
|
|
const [displayBlurHash, setDisplayBlurHash] = useState(true) |
|
|
|
|
const [blurDataUrl, setBlurDataUrl] = useState<string | null>(null) |
|
|
|
|
const { width, height } = useMemo<{ width?: number; height?: number }>(() => { |
|
|
|
|
if (dim) { |
|
|
|
|
return dim |
|
|
|
|
} |
|
|
|
|
if (blurHash) { |
|
|
|
|
const { numX, numY } = decodeBlurHashSize(blurHash) |
|
|
|
|
return { width: numX * 10, height: numY * 10 } |
|
|
|
|
} |
|
|
|
|
return {} |
|
|
|
|
}, [dim]) |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
if (blurHash) { |
|
|
|
|
const pixels = decode(blurHash, 32, 32) |
|
|
|
|
const { numX, numY } = decodeBlurHashSize(blurHash) |
|
|
|
|
const width = numX * 5 |
|
|
|
|
const height = numY * 5 |
|
|
|
|
const pixels = decode(blurHash, width, height) |
|
|
|
|
const canvas = document.createElement('canvas') |
|
|
|
|
canvas.width = 32 |
|
|
|
|
canvas.height = 32 |
|
|
|
|
canvas.width = width |
|
|
|
|
canvas.height = height |
|
|
|
|
const ctx = canvas.getContext('2d') |
|
|
|
|
if (ctx) { |
|
|
|
|
const imageData = ctx.createImageData(32, 32) |
|
|
|
|
const imageData = ctx.createImageData(width, height) |
|
|
|
|
imageData.data.set(pixels) |
|
|
|
|
ctx.putImageData(imageData, 0, 0) |
|
|
|
|
setBlurDataUrl(canvas.toDataURL()) |
|
|
|
|
@ -54,22 +47,20 @@ export default function Image({
@@ -54,22 +47,20 @@ export default function Image({
|
|
|
|
|
src={url} |
|
|
|
|
alt={alt} |
|
|
|
|
className={cn( |
|
|
|
|
'object-cover transition-opacity duration-700', |
|
|
|
|
'object-cover transition-opacity duration-300', |
|
|
|
|
isLoading ? 'opacity-0' : 'opacity-100', |
|
|
|
|
className |
|
|
|
|
)} |
|
|
|
|
onLoad={() => { |
|
|
|
|
setIsLoading(false) |
|
|
|
|
setTimeout(() => setDisplayBlurHash(false), 1000) |
|
|
|
|
setTimeout(() => setDisplayBlurHash(false), 500) |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
{displayBlurHash && blurDataUrl && ( |
|
|
|
|
<img |
|
|
|
|
src={blurDataUrl} |
|
|
|
|
className={cn('absolute inset-0 object-cover -z-10', className)} |
|
|
|
|
className={cn('absolute inset-0 object-cover w-full h-full -z-10', className)} |
|
|
|
|
alt={alt} |
|
|
|
|
width={width} |
|
|
|
|
height={height} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
@ -78,15 +69,8 @@ export default function Image({
@@ -78,15 +69,8 @@ export default function Image({
|
|
|
|
|
|
|
|
|
|
const DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~' |
|
|
|
|
function decodeBlurHashSize(blurHash: string) { |
|
|
|
|
const sizeFlag = blurHash.charAt(0) |
|
|
|
|
|
|
|
|
|
const sizeValue = DIGITS.indexOf(sizeFlag) |
|
|
|
|
|
|
|
|
|
const numY = Math.floor(sizeValue / 9) + 1 |
|
|
|
|
const sizeValue = DIGITS.indexOf(blurHash[0]) |
|
|
|
|
const numY = (sizeValue / 9 + 1) | 0 |
|
|
|
|
const numX = (sizeValue % 9) + 1 |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
numX, |
|
|
|
|
numY |
|
|
|
|
} |
|
|
|
|
return { numX, numY } |
|
|
|
|
} |
|
|
|
|
|