UNPKG

@yandex/ui

Version:

Yandex UI components

88 lines (86 loc) 4.89 kB
import { __read } from "tslib"; import React, { useCallback, useState, useEffect, useMemo, useRef, } from 'react'; import { cn } from '@bem-react/classname'; import { mergeAllRefs } from '../lib/mergeRefs'; import './Image.css'; var cnImage = cn('Image'); var emptyImage = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='; var ANIMATION_DELAY_MS = 50; /** * Компонент для красивой загрузки картинок * - Пока загружается картинка, пользователь будет видеть `<stub/>` * - После загрузки картинки она будет плавно отображена поверх `<stub/>` * - После плавного показана картинки `<stub/>` будет удалён из `DOM` * - Если картинка загружается из кэша, то пользователь увидит сразу картинку без показа `<stub/>` * - Если картинку не удалось загрузить, то покажется стандартная картинка из свойства `fallback` * @param {IImageProps} props */ export var Image = function (props) { var src = props.src, src2x = props.src2x, srcSet = props.srcSet, sizes = props.sizes, fallbackSrc = props.fallbackSrc, className = props.className, imageClassName = props.imageClassName, stub = props.stub, alt = props.alt, ariaLabel = props.ariaLabel, onClick = props.onClick, _a = props.loading, loading = _a === void 0 ? 'lazy' : _a, width = props.width, height = props.height, borderRadius = props.borderRadius, innerRef = props.innerRef; var _b = __read(useState(false), 2), isLoaded = _b[0], setLoaded = _b[1]; var _c = __read(useState(false), 2), isFailed = _c[0], setFailed = _c[1]; var _d = __read(useState(false), 2), needAnimation = _d[0], setNeedAnimation = _d[1]; var _e = __read(useState(false), 2), canRemoveStub = _e[0], setCanRemoveStub = _e[1]; // нужен реф по условию, а значит useRef не годится // важный момент: в innerRef должна быть актуальная ссылка на ref var imageRef = useRef(null); // объединяем рефы var mergedRefs = useMemo(function () { return mergeAllRefs(imageRef, innerRef); }, [imageRef, innerRef]); var imageSrc = src || emptyImage; var legacySrcSet = src2x ? src2x + " 2x" : undefined; var imageSrcSet = srcSet || legacySrcSet; var isRendered = useCallback(function () { if (!imageRef.current) { return false; } var _a = imageRef.current, naturalWidth = _a.naturalWidth, naturalHeight = _a.naturalHeight; return naturalWidth > 0 && naturalHeight > 0; }, [imageRef]); useEffect(function () { var timer = setTimeout(function () { if (!isLoaded && !isRendered()) { setNeedAnimation(true); } }, ANIMATION_DELAY_MS); return function () { return clearTimeout(timer); }; }, [isLoaded, isRendered, setNeedAnimation]); var handleLoad = useCallback(function () { setLoaded(true); }, []); var handleError = useCallback(function () { setFailed(true); }, []); var containerClassNames = cnImage('Container', [className]); var imageClassNames = cnImage({ loaded: isLoaded, loading: needAnimation, animated: needAnimation && isLoaded, }, // Если нет обертки, то добавляем className в изображение [!stub ? className : undefined, imageClassName]); var handleOnAnimationEnd = useCallback(function () { setCanRemoveStub(true); }, []); var sizeAttrs = {}; // безопасно устанавливаем стили, чтобы избежать undefined в html if (width !== undefined) { sizeAttrs.width = width; } if (height !== undefined) { sizeAttrs.height = height; } if (borderRadius !== undefined) { sizeAttrs.borderRadius = borderRadius; } var image = (React.createElement("img", { style: sizeAttrs, alt: alt, "aria-hidden": ariaLabel ? 'false' : 'true', "aria-label": ariaLabel, className: imageClassNames, onAnimationEnd: handleOnAnimationEnd, onClick: onClick, onError: handleError, onLoad: handleLoad, ref: mergedRefs, src: isFailed && fallbackSrc ? fallbackSrc : imageSrc, srcSet: imageSrcSet, sizes: sizes, // TypeScript пока не знает про такой атрибут // @ts-ignore loading: loading })); if (stub) { return (React.createElement("div", { className: containerClassNames, style: sizeAttrs }, needAnimation && !canRemoveStub && stub, image)); } return image; }; Image.displayName = cnImage();