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.
This commit is contained in:
infinite-persistence 2022-03-21 11:40:32 +08:00 committed by Thomas Zarebczan
parent 75478ad18f
commit 1e0da66110
3 changed files with 34 additions and 102 deletions

View file

@ -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) {

View file

@ -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<any>();
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';
}
}}
/>
);

View file

@ -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) {
<ClaimMenuList uri={claim.permanent_url} inline isChannelPage />
</div>
{cover && <img className={classnames('channel-cover__custom')} src={PlaceholderTx} />}
{cover && (
<OptimizedImage
className={classnames('channel-cover__custom')}
src={getThumbnailCdnUrl({ thumbnail: cover, width: 0, height: 0, quality: 85 })}
objectFit="cover"
/>
)}
{cover && <OptimizedImage className={classnames('channel-cover__custom')} src={cover} objectFit="cover" />}
<div className="channel__primary-info">
<ChannelThumbnail
className="channel__thumbnail--channel-page"
uri={uri}
minOptimization
allowGifs
showMemberBadge
isChannel