From 1e0da66110f65caa6c8bb7833c35f57582938bd2 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Mon, 21 Mar 2022 11:40:32 +0800 Subject: [PATCH] OptimizedImage: simplify implementation It has been broken for a while, probably since the theme changes? - Anyway, removed the effort to get the exact mounted size before requesting the image from CDN. - When it works, it does prevent blurry image in different screen resolutions, but it's hard to maintain and too expensive. ChannelThumbnail: use 64 to cover both the 64 and 40 case (the cached image will work on both, potentially reducing a call). I think the size is close enough to not trigger a Core Vital size warning. --- ui/component/channelThumbnail/view.jsx | 13 +-- ui/component/optimizedImage/view.jsx | 113 +++++++------------------ ui/page/channel/view.jsx | 10 +-- 3 files changed, 34 insertions(+), 102 deletions(-) diff --git a/ui/component/channelThumbnail/view.jsx b/ui/component/channelThumbnail/view.jsx index d2b6a73b9..8d7675585 100644 --- a/ui/component/channelThumbnail/view.jsx +++ b/ui/component/channelThumbnail/view.jsx @@ -8,7 +8,6 @@ import OptimizedImage from 'component/optimizedImage'; import { AVATAR_DEFAULT } from 'config'; import useGetUserMemberships from 'effects/use-get-user-memberships'; import PremiumBadge from 'component/common/premium-badge'; -import { getThumbnailCdnUrl } from 'util/thumbnail'; type Props = { thumbnail: ?string, @@ -25,7 +24,6 @@ type Props = { noLazyLoad?: boolean, hideStakedIndicator?: boolean, hideTooltip?: boolean, - minOptimization?: boolean, setThumbUploadError: (boolean) => void, ThumbUploadError: boolean, claimsByUri: { [string]: any }, @@ -59,21 +57,13 @@ function ChannelThumbnail(props: Props) { showMemberBadge, isChannel, checkMembership = true, - minOptimization, } = props; const [thumbLoadError, setThumbLoadError] = React.useState(ThumbUploadError); const shouldResolve = !isResolving && claim === undefined; const thumbnail = rawThumbnail && rawThumbnail.trim().replace(/^http:\/\//i, 'https://'); const thumbnailPreview = rawThumbnailPreview && rawThumbnailPreview.trim().replace(/^http:\/\//i, 'https://'); const defaultAvatar = AVATAR_DEFAULT || Gerbil; - const thumb = thumbnailPreview || thumbnail || defaultAvatar; - // checking with CDN to see if a max size can be passed, but for now, this is an improvement vs not using the optimizer - const channelThumbnail = getThumbnailCdnUrl({ - thumbnail: thumb, - width: minOptimization ? 500 : 50, - height: minOptimization ? 500 : 50, - quality: minOptimization ? 95 : 85, - }); + const channelThumbnail = thumbnailPreview || thumbnail || defaultAvatar; const isGif = channelThumbnail && channelThumbnail.endsWith('gif'); const showThumb = (!obscure && !!thumbnail) || thumbnailPreview; @@ -125,6 +115,7 @@ function ChannelThumbnail(props: Props) { alt={__('Channel profile picture')} className={!channelThumbnail ? 'channel-thumbnail__default' : 'channel-thumbnail__custom'} src={(!thumbLoadError && channelThumbnail) || defaultAvatar} + width={small || xsmall ? 64 : 160} loading={noLazyLoad ? undefined : 'lazy'} onError={() => { if (setThumbUploadError) { diff --git a/ui/component/optimizedImage/view.jsx b/ui/component/optimizedImage/view.jsx index 63b751b72..6e5b08e67 100644 --- a/ui/component/optimizedImage/view.jsx +++ b/ui/component/optimizedImage/view.jsx @@ -2,97 +2,45 @@ import React from 'react'; import { getThumbnailCdnUrl } from 'util/thumbnail'; -function scaleToDevicePixelRatio(value: number, window: any) { +function scaleToDevicePixelRatio(value: number) { const devicePixelRatio = window.devicePixelRatio || 1.0; return Math.ceil(value * devicePixelRatio); } +function getOptimizedImgUrl(url, width, height, quality) { + let optimizedUrl = url; + if (url && !url.startsWith('/public/')) { + optimizedUrl = url.trim().replace(/^http:\/\//i, 'https://'); + + if (!optimizedUrl.endsWith('.gif')) { + optimizedUrl = getThumbnailCdnUrl({ thumbnail: optimizedUrl, width, height, quality }); + } + } + return optimizedUrl; +} + +// **************************************************************************** +// OptimizedImage +// **************************************************************************** + type Props = { src: string, - objectFit?: string, + width?: number, + quality?: number, waitLoad?: boolean, }; function OptimizedImage(props: Props) { - const { objectFit, src, waitLoad, ...imgProps } = props; - const [optimizedSrc, setOptimizedSrc] = React.useState(''); + const { + src, + width = 0, // 0 = use intrinsic width + quality = 85, + waitLoad, + ...imgProps + } = props; + const ref = React.useRef(); - - function getOptimizedImgUrl(url, width, height) { - let optimizedUrl = url; - if (url && !url.startsWith('/public/')) { - optimizedUrl = url.trim().replace(/^http:\/\//i, 'https://'); - - // @if TARGET='web' - if (!optimizedUrl.endsWith('.gif')) { - optimizedUrl = getThumbnailCdnUrl({ thumbnail: optimizedUrl, width, height, quality: 85 }); - } - // @endif - } - return optimizedUrl; - } - - function getOptimumSize(elem) { - if (!elem || !elem.parentElement || !elem.parentElement.clientWidth || !elem.parentElement.clientHeight) { - return null; - } - - let width = elem.parentElement.clientWidth; - let height = elem.parentElement.clientHeight; - - width = scaleToDevicePixelRatio(width, window); - height = scaleToDevicePixelRatio(height, window); - - // Round to next 100px for better caching - width = Math.ceil(width / 100) * 100; - height = Math.ceil(height / 100) * 100; - - // Reminder: CDN expects integers. - return { width, height }; - } - - function adjustOptimizationIfNeeded(elem, objectFit, origSrc) { - if (objectFit === 'cover' && elem) { - const containerSize = getOptimumSize(elem); - if (containerSize) { - // $FlowFixMe - if (elem.naturalWidth < containerSize.width) { - // For 'cover', we don't want to stretch the image. We started off by - // filling up the container height, but the width still has a gap for - // this instance (usually due to aspect ratio mismatch). - // If the original image is much larger, we can request for a larger - // image so that "objectFit=cover" will center it without stretching and - // making it blur. The double fetch might seem wasteful, but on - // average the total transferred bytes is still less than the original. - const probablyMaxedOut = elem.naturalHeight < containerSize.height; - if (!probablyMaxedOut) { - const newOptimizedSrc = getOptimizedImgUrl(origSrc, containerSize.width, 0); - if (newOptimizedSrc && newOptimizedSrc !== optimizedSrc) { - setOptimizedSrc(newOptimizedSrc); - } - } - } - } - } - } - - React.useEffect(() => { - const containerSize = getOptimumSize(ref.current); - if (containerSize) { - const width = 0; // The CDN will fill the zeroed attribute per image's aspect ratio. - const height = containerSize.height; - - const newOptimizedSrc = getOptimizedImgUrl(src, width, height); - - if (newOptimizedSrc !== optimizedSrc) { - setOptimizedSrc(newOptimizedSrc); - } - } else { - setOptimizedSrc(src); - } - // We only want to run this on (1) initial mount and (2) 'src' change. Nothing else. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [src]); + const optimizedSrc = getOptimizedImgUrl(src, scaleToDevicePixelRatio(width), 0, quality); if (!src || !optimizedSrc) { return null; @@ -105,8 +53,9 @@ function OptimizedImage(props: Props) { style={{ visibility: waitLoad ? 'hidden' : 'visible' }} src={optimizedSrc} onLoad={() => { - if (waitLoad) ref.current.style.visibility = 'visible'; - adjustOptimizationIfNeeded(ref.current, objectFit, src); + if (waitLoad) { + ref.current.style.visibility = 'visible'; + } }} /> ); diff --git a/ui/page/channel/view.jsx b/ui/page/channel/view.jsx index 20e062378..e417a33ce 100644 --- a/ui/page/channel/view.jsx +++ b/ui/page/channel/view.jsx @@ -28,7 +28,6 @@ import TruncatedText from 'component/common/truncated-text'; import PlaceholderTx from 'static/img/placeholderTx.gif'; import Tooltip from 'component/common/tooltip'; import { toCompactNotation } from 'util/string'; -import { getThumbnailCdnUrl } from 'util/thumbnail'; export const PAGE_VIEW_QUERY = `view`; export const DISCUSSION_PAGE = `discussion`; @@ -236,18 +235,11 @@ function ChannelPage(props: Props) { {cover && } - {cover && ( - - )} + {cover && }