signal-desktop/ts/components/ImageOrBlurhash.tsx

73 lines
1.9 KiB
TypeScript
Raw Normal View History

2025-08-04 09:16:54 -07:00
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
2025-08-05 11:13:10 -07:00
import React, { useMemo, useCallback, useState, useRef } from 'react';
2025-08-04 09:16:54 -07:00
import { computeBlurHashUrl } from '../util/computeBlurHashUrl.js';
2025-08-04 09:16:54 -07:00
export type Props = React.ImgHTMLAttributes<HTMLImageElement> &
Readonly<{
blurHash?: string;
alt: string;
intrinsicWidth?: number;
intrinsicHeight?: number;
}>;
export function ImageOrBlurhash({
src: imageSrc,
blurHash,
alt,
intrinsicWidth,
intrinsicHeight,
...rest
}: Props): JSX.Element {
2025-08-05 11:13:10 -07:00
const ref = useRef<HTMLImageElement | null>(null);
const [isLoaded, setIsLoaded] = useState(false);
2025-08-04 09:16:54 -07:00
const blurHashUrl = useMemo(() => {
return blurHash
? computeBlurHashUrl(blurHash, intrinsicWidth, intrinsicHeight)
: undefined;
}, [blurHash, intrinsicWidth, intrinsicHeight]);
2025-08-05 11:13:10 -07:00
const onLoad = useCallback(() => {
// Don't let background blurhash be visible at the same time as the image
// while React propagates the `isLoaded` change.
if (ref.current) {
ref.current.style.backgroundImage = 'none';
}
setIsLoaded(true);
}, [ref]);
2025-08-04 09:16:54 -07:00
const src = imageSrc ?? blurHashUrl;
return (
<img
{...rest}
2025-08-05 11:13:10 -07:00
ref={ref}
2025-08-04 09:16:54 -07:00
src={src}
alt={alt}
2025-08-05 11:13:10 -07:00
onLoad={onLoad}
2025-08-04 09:16:54 -07:00
style={{
// Use a background image with an data url of the blurhash which should
// show quickly and stay visible until the img src is loaded/decoded.
backgroundImage:
2025-08-05 11:13:10 -07:00
blurHashUrl != null && blurHashUrl !== src && !isLoaded
2025-08-04 09:16:54 -07:00
? `url(${blurHashUrl})`
: 'none',
2025-08-05 11:13:10 -07:00
aspectRatio:
intrinsicWidth && intrinsicHeight
? `${intrinsicWidth} / ${intrinsicHeight}`
: undefined,
2025-08-04 09:16:54 -07:00
2025-08-11 10:22:54 -07:00
width: '100%',
height: '100%',
2025-08-04 09:16:54 -07:00
// Preserve aspect ratio
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
loading={blurHashUrl != null ? 'lazy' : 'eager'}
/>
);
}