[Perf] Another attempt to use canvas for faster blur (#1126)
This commit is contained in:
parent
d40214428d
commit
ab3a00f29a
@ -20,12 +20,16 @@
|
|||||||
grid-column-end: span 2;
|
grid-column-end: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.thumbnail {
|
||||||
background-size: cover !important;
|
background-size: cover !important;
|
||||||
background: transparent no-repeat center;
|
background: transparent no-repeat center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview, video {
|
.thumbnail ~ video {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail, video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
import useTransitionForMedia from '../../hooks/useTransitionForMedia';
|
import useTransitionForMedia from '../../hooks/useTransitionForMedia';
|
||||||
import useBlur from '../../hooks/useBlur';
|
|
||||||
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
||||||
import useBuffering from '../../hooks/useBuffering';
|
import useBuffering from '../../hooks/useBuffering';
|
||||||
|
import useCanvasBlur from '../../hooks/useCanvasBlur';
|
||||||
|
|
||||||
import Spinner from '../ui/Spinner';
|
import Spinner from '../ui/Spinner';
|
||||||
|
|
||||||
@ -31,15 +31,15 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
|
|
||||||
|
const hasThumbnail = gif.thumbnail && !!gif.thumbnail.dataUri;
|
||||||
const localMediaHash = `gif${gif.id}`;
|
const localMediaHash = `gif${gif.id}`;
|
||||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||||
const loadAndPlay = isIntersecting && !isDisabled;
|
const loadAndPlay = isIntersecting && !isDisabled;
|
||||||
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`, !loadAndPlay, ApiMediaFormat.BlobUrl);
|
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`, !loadAndPlay, ApiMediaFormat.BlobUrl);
|
||||||
const thumbDataUri = useBlur(gif.thumbnail && gif.thumbnail.dataUri, Boolean(previewBlobUrl));
|
const thumbRef = useCanvasBlur(gif.thumbnail && gif.thumbnail.dataUri, Boolean(previewBlobUrl));
|
||||||
const previewData = previewBlobUrl || thumbDataUri;
|
|
||||||
const videoData = useMedia(localMediaHash, !loadAndPlay, ApiMediaFormat.BlobUrl);
|
const videoData = useMedia(localMediaHash, !loadAndPlay, ApiMediaFormat.BlobUrl);
|
||||||
const shouldRenderVideo = Boolean(loadAndPlay && videoData);
|
const shouldRenderVideo = Boolean(loadAndPlay && videoData);
|
||||||
const { transitionClassNames } = useTransitionForMedia(previewData || videoData, 'slow');
|
const { transitionClassNames } = useTransitionForMedia(hasThumbnail || previewBlobUrl || videoData, 'slow');
|
||||||
const { isBuffered, bufferingHandlers } = useBuffering(true);
|
const { isBuffered, bufferingHandlers } = useBuffering(true);
|
||||||
const shouldRenderSpinner = loadAndPlay && !isBuffered;
|
const shouldRenderSpinner = loadAndPlay && !isBuffered;
|
||||||
|
|
||||||
@ -66,14 +66,20 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
className={className}
|
className={className}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{previewData && !shouldRenderVideo && (
|
{hasThumbnail && (
|
||||||
<div
|
<canvas
|
||||||
className="preview"
|
ref={thumbRef}
|
||||||
// @ts-ignore
|
className="thumbnail"
|
||||||
style={`background-image: url(${previewData});`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{shouldRenderVideo && (
|
{!hasThumbnail && previewBlobUrl && (
|
||||||
|
<img
|
||||||
|
src={previewBlobUrl}
|
||||||
|
alt=""
|
||||||
|
className="thumbnail"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(shouldRenderVideo || previewBlobUrl) && (
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
autoPlay
|
autoPlay
|
||||||
@ -81,7 +87,6 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
muted
|
muted
|
||||||
playsInline
|
playsInline
|
||||||
preload="none"
|
preload="none"
|
||||||
poster={previewData}
|
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...bufferingHandlers}
|
{...bufferingHandlers}
|
||||||
>
|
>
|
||||||
@ -89,7 +94,7 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
</video>
|
</video>
|
||||||
)}
|
)}
|
||||||
{shouldRenderSpinner && (
|
{shouldRenderSpinner && (
|
||||||
<Spinner color={previewData ? 'white' : 'black'} />
|
<Spinner color={previewBlobUrl || hasThumbnail ? 'white' : 'black'} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
transition: transform .15s ease;
|
transition: transform .15s ease;
|
||||||
|
|
||||||
img {
|
img, canvas {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import useMedia from '../../../hooks/useMedia';
|
|||||||
import useMediaWithDownloadProgress from '../../../hooks/useMediaWithDownloadProgress';
|
import useMediaWithDownloadProgress from '../../../hooks/useMediaWithDownloadProgress';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useBlur from '../../../hooks/useBlur';
|
import useCanvasBlur from '../../../hooks/useCanvasBlur';
|
||||||
|
|
||||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||||
|
|
||||||
@ -26,8 +26,6 @@ type OwnProps = {
|
|||||||
onClick: (slug: string) => void;
|
onClick: (slug: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ANIMATION_DURATION = 300;
|
|
||||||
|
|
||||||
const WallpaperTile: FC<OwnProps> = ({
|
const WallpaperTile: FC<OwnProps> = ({
|
||||||
wallpaper,
|
wallpaper,
|
||||||
theme,
|
theme,
|
||||||
@ -38,10 +36,10 @@ const WallpaperTile: FC<OwnProps> = ({
|
|||||||
const localMediaHash = `wallpaper${document.id!}`;
|
const localMediaHash = `wallpaper${document.id!}`;
|
||||||
const localBlobUrl = document.previewBlobUrl;
|
const localBlobUrl = document.previewBlobUrl;
|
||||||
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`);
|
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`);
|
||||||
const thumbDataUri = useBlur(
|
const thumbRef = useCanvasBlur(
|
||||||
document.thumbnail && document.thumbnail.dataUri,
|
document.thumbnail && document.thumbnail.dataUri,
|
||||||
Boolean(previewBlobUrl),
|
Boolean(previewBlobUrl),
|
||||||
ANIMATION_DURATION,
|
true,
|
||||||
);
|
);
|
||||||
const {
|
const {
|
||||||
shouldRenderThumb, shouldRenderFullMedia, transitionClassNames,
|
shouldRenderThumb, shouldRenderFullMedia, transitionClassNames,
|
||||||
@ -92,10 +90,9 @@ const WallpaperTile: FC<OwnProps> = ({
|
|||||||
<div className={className} onClick={handleClick}>
|
<div className={className} onClick={handleClick}>
|
||||||
<div className="media-inner">
|
<div className="media-inner">
|
||||||
{shouldRenderThumb && (
|
{shouldRenderThumb && (
|
||||||
<img
|
<canvas
|
||||||
src={thumbDataUri}
|
ref={thumbRef}
|
||||||
className="thumbnail"
|
className="thumbnail"
|
||||||
alt=""
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{shouldRenderFullMedia && (
|
{shouldRenderFullMedia && (
|
||||||
|
|||||||
@ -436,6 +436,10 @@
|
|||||||
border-bottom-right-radius: var(--border-bottom-right-radius);
|
border-bottom-right-radius: var(--border-bottom-right-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-inner video.full-media {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
.media-inner {
|
.media-inner {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
|||||||
@ -16,8 +16,8 @@ import { ObserveFn, useIsIntersecting } from '../../../hooks/useIntersectionObse
|
|||||||
import useMediaWithDownloadProgress from '../../../hooks/useMediaWithDownloadProgress';
|
import useMediaWithDownloadProgress from '../../../hooks/useMediaWithDownloadProgress';
|
||||||
import useTransitionForMedia from '../../../hooks/useTransitionForMedia';
|
import useTransitionForMedia from '../../../hooks/useTransitionForMedia';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
|
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useBlurredMediaThumb from './hooks/useBlurredMediaThumb';
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import getCustomAppendixBg from './helpers/getCustomAppendixBg';
|
import getCustomAppendixBg from './helpers/getCustomAppendixBg';
|
||||||
import { calculateMediaDimensions } from './helpers/mediaDimensions';
|
import { calculateMediaDimensions } from './helpers/mediaDimensions';
|
||||||
@ -71,7 +71,7 @@ const Photo: FC<OwnProps> = ({
|
|||||||
mediaData, downloadProgress,
|
mediaData, downloadProgress,
|
||||||
} = useMediaWithDownloadProgress(getMessageMediaHash(message, size), !shouldDownload);
|
} = useMediaWithDownloadProgress(getMessageMediaHash(message, size), !shouldDownload);
|
||||||
const fullMediaData = localBlobUrl || mediaData;
|
const fullMediaData = localBlobUrl || mediaData;
|
||||||
const thumbDataUri = useBlurredMediaThumb(message, fullMediaData);
|
const thumbRef = useBlurredMediaThumbRef(message, fullMediaData);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isUploading, isTransferring, transferProgress,
|
isUploading, isTransferring, transferProgress,
|
||||||
@ -124,11 +124,6 @@ const Photo: FC<OwnProps> = ({
|
|||||||
width === height && 'square-image',
|
width === height && 'square-image',
|
||||||
);
|
);
|
||||||
|
|
||||||
const thumbClassName = buildClassName(
|
|
||||||
'thumbnail',
|
|
||||||
!thumbDataUri && 'empty',
|
|
||||||
);
|
|
||||||
|
|
||||||
const style = dimensions
|
const style = dimensions
|
||||||
? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;`
|
? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;`
|
||||||
: '';
|
: '';
|
||||||
@ -143,12 +138,11 @@ const Photo: FC<OwnProps> = ({
|
|||||||
onClick={isUploading ? undefined : handleClick}
|
onClick={isUploading ? undefined : handleClick}
|
||||||
>
|
>
|
||||||
{shouldRenderThumb && (
|
{shouldRenderThumb && (
|
||||||
<img
|
<canvas
|
||||||
src={thumbDataUri}
|
ref={thumbRef}
|
||||||
className={thumbClassName}
|
className="thumbnail"
|
||||||
width={width}
|
// @ts-ignore teact feature
|
||||||
height={height}
|
style={`width: ${width}px; height: ${height}px`}
|
||||||
alt=""
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{shouldRenderFullMedia && (
|
{shouldRenderFullMedia && (
|
||||||
|
|||||||
@ -20,8 +20,8 @@ import useBuffering from '../../../hooks/useBuffering';
|
|||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import useHeavyAnimationCheckForVideo from '../../../hooks/useHeavyAnimationCheckForVideo';
|
import useHeavyAnimationCheckForVideo from '../../../hooks/useHeavyAnimationCheckForVideo';
|
||||||
import useVideoCleanup from '../../../hooks/useVideoCleanup';
|
import useVideoCleanup from '../../../hooks/useVideoCleanup';
|
||||||
import useBlurredMediaThumb from './hooks/useBlurredMediaThumb';
|
|
||||||
import usePauseOnInactive from './hooks/usePauseOnInactive';
|
import usePauseOnInactive from './hooks/usePauseOnInactive';
|
||||||
|
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||||
import safePlay from '../../../util/safePlay';
|
import safePlay from '../../../util/safePlay';
|
||||||
|
|
||||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||||
@ -74,7 +74,7 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
getMessageMediaFormat(message, 'inline'),
|
getMessageMediaFormat(message, 'inline'),
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
);
|
);
|
||||||
const thumbDataUri = useBlurredMediaThumb(message, mediaData);
|
const thumbRef = useBlurredMediaThumbRef(message, mediaData);
|
||||||
|
|
||||||
const { isBuffered, bufferingHandlers } = useBuffering();
|
const { isBuffered, bufferingHandlers } = useBuffering();
|
||||||
const isTransferring = isDownloadAllowed && !isBuffered;
|
const isTransferring = isDownloadAllowed && !isBuffered;
|
||||||
@ -183,12 +183,11 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
>
|
>
|
||||||
{shouldRenderThumb && (
|
{shouldRenderThumb && (
|
||||||
<div className="thumbnail-wrapper">
|
<div className="thumbnail-wrapper">
|
||||||
<img
|
<canvas
|
||||||
src={thumbDataUri}
|
ref={thumbRef}
|
||||||
className="thumbnail"
|
className="thumbnail"
|
||||||
width={ROUND_VIDEO_DIMENSIONS}
|
// @ts-ignore teact feature
|
||||||
height={ROUND_VIDEO_DIMENSIONS}
|
style={`width: ${ROUND_VIDEO_DIMENSIONS}px; height: ${ROUND_VIDEO_DIMENSIONS}px`}
|
||||||
alt=""
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -204,7 +203,6 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
muted={!isActivated}
|
muted={!isActivated}
|
||||||
loop={!isActivated}
|
loop={!isActivated}
|
||||||
playsInline
|
playsInline
|
||||||
poster={thumbDataUri}
|
|
||||||
onEnded={isActivated ? stopPlaying : undefined}
|
onEnded={isActivated ? stopPlaying : undefined}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...bufferingHandlers}
|
{...bufferingHandlers}
|
||||||
|
|||||||
@ -24,9 +24,9 @@ import useTransitionForMedia from '../../../hooks/useTransitionForMedia';
|
|||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useBuffering from '../../../hooks/useBuffering';
|
import useBuffering from '../../../hooks/useBuffering';
|
||||||
import useHeavyAnimationCheckForVideo from '../../../hooks/useHeavyAnimationCheckForVideo';
|
import useHeavyAnimationCheckForVideo from '../../../hooks/useHeavyAnimationCheckForVideo';
|
||||||
import useBlurredMediaThumb from './hooks/useBlurredMediaThumb';
|
|
||||||
import useVideoCleanup from '../../../hooks/useVideoCleanup';
|
import useVideoCleanup from '../../../hooks/useVideoCleanup';
|
||||||
import usePauseOnInactive from './hooks/usePauseOnInactive';
|
import usePauseOnInactive from './hooks/usePauseOnInactive';
|
||||||
|
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||||
|
|
||||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ const Video: FC<OwnProps> = ({
|
|||||||
getMessageMediaFormat(message, 'pictogram'),
|
getMessageMediaFormat(message, 'pictogram'),
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
);
|
);
|
||||||
const thumbDataUri = useBlurredMediaThumb(message, previewBlobUrl);
|
const thumbRef = useBlurredMediaThumbRef(message);
|
||||||
const { mediaData, downloadProgress } = useMediaWithDownloadProgress(
|
const { mediaData, downloadProgress } = useMediaWithDownloadProgress(
|
||||||
getMessageMediaHash(message, 'inline'),
|
getMessageMediaHash(message, 'inline'),
|
||||||
!shouldDownload,
|
!shouldDownload,
|
||||||
@ -84,7 +84,6 @@ const Video: FC<OwnProps> = ({
|
|||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const previewMediaData = previewBlobUrl || thumbDataUri;
|
|
||||||
const fullMediaData = localBlobUrl || mediaData;
|
const fullMediaData = localBlobUrl || mediaData;
|
||||||
const isInline = Boolean(canPlayInline && isIntersecting && fullMediaData);
|
const isInline = Boolean(canPlayInline && isIntersecting && fullMediaData);
|
||||||
|
|
||||||
@ -132,9 +131,7 @@ const Video: FC<OwnProps> = ({
|
|||||||
}, [isUploading, canPlayInline, fullMediaData, isPlayAllowed, onClick, onCancelUpload, message]);
|
}, [isUploading, canPlayInline, fullMediaData, isPlayAllowed, onClick, onCancelUpload, message]);
|
||||||
|
|
||||||
const className = buildClassName('media-inner dark', !isUploading && 'interactive');
|
const className = buildClassName('media-inner dark', !isUploading && 'interactive');
|
||||||
const thumbClassName = buildClassName('thumbnail', !previewMediaData && 'empty');
|
|
||||||
const videoClassName = buildClassName('full-media', transitionClassNames);
|
const videoClassName = buildClassName('full-media', transitionClassNames);
|
||||||
const videoStyle = previewMediaData ? `background-image: url(${previewMediaData}); background-size: cover` : '';
|
|
||||||
|
|
||||||
const style = dimensions
|
const style = dimensions
|
||||||
? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;`
|
? `width: ${width}px; height: ${height}px; left: ${dimensions.x}px; top: ${dimensions.y}px;`
|
||||||
@ -154,15 +151,25 @@ const Video: FC<OwnProps> = ({
|
|||||||
style={style}
|
style={style}
|
||||||
onClick={isUploading ? undefined : handleClick}
|
onClick={isUploading ? undefined : handleClick}
|
||||||
>
|
>
|
||||||
{(shouldRenderThumb || !isInline) && (
|
{(!isInline || shouldRenderThumb)
|
||||||
|
&& (
|
||||||
|
<canvas
|
||||||
|
ref={thumbRef}
|
||||||
|
className="thumbnail"
|
||||||
|
// @ts-ignore teact feature
|
||||||
|
style={`width: ${width}px; height: ${height}px;`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{previewBlobUrl && (
|
||||||
<img
|
<img
|
||||||
src={previewMediaData}
|
src={previewBlobUrl}
|
||||||
className={thumbClassName}
|
className="thumbnail"
|
||||||
width={width}
|
// @ts-ignore teact feature
|
||||||
height={height}
|
style={`width: ${width}px; height: ${height}px;`}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{shouldRenderInlineVideo && (
|
{shouldRenderInlineVideo && (
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
@ -173,8 +180,6 @@ const Video: FC<OwnProps> = ({
|
|||||||
muted
|
muted
|
||||||
loop
|
loop
|
||||||
playsInline
|
playsInline
|
||||||
// @ts-ignore teact feature
|
|
||||||
style={videoStyle}
|
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...bufferingHandlers}
|
{...bufferingHandlers}
|
||||||
onTimeUpdate={handleTimeUpdate}
|
onTimeUpdate={handleTimeUpdate}
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
import { ApiMessage } from '../../../../api/types';
|
|
||||||
|
|
||||||
import { LAYERS_TRANSITION_DURATION } from '../../../../config';
|
|
||||||
import { IS_MOBILE_SCREEN } from '../../../../util/environment';
|
|
||||||
import { getMessageMediaThumbDataUri } from '../../../../modules/helpers';
|
|
||||||
import useBlur from '../../../../hooks/useBlur';
|
|
||||||
|
|
||||||
export default function useBlurredMediaThumb(message: ApiMessage, fullMediaData?: string) {
|
|
||||||
return useBlur(
|
|
||||||
getMessageMediaThumbDataUri(message),
|
|
||||||
Boolean(fullMediaData),
|
|
||||||
IS_MOBILE_SCREEN ? LAYERS_TRANSITION_DURATION : undefined,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { ApiMessage } from '../../../../api/types';
|
||||||
|
|
||||||
|
import { IS_CANVAS_FILTER_SUPPORTED, IS_MOBILE_SCREEN } from '../../../../util/environment';
|
||||||
|
import { getMessageMediaThumbDataUri } from '../../../../modules/helpers';
|
||||||
|
import useCanvasBlur from '../../../../hooks/useCanvasBlur';
|
||||||
|
|
||||||
|
export default function useBlurredMediaThumbRef(message: ApiMessage, fullMediaData?: string) {
|
||||||
|
return useCanvasBlur(
|
||||||
|
getMessageMediaThumbDataUri(message),
|
||||||
|
Boolean(fullMediaData),
|
||||||
|
IS_MOBILE_SCREEN && !IS_CANVAS_FILTER_SUPPORTED,
|
||||||
|
);
|
||||||
|
}
|
||||||
53
src/hooks/useCanvasBlur.ts
Normal file
53
src/hooks/useCanvasBlur.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { useEffect, useRef } from '../lib/teact/teact';
|
||||||
|
|
||||||
|
import fastBlur from '../lib/fastBlur';
|
||||||
|
import useForceUpdate from './useForceUpdate';
|
||||||
|
import { IS_CANVAS_FILTER_SUPPORTED } from '../util/environment';
|
||||||
|
|
||||||
|
const RADIUS = 2;
|
||||||
|
const ITERATIONS = 2;
|
||||||
|
|
||||||
|
export default function useCanvasBlur(dataUri?: string, isDisabled = false, withRaf?: boolean) {
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const forceUpdate = useForceUpdate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
|
||||||
|
if (!dataUri || !canvas || isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
|
||||||
|
const processBlur = () => {
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d', { alpha: false })!;
|
||||||
|
|
||||||
|
if (IS_CANVAS_FILTER_SUPPORTED) {
|
||||||
|
ctx.filter = `blur(${RADIUS}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.drawImage(img, -RADIUS * 2, -RADIUS * 2, canvas.width + RADIUS * 4, canvas.height + RADIUS * 4);
|
||||||
|
|
||||||
|
if (!IS_CANVAS_FILTER_SUPPORTED) {
|
||||||
|
fastBlur(ctx, 0, 0, canvas.width, canvas.height, RADIUS, ITERATIONS);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
if (withRaf) {
|
||||||
|
requestAnimationFrame(processBlur);
|
||||||
|
} else {
|
||||||
|
processBlur();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = dataUri;
|
||||||
|
}, [canvasRef, dataUri, forceUpdate, isDisabled, withRaf]);
|
||||||
|
|
||||||
|
return canvasRef;
|
||||||
|
}
|
||||||
@ -16,15 +16,9 @@
|
|||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail ~ .full-media, .media-loading {
|
.thumbnail ~ .thumbnail, .thumbnail ~ .full-media, .media-loading {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail {
|
|
||||||
&.empty {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.animated-close-icon {
|
.animated-close-icon {
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export const IS_SERVICE_WORKER_SUPPORTED = 'serviceWorker' in navigator;
|
|||||||
export const IS_PROGRESSIVE_SUPPORTED = IS_SERVICE_WORKER_SUPPORTED;
|
export const IS_PROGRESSIVE_SUPPORTED = IS_SERVICE_WORKER_SUPPORTED;
|
||||||
export const IS_STREAMING_SUPPORTED = 'MediaSource' in window;
|
export const IS_STREAMING_SUPPORTED = 'MediaSource' in window;
|
||||||
export const IS_OPUS_SUPPORTED = Boolean((new Audio()).canPlayType('audio/ogg; codecs=opus'));
|
export const IS_OPUS_SUPPORTED = Boolean((new Audio()).canPlayType('audio/ogg; codecs=opus'));
|
||||||
|
export const IS_CANVAS_FILTER_SUPPORTED = 'filter' in (document.createElement('canvas').getContext('2d') || {});
|
||||||
|
|
||||||
export const DPR = window.devicePixelRatio || 1;
|
export const DPR = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user