diff --git a/src/plugins/files/public/components/image/image.tsx b/src/plugins/files/public/components/image/image.tsx index dc0ffe6cf0e51..d538ad2308040 100644 --- a/src/plugins/files/public/components/image/image.tsx +++ b/src/plugins/files/public/components/image/image.tsx @@ -9,6 +9,8 @@ import React from 'react'; import { useState } from 'react'; import { EuiImage, EuiImageProps } from '@elastic/eui'; +import classNames from 'classnames'; +import { css } from '@emotion/react'; import type { FileImageMetadata } from '../../../common'; import { getBlurhashSrc } from '../util'; @@ -28,10 +30,19 @@ export const Image = ({ src, url, alt, onLoad, onError, meta, ...rest }: Props) const [isBlurHashLoaded, setIsBlurHashLoaded] = useState(false); const { blurhash, width, height } = meta ?? {}; - const blurhashSrc = React.useMemo( - () => (blurhash && width && height ? getBlurhashSrc({ hash: blurhash, width, height }) : null), - [blurhash, width, height] - ); + const blurhashSrc = React.useMemo(() => { + if (blurhash && width && height) { + try { + return getBlurhashSrc({ hash: blurhash, width, height }); + } catch (e) { + // eslint-disable-next-line no-console + console.warn(`Failed to generate image src from blurhash`, e); + return null; + } + } else { + return null; + } + }, [blurhash, width, height]); // prettier-ignore const currentSrc = (isBlurHashLoaded || !blurhashSrc) ? imageSrc : blurhashSrc @@ -41,6 +52,26 @@ export const Image = ({ src, url, alt, onLoad, onError, meta, ...rest }: Props) alt="" loading={'lazy'} {...rest} + className={classNames(rest.className, { blurhash: currentSrc === blurhashSrc })} + css={css` + &.blurhash { + // Makes blurhash image visually appear after the .9s delay with a .1s transition duration. + // This is needed for a nicer UX when the original image loads fast. + animation-name: imageBlurhashFadeIn; + animation-duration: 1s; + @keyframes imageBlurhashFadeIn { + 0% { + opacity: 0; + } + 90% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + } + `} src={currentSrc} onLoad={(ev) => { // if the `meta.blurhash` is passed, then the component first renders the blurhash and the `onLoad` event fires for the first time, @@ -62,7 +93,14 @@ export const Image = ({ src, url, alt, onLoad, onError, meta, ...rest }: Props) } }} onError={(ev) => { - onError?.(ev); + if (currentSrc === imageSrc) { + onError?.(ev); + } else { + // blurhash failed to load, consider it is loaded to start loading the full image + // eslint-disable-next-line no-console + console.warn(`Failed to load blurhash src`); + setIsBlurHashLoaded(true); + } }} /> );