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:
parent
75478ad18f
commit
1e0da66110
3 changed files with 34 additions and 102 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
src: string,
|
||||
objectFit?: string,
|
||||
waitLoad?: boolean,
|
||||
};
|
||||
|
||||
function OptimizedImage(props: Props) {
|
||||
const { objectFit, src, waitLoad, ...imgProps } = props;
|
||||
const [optimizedSrc, setOptimizedSrc] = React.useState('');
|
||||
const ref = React.useRef<any>();
|
||||
|
||||
function getOptimizedImgUrl(url, width, height) {
|
||||
function getOptimizedImgUrl(url, width, height, quality) {
|
||||
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 });
|
||||
optimizedUrl = getThumbnailCdnUrl({ thumbnail: optimizedUrl, width, height, quality });
|
||||
}
|
||||
// @endif
|
||||
}
|
||||
return optimizedUrl;
|
||||
}
|
||||
|
||||
function getOptimumSize(elem) {
|
||||
if (!elem || !elem.parentElement || !elem.parentElement.clientWidth || !elem.parentElement.clientHeight) {
|
||||
return null;
|
||||
}
|
||||
// ****************************************************************************
|
||||
// OptimizedImage
|
||||
// ****************************************************************************
|
||||
|
||||
let width = elem.parentElement.clientWidth;
|
||||
let height = elem.parentElement.clientHeight;
|
||||
type Props = {
|
||||
src: string,
|
||||
width?: number,
|
||||
quality?: number,
|
||||
waitLoad?: boolean,
|
||||
};
|
||||
|
||||
width = scaleToDevicePixelRatio(width, window);
|
||||
height = scaleToDevicePixelRatio(height, window);
|
||||
function OptimizedImage(props: Props) {
|
||||
const {
|
||||
src,
|
||||
width = 0, // 0 = use intrinsic width
|
||||
quality = 85,
|
||||
waitLoad,
|
||||
...imgProps
|
||||
} = props;
|
||||
|
||||
// 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 ref = React.useRef<any>();
|
||||
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';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue