From 65d36c772325a66af6535bf50290c64a7c6cd0b1 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 24 May 2021 00:28:09 +0300 Subject: [PATCH] [Perf] Use canvas for faster blur (#1100) --- src/components/common/GifButton.scss | 8 ++- src/components/common/GifButton.tsx | 29 +++++----- .../left/settings/WallpaperTile.scss | 2 +- .../left/settings/WallpaperTile.tsx | 13 ++--- src/components/middle/message/Photo.tsx | 20 +++---- src/components/middle/message/RoundVideo.tsx | 14 +++-- src/components/middle/message/Video.tsx | 30 +++++++---- .../message/hooks/useBlurredMediaThumb.ts | 14 ----- .../message/hooks/useBlurredMediaThumbRef.ts | 13 +++++ src/hooks/useCanvasBlur.ts | 53 +++++++++++++++++++ src/styles/_common.scss | 6 --- src/util/environment.ts | 1 + 12 files changed, 129 insertions(+), 74 deletions(-) delete mode 100644 src/components/middle/message/hooks/useBlurredMediaThumb.ts create mode 100644 src/components/middle/message/hooks/useBlurredMediaThumbRef.ts create mode 100644 src/hooks/useCanvasBlur.ts diff --git a/src/components/common/GifButton.scss b/src/components/common/GifButton.scss index d0575d49b..aefe8875b 100644 --- a/src/components/common/GifButton.scss +++ b/src/components/common/GifButton.scss @@ -20,12 +20,16 @@ grid-column-end: span 2; } - .preview { + .thumbnail { background-size: cover !important; background: transparent no-repeat center; } - .preview, video { + .thumbnail ~ video { + position: absolute; + } + + .thumbnail, video { width: 100%; height: 100%; object-fit: cover; diff --git a/src/components/common/GifButton.tsx b/src/components/common/GifButton.tsx index a86c58b93..293600531 100644 --- a/src/components/common/GifButton.tsx +++ b/src/components/common/GifButton.tsx @@ -8,9 +8,9 @@ import buildClassName from '../../util/buildClassName'; import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver'; import useMedia from '../../hooks/useMedia'; import useTransitionForMedia from '../../hooks/useTransitionForMedia'; -import useBlur from '../../hooks/useBlur'; import useVideoCleanup from '../../hooks/useVideoCleanup'; import useBuffering from '../../hooks/useBuffering'; +import useCanvasBlur from '../../hooks/useCanvasBlur'; import Spinner from '../ui/Spinner'; @@ -31,15 +31,15 @@ const GifButton: FC = ({ // eslint-disable-next-line no-null/no-null const videoRef = useRef(null); + const hasThumbnail = gif.thumbnail && !!gif.thumbnail.dataUri; const localMediaHash = `gif${gif.id}`; const isIntersecting = useIsIntersecting(ref, observeIntersection); const loadAndPlay = isIntersecting && !isDisabled; const previewBlobUrl = useMedia(`${localMediaHash}?size=m`, !loadAndPlay, ApiMediaFormat.BlobUrl); - const thumbDataUri = useBlur(gif.thumbnail && gif.thumbnail.dataUri, Boolean(previewBlobUrl)); - const previewData = previewBlobUrl || thumbDataUri; + const thumbRef = useCanvasBlur(gif.thumbnail && gif.thumbnail.dataUri, Boolean(previewBlobUrl)); const videoData = useMedia(localMediaHash, !loadAndPlay, ApiMediaFormat.BlobUrl); const shouldRenderVideo = Boolean(loadAndPlay && videoData); - const { transitionClassNames } = useTransitionForMedia(previewData || videoData, 'slow'); + const { transitionClassNames } = useTransitionForMedia(hasThumbnail || previewBlobUrl || videoData, 'slow'); const { isBuffered, bufferingHandlers } = useBuffering(true); const shouldRenderSpinner = loadAndPlay && !isBuffered; @@ -66,14 +66,20 @@ const GifButton: FC = ({ className={className} onClick={handleClick} > - {previewData && !shouldRenderVideo && ( -
)} - {shouldRenderVideo && ( + {!hasThumbnail && previewBlobUrl && ( + + )} + {(shouldRenderVideo || previewBlobUrl) && ( )} {shouldRenderSpinner && ( - + )}
); diff --git a/src/components/left/settings/WallpaperTile.scss b/src/components/left/settings/WallpaperTile.scss index 1115e407c..0cd8314ce 100644 --- a/src/components/left/settings/WallpaperTile.scss +++ b/src/components/left/settings/WallpaperTile.scss @@ -18,7 +18,7 @@ transform: scale(1); transition: transform .15s ease; - img { + img, canvas { position: absolute; left: 0; top: 0; diff --git a/src/components/left/settings/WallpaperTile.tsx b/src/components/left/settings/WallpaperTile.tsx index d517a98d1..d595cbf91 100644 --- a/src/components/left/settings/WallpaperTile.tsx +++ b/src/components/left/settings/WallpaperTile.tsx @@ -13,7 +13,7 @@ import useMedia from '../../../hooks/useMedia'; import useMediaWithDownloadProgress from '../../../hooks/useMediaWithDownloadProgress'; import useShowTransition from '../../../hooks/useShowTransition'; import usePrevious from '../../../hooks/usePrevious'; -import useBlur from '../../../hooks/useBlur'; +import useCanvasBlur from '../../../hooks/useCanvasBlur'; import ProgressSpinner from '../../ui/ProgressSpinner'; @@ -25,8 +25,6 @@ type OwnProps = { onClick: (slug: string) => void; }; -const ANIMATION_DURATION = 300; - const WallpaperTile: FC = ({ wallpaper, isSelected, @@ -37,10 +35,10 @@ const WallpaperTile: FC = ({ const localMediaHash = `wallpaper${document.id!}`; const localBlobUrl = document.previewBlobUrl; const previewBlobUrl = useMedia(`${localMediaHash}?size=m`); - const thumbDataUri = useBlur( + const thumbRef = useCanvasBlur( document.thumbnail && document.thumbnail.dataUri, Boolean(previewBlobUrl), - ANIMATION_DURATION, + true, ); const { shouldRenderThumb, shouldRenderFullMedia, transitionClassNames, @@ -88,10 +86,9 @@ const WallpaperTile: FC = ({
{shouldRenderThumb && ( - )} {shouldRenderFullMedia && ( diff --git a/src/components/middle/message/Photo.tsx b/src/components/middle/message/Photo.tsx index e3fcfc627..872b5fef1 100644 --- a/src/components/middle/message/Photo.tsx +++ b/src/components/middle/message/Photo.tsx @@ -16,8 +16,8 @@ import { ObserveFn, useIsIntersecting } from '../../../hooks/useIntersectionObse import useMediaWithDownloadProgress from '../../../hooks/useMediaWithDownloadProgress'; import useTransitionForMedia from '../../../hooks/useTransitionForMedia'; import useShowTransition from '../../../hooks/useShowTransition'; +import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef'; import usePrevious from '../../../hooks/usePrevious'; -import useBlurredMediaThumb from './hooks/useBlurredMediaThumb'; import buildClassName from '../../../util/buildClassName'; import getCustomAppendixBg from './helpers/getCustomAppendixBg'; import { calculateMediaDimensions } from './helpers/mediaDimensions'; @@ -69,7 +69,7 @@ const Photo: FC = ({ mediaData, downloadProgress, } = useMediaWithDownloadProgress(getMessageMediaHash(message, size), !shouldDownload); const fullMediaData = localBlobUrl || mediaData; - const thumbDataUri = useBlurredMediaThumb(message, fullMediaData); + const thumbRef = useBlurredMediaThumbRef(message, fullMediaData); const { isUploading, isTransferring, transferProgress, @@ -122,11 +122,6 @@ const Photo: FC = ({ width === height && 'square-image', ); - const thumbClassName = buildClassName( - 'thumbnail', - !thumbDataUri && 'empty', - ); - const style = dimensions ? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;` : ''; @@ -141,12 +136,11 @@ const Photo: FC = ({ onClick={isUploading ? undefined : handleClick} > {shouldRenderThumb && ( - )} {shouldRenderFullMedia && ( diff --git a/src/components/middle/message/RoundVideo.tsx b/src/components/middle/message/RoundVideo.tsx index aa1b5fa71..57303a1c3 100644 --- a/src/components/middle/message/RoundVideo.tsx +++ b/src/components/middle/message/RoundVideo.tsx @@ -20,8 +20,8 @@ import useBuffering from '../../../hooks/useBuffering'; import buildClassName from '../../../util/buildClassName'; import useHeavyAnimationCheckForVideo from '../../../hooks/useHeavyAnimationCheckForVideo'; import useVideoCleanup from '../../../hooks/useVideoCleanup'; -import useBlurredMediaThumb from './hooks/useBlurredMediaThumb'; import usePauseOnInactive from './hooks/usePauseOnInactive'; +import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef'; import safePlay from '../../../util/safePlay'; import ProgressSpinner from '../../ui/ProgressSpinner'; @@ -74,7 +74,7 @@ const RoundVideo: FC = ({ getMessageMediaFormat(message, 'inline'), lastSyncTime, ); - const thumbDataUri = useBlurredMediaThumb(message, mediaData); + const thumbRef = useBlurredMediaThumbRef(message, mediaData); const { isBuffered, bufferingHandlers } = useBuffering(); const isTransferring = isDownloadAllowed && !isBuffered; @@ -183,12 +183,11 @@ const RoundVideo: FC = ({ > {shouldRenderThumb && (
-
)} @@ -204,7 +203,6 @@ const RoundVideo: FC = ({ muted={!isActivated} loop={!isActivated} playsInline - poster={thumbDataUri} onEnded={isActivated ? stopPlaying : undefined} // eslint-disable-next-line react/jsx-props-no-spreading {...bufferingHandlers} diff --git a/src/components/middle/message/Video.tsx b/src/components/middle/message/Video.tsx index e57d37012..3237b3abc 100644 --- a/src/components/middle/message/Video.tsx +++ b/src/components/middle/message/Video.tsx @@ -24,9 +24,9 @@ import useTransitionForMedia from '../../../hooks/useTransitionForMedia'; import usePrevious from '../../../hooks/usePrevious'; import useBuffering from '../../../hooks/useBuffering'; import useHeavyAnimationCheckForVideo from '../../../hooks/useHeavyAnimationCheckForVideo'; -import useBlurredMediaThumb from './hooks/useBlurredMediaThumb'; import useVideoCleanup from '../../../hooks/useVideoCleanup'; import usePauseOnInactive from './hooks/usePauseOnInactive'; +import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef'; import ProgressSpinner from '../../ui/ProgressSpinner'; @@ -76,7 +76,7 @@ const Video: FC = ({ getMessageMediaFormat(message, 'pictogram'), lastSyncTime, ); - const thumbDataUri = useBlurredMediaThumb(message, previewBlobUrl); + const thumbRef = useBlurredMediaThumbRef(message); const { mediaData, downloadProgress } = useMediaWithDownloadProgress( getMessageMediaHash(message, 'inline'), !shouldDownload, @@ -84,7 +84,6 @@ const Video: FC = ({ lastSyncTime, ); - const previewMediaData = previewBlobUrl || thumbDataUri; const fullMediaData = localBlobUrl || mediaData; const isInline = Boolean(canPlayInline && isIntersecting && fullMediaData); @@ -132,9 +131,10 @@ const Video: FC = ({ }, [isUploading, canPlayInline, fullMediaData, isPlayAllowed, onClick, onCancelUpload, message]); const className = buildClassName('media-inner dark', !isUploading && 'interactive'); - const thumbClassName = buildClassName('thumbnail', !previewMediaData && 'empty'); const videoClassName = buildClassName('full-media', transitionClassNames); - const videoStyle = previewMediaData ? `background-image: url(${previewMediaData}); background-size: cover` : ''; + const videoStyle = previewBlobUrl + ? `background-image: url(${previewBlobUrl}); background-size: cover` + : 'background: transparent'; const style = dimensions ? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;` @@ -154,15 +154,25 @@ const Video: FC = ({ style={style} onClick={isUploading ? undefined : handleClick} > - {(shouldRenderThumb || !isInline) && ( + {((!isInline || shouldRenderThumb) && !previewBlobUrl) + && ( + + )} + {previewBlobUrl && fullMediaData && ( )} + {shouldRenderInlineVideo && (