Fix #5795 - Claim images not lazy loaded

This commit is contained in:
Louis Sandoval 2021-04-06 22:33:36 -07:00 committed by Sean Yesmunt
parent fe69ef2c90
commit a7cb0e240e
5 changed files with 69 additions and 5 deletions

View file

@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed ### Fixed
- Lazy-load claim images to improve app responsiveness ([#5795](https://github.com/lbryio/lbry-desktop/issues/5795))
- Fix display of upload date and view count on smaller screens ([#5822](https://github.com/lbryio/lbry-desktop/issues/5822)) - Fix display of upload date and view count on smaller screens ([#5822](https://github.com/lbryio/lbry-desktop/issues/5822))
- Autoplay looping to a previous video or itself ([#5711](https://github.com/lbryio/lbry-desktop/pull/5711)) - Autoplay looping to a previous video or itself ([#5711](https://github.com/lbryio/lbry-desktop/pull/5711))
- Autoplay not working in mini-player mode ([#5716](https://github.com/lbryio/lbry-desktop/pull/5716)) - Autoplay not working in mini-player mode ([#5716](https://github.com/lbryio/lbry-desktop/pull/5716))

View file

@ -5,6 +5,7 @@ import classnames from 'classnames';
import Gerbil from './gerbil.png'; import Gerbil from './gerbil.png';
import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper'; import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper';
import ChannelStakedIndicator from 'component/channelStakedIndicator'; import ChannelStakedIndicator from 'component/channelStakedIndicator';
import useLazyLoading from '../../util/useLazyLoading';
type Props = { type Props = {
thumbnail: ?string, thumbnail: ?string,
@ -42,6 +43,7 @@ function ChannelThumbnail(props: Props) {
const thumbnailPreview = rawThumbnailPreview && rawThumbnailPreview.trim().replace(/^http:\/\//i, 'https://'); const thumbnailPreview = rawThumbnailPreview && rawThumbnailPreview.trim().replace(/^http:\/\//i, 'https://');
const channelThumbnail = thumbnail || thumbnailPreview; const channelThumbnail = thumbnail || thumbnailPreview;
const showThumb = (!obscure && !!thumbnail) || thumbnailPreview; const showThumb = (!obscure && !!thumbnail) || thumbnailPreview;
const thumbnailRef = React.useRef(null);
// Generate a random color class based on the first letter of the channel name // Generate a random color class based on the first letter of the channel name
const { channelName } = parseURI(uri); const { channelName } = parseURI(uri);
let initializer; let initializer;
@ -59,6 +61,8 @@ function ChannelThumbnail(props: Props) {
} }
}, [doResolveUri, shouldResolve, uri]); }, [doResolveUri, shouldResolve, uri]);
useLazyLoading(thumbnailRef, 0.25, [showThumb]);
if (channelThumbnail && channelThumbnail.endsWith('gif') && !allowGifs) { if (channelThumbnail && channelThumbnail.endsWith('gif') && !allowGifs) {
return ( return (
<FreezeframeWrapper src={channelThumbnail} className={classnames('channel-thumbnail', className)}> <FreezeframeWrapper src={channelThumbnail} className={classnames('channel-thumbnail', className)}>
@ -77,9 +81,10 @@ function ChannelThumbnail(props: Props) {
> >
{!showThumb && ( {!showThumb && (
<img <img
ref={thumbnailRef}
alt={__('Channel profile picture')} alt={__('Channel profile picture')}
className="channel-thumbnail__default" className="channel-thumbnail__default"
src={!thumbError && thumbnailPreview ? thumbnailPreview : Gerbil} data-src={!thumbError && thumbnailPreview ? thumbnailPreview : Gerbil}
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil. onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
/> />
)} )}
@ -89,9 +94,10 @@ function ChannelThumbnail(props: Props) {
<div className="chanel-thumbnail--waiting">{__('This will be visible in a few minutes.')}</div> <div className="chanel-thumbnail--waiting">{__('This will be visible in a few minutes.')}</div>
) : ( ) : (
<img <img
ref={thumbnailRef}
alt={__('Channel profile picture')} alt={__('Channel profile picture')}
className="channel-thumbnail__custom" className="channel-thumbnail__custom"
src={!thumbError ? thumbnailPreview || thumbnail : Gerbil} data-src={!thumbError ? thumbnailPreview || thumbnail : Gerbil}
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil. onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
/> />
)} )}

View file

@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Freezeframe from './FreezeframeLite'; import Freezeframe from './FreezeframeLite';
import useLazyLoading from '../../util/useLazyLoading';
const FreezeframeWrapper = (props) => { const FreezeframeWrapper = (props) => {
const imgRef = React.useRef(); const imgRef = React.useRef();
@ -13,10 +14,12 @@ const FreezeframeWrapper = (props) => {
freezeframe.current = new Freezeframe(imgRef.current); freezeframe.current = new Freezeframe(imgRef.current);
}, []); }, []);
useLazyLoading(imgRef);
return ( return (
<div className={classnames(className, 'freezeframe-wrapper')}> <div className={classnames(className, 'freezeframe-wrapper')}>
<> <>
<img ref={imgRef} src={src} className="freezeframe-img" /> <img ref={imgRef} data-src={src} className="freezeframe-img" />
{children} {children}
</> </>
</div> </div>

View file

@ -5,6 +5,7 @@ import React from 'react';
import FreezeframeWrapper from './FreezeframeWrapper'; import FreezeframeWrapper from './FreezeframeWrapper';
import Placeholder from './placeholder.png'; import Placeholder from './placeholder.png';
import classnames from 'classnames'; import classnames from 'classnames';
import useLazyLoading from '../../util/useLazyLoading';
type Props = { type Props = {
uri: string, uri: string,
@ -12,7 +13,7 @@ type Props = {
children?: Node, children?: Node,
allowGifs: boolean, allowGifs: boolean,
claim: ?StreamClaim, claim: ?StreamClaim,
doResolveUri: string => void, doResolveUri: (string) => void,
className?: string, className?: string,
}; };
@ -23,6 +24,7 @@ function FileThumbnail(props: Props) {
uri && claim && claim.value && claim.value.thumbnail ? claim.value.thumbnail.url : undefined; uri && claim && claim.value && claim.value.thumbnail ? claim.value.thumbnail.url : undefined;
const thumbnail = passedThumbnail || thumbnailFromClaim; const thumbnail = passedThumbnail || thumbnailFromClaim;
const hasResolvedClaim = claim !== undefined; const hasResolvedClaim = claim !== undefined;
const thumbnailRef = React.useRef(null);
React.useEffect(() => { React.useEffect(() => {
if (!hasResolvedClaim && uri) { if (!hasResolvedClaim && uri) {
@ -30,6 +32,8 @@ function FileThumbnail(props: Props) {
} }
}, [hasResolvedClaim, uri, doResolveUri]); }, [hasResolvedClaim, uri, doResolveUri]);
useLazyLoading(thumbnailRef);
if (!allowGifs && thumbnail && thumbnail.endsWith('gif')) { if (!allowGifs && thumbnail && thumbnail.endsWith('gif')) {
return ( return (
<FreezeframeWrapper src={thumbnail} className={classnames('media__thumb', className)}> <FreezeframeWrapper src={thumbnail} className={classnames('media__thumb', className)}>
@ -46,9 +50,12 @@ function FileThumbnail(props: Props) {
} }
// @endif // @endif
const thumnailUrl = url ? url.replace(/'/g, "\\'") : '';
return ( return (
<div <div
style={{ backgroundImage: `url('${url ? url.replace(/'/g, "\\'") : ''}')` }} ref={thumbnailRef}
data-background-image={thumnailUrl}
className={classnames('media__thumb', className, { className={classnames('media__thumb', className, {
'media__thumb--resolving': !hasResolvedClaim, 'media__thumb--resolving': !hasResolvedClaim,
})} })}

47
ui/util/useLazyLoading.js Normal file
View file

@ -0,0 +1,47 @@
import { useEffect } from 'react';
/**
* Helper React hook for lazy loading images
* @param elementRef - A React useRef instance to the element to lazy load.
* @param {Number} [threshold=0.5] - The percent visible in order for loading to begin.
* @param {Array<>} [deps=[]] - The dependencies this lazy-load is reliant on.
*/
export default function useLazyLoading(elementRef, threshold = 0.25, deps = []) {
useEffect(() => {
if (!elementRef.current) {
return;
}
const lazyLoadingObserver = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
if (entry.intersectionRatio >= threshold) {
const { target } = entry;
observer.unobserve(target);
// useful for lazy loading img tags
if (target.dataset.src) {
target.src = target.dataset.src;
return;
}
// useful for lazy loading background images on divs
if (target.dataset.backgroundImage) {
target.style.backgroundImage = `url(${target.dataset.backgroundImage})`;
}
}
});
},
{
root: null,
rootMargin: '0px',
threshold,
}
);
lazyLoadingObserver.observe(elementRef.current);
// re-run whenever the element ref changes
}, deps);
}