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 { AVATAR_DEFAULT } from 'config';
|
||||||
import useGetUserMemberships from 'effects/use-get-user-memberships';
|
import useGetUserMemberships from 'effects/use-get-user-memberships';
|
||||||
import PremiumBadge from 'component/common/premium-badge';
|
import PremiumBadge from 'component/common/premium-badge';
|
||||||
import { getThumbnailCdnUrl } from 'util/thumbnail';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
thumbnail: ?string,
|
thumbnail: ?string,
|
||||||
|
@ -25,7 +24,6 @@ type Props = {
|
||||||
noLazyLoad?: boolean,
|
noLazyLoad?: boolean,
|
||||||
hideStakedIndicator?: boolean,
|
hideStakedIndicator?: boolean,
|
||||||
hideTooltip?: boolean,
|
hideTooltip?: boolean,
|
||||||
minOptimization?: boolean,
|
|
||||||
setThumbUploadError: (boolean) => void,
|
setThumbUploadError: (boolean) => void,
|
||||||
ThumbUploadError: boolean,
|
ThumbUploadError: boolean,
|
||||||
claimsByUri: { [string]: any },
|
claimsByUri: { [string]: any },
|
||||||
|
@ -59,21 +57,13 @@ function ChannelThumbnail(props: Props) {
|
||||||
showMemberBadge,
|
showMemberBadge,
|
||||||
isChannel,
|
isChannel,
|
||||||
checkMembership = true,
|
checkMembership = true,
|
||||||
minOptimization,
|
|
||||||
} = props;
|
} = props;
|
||||||
const [thumbLoadError, setThumbLoadError] = React.useState(ThumbUploadError);
|
const [thumbLoadError, setThumbLoadError] = React.useState(ThumbUploadError);
|
||||||
const shouldResolve = !isResolving && claim === undefined;
|
const shouldResolve = !isResolving && claim === undefined;
|
||||||
const thumbnail = rawThumbnail && rawThumbnail.trim().replace(/^http:\/\//i, 'https://');
|
const thumbnail = rawThumbnail && rawThumbnail.trim().replace(/^http:\/\//i, 'https://');
|
||||||
const thumbnailPreview = rawThumbnailPreview && rawThumbnailPreview.trim().replace(/^http:\/\//i, 'https://');
|
const thumbnailPreview = rawThumbnailPreview && rawThumbnailPreview.trim().replace(/^http:\/\//i, 'https://');
|
||||||
const defaultAvatar = AVATAR_DEFAULT || Gerbil;
|
const defaultAvatar = AVATAR_DEFAULT || Gerbil;
|
||||||
const thumb = thumbnailPreview || thumbnail || defaultAvatar;
|
const channelThumbnail = 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 isGif = channelThumbnail && channelThumbnail.endsWith('gif');
|
const isGif = channelThumbnail && channelThumbnail.endsWith('gif');
|
||||||
const showThumb = (!obscure && !!thumbnail) || thumbnailPreview;
|
const showThumb = (!obscure && !!thumbnail) || thumbnailPreview;
|
||||||
|
|
||||||
|
@ -125,6 +115,7 @@ function ChannelThumbnail(props: Props) {
|
||||||
alt={__('Channel profile picture')}
|
alt={__('Channel profile picture')}
|
||||||
className={!channelThumbnail ? 'channel-thumbnail__default' : 'channel-thumbnail__custom'}
|
className={!channelThumbnail ? 'channel-thumbnail__default' : 'channel-thumbnail__custom'}
|
||||||
src={(!thumbLoadError && channelThumbnail) || defaultAvatar}
|
src={(!thumbLoadError && channelThumbnail) || defaultAvatar}
|
||||||
|
width={small || xsmall ? 64 : 160}
|
||||||
loading={noLazyLoad ? undefined : 'lazy'}
|
loading={noLazyLoad ? undefined : 'lazy'}
|
||||||
onError={() => {
|
onError={() => {
|
||||||
if (setThumbUploadError) {
|
if (setThumbUploadError) {
|
||||||
|
|
|
@ -2,97 +2,45 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getThumbnailCdnUrl } from 'util/thumbnail';
|
import { getThumbnailCdnUrl } from 'util/thumbnail';
|
||||||
|
|
||||||
function scaleToDevicePixelRatio(value: number, window: any) {
|
function scaleToDevicePixelRatio(value: number) {
|
||||||
const devicePixelRatio = window.devicePixelRatio || 1.0;
|
const devicePixelRatio = window.devicePixelRatio || 1.0;
|
||||||
return Math.ceil(value * devicePixelRatio);
|
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 = {
|
type Props = {
|
||||||
src: string,
|
src: string,
|
||||||
objectFit?: string,
|
width?: number,
|
||||||
|
quality?: number,
|
||||||
waitLoad?: boolean,
|
waitLoad?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function OptimizedImage(props: Props) {
|
function OptimizedImage(props: Props) {
|
||||||
const { objectFit, src, waitLoad, ...imgProps } = props;
|
const {
|
||||||
const [optimizedSrc, setOptimizedSrc] = React.useState('');
|
src,
|
||||||
|
width = 0, // 0 = use intrinsic width
|
||||||
|
quality = 85,
|
||||||
|
waitLoad,
|
||||||
|
...imgProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
const ref = React.useRef<any>();
|
const ref = React.useRef<any>();
|
||||||
|
const optimizedSrc = getOptimizedImgUrl(src, scaleToDevicePixelRatio(width), 0, quality);
|
||||||
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]);
|
|
||||||
|
|
||||||
if (!src || !optimizedSrc) {
|
if (!src || !optimizedSrc) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -105,8 +53,9 @@ function OptimizedImage(props: Props) {
|
||||||
style={{ visibility: waitLoad ? 'hidden' : 'visible' }}
|
style={{ visibility: waitLoad ? 'hidden' : 'visible' }}
|
||||||
src={optimizedSrc}
|
src={optimizedSrc}
|
||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
if (waitLoad) ref.current.style.visibility = 'visible';
|
if (waitLoad) {
|
||||||
adjustOptimizationIfNeeded(ref.current, objectFit, src);
|
ref.current.style.visibility = 'visible';
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,7 +28,6 @@ import TruncatedText from 'component/common/truncated-text';
|
||||||
import PlaceholderTx from 'static/img/placeholderTx.gif';
|
import PlaceholderTx from 'static/img/placeholderTx.gif';
|
||||||
import Tooltip from 'component/common/tooltip';
|
import Tooltip from 'component/common/tooltip';
|
||||||
import { toCompactNotation } from 'util/string';
|
import { toCompactNotation } from 'util/string';
|
||||||
import { getThumbnailCdnUrl } from 'util/thumbnail';
|
|
||||||
|
|
||||||
export const PAGE_VIEW_QUERY = `view`;
|
export const PAGE_VIEW_QUERY = `view`;
|
||||||
export const DISCUSSION_PAGE = `discussion`;
|
export const DISCUSSION_PAGE = `discussion`;
|
||||||
|
@ -236,18 +235,11 @@ function ChannelPage(props: Props) {
|
||||||
<ClaimMenuList uri={claim.permanent_url} inline isChannelPage />
|
<ClaimMenuList uri={claim.permanent_url} inline isChannelPage />
|
||||||
</div>
|
</div>
|
||||||
{cover && <img className={classnames('channel-cover__custom')} src={PlaceholderTx} />}
|
{cover && <img className={classnames('channel-cover__custom')} src={PlaceholderTx} />}
|
||||||
{cover && (
|
{cover && <OptimizedImage className={classnames('channel-cover__custom')} src={cover} objectFit="cover" />}
|
||||||
<OptimizedImage
|
|
||||||
className={classnames('channel-cover__custom')}
|
|
||||||
src={getThumbnailCdnUrl({ thumbnail: cover, width: 0, height: 0, quality: 85 })}
|
|
||||||
objectFit="cover"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="channel__primary-info">
|
<div className="channel__primary-info">
|
||||||
<ChannelThumbnail
|
<ChannelThumbnail
|
||||||
className="channel__thumbnail--channel-page"
|
className="channel__thumbnail--channel-page"
|
||||||
uri={uri}
|
uri={uri}
|
||||||
minOptimization
|
|
||||||
allowGifs
|
allowGifs
|
||||||
showMemberBadge
|
showMemberBadge
|
||||||
isChannel
|
isChannel
|
||||||
|
|
Loading…
Reference in a new issue