diff --git a/CHANGELOG.md b/CHANGELOG.md index a8fbfdf70..2e47f5273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### 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)) - 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)) diff --git a/ui/component/channelThumbnail/view.jsx b/ui/component/channelThumbnail/view.jsx index d3af5c508..e48a8c46c 100644 --- a/ui/component/channelThumbnail/view.jsx +++ b/ui/component/channelThumbnail/view.jsx @@ -5,6 +5,7 @@ import classnames from 'classnames'; import Gerbil from './gerbil.png'; import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper'; import ChannelStakedIndicator from 'component/channelStakedIndicator'; +import useLazyLoading from '../../util/useLazyLoading'; type Props = { thumbnail: ?string, @@ -42,6 +43,7 @@ function ChannelThumbnail(props: Props) { const thumbnailPreview = rawThumbnailPreview && rawThumbnailPreview.trim().replace(/^http:\/\//i, 'https://'); const channelThumbnail = 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 const { channelName } = parseURI(uri); let initializer; @@ -59,6 +61,8 @@ function ChannelThumbnail(props: Props) { } }, [doResolveUri, shouldResolve, uri]); + useLazyLoading(thumbnailRef, 0.25, [showThumb]); + if (channelThumbnail && channelThumbnail.endsWith('gif') && !allowGifs) { return ( @@ -77,9 +81,10 @@ function ChannelThumbnail(props: Props) { > {!showThumb && ( {__('Channel setThumbError(true)} // if thumb fails (including due to https replace, show gerbil. /> )} @@ -89,9 +94,10 @@ function ChannelThumbnail(props: Props) {
{__('This will be visible in a few minutes.')}
) : ( {__('Channel setThumbError(true)} // if thumb fails (including due to https replace, show gerbil. /> )} diff --git a/ui/component/fileThumbnail/FreezeframeWrapper.jsx b/ui/component/fileThumbnail/FreezeframeWrapper.jsx index 2c2ec9afd..d255c3a4e 100644 --- a/ui/component/fileThumbnail/FreezeframeWrapper.jsx +++ b/ui/component/fileThumbnail/FreezeframeWrapper.jsx @@ -2,6 +2,7 @@ import React, { useEffect } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; import Freezeframe from './FreezeframeLite'; +import useLazyLoading from '../../util/useLazyLoading'; const FreezeframeWrapper = (props) => { const imgRef = React.useRef(); @@ -13,10 +14,12 @@ const FreezeframeWrapper = (props) => { freezeframe.current = new Freezeframe(imgRef.current); }, []); + useLazyLoading(imgRef); + return (
<> - + {children}
diff --git a/ui/component/fileThumbnail/view.jsx b/ui/component/fileThumbnail/view.jsx index ad818c19c..f5920d090 100644 --- a/ui/component/fileThumbnail/view.jsx +++ b/ui/component/fileThumbnail/view.jsx @@ -5,6 +5,7 @@ import React from 'react'; import FreezeframeWrapper from './FreezeframeWrapper'; import Placeholder from './placeholder.png'; import classnames from 'classnames'; +import useLazyLoading from '../../util/useLazyLoading'; type Props = { uri: string, @@ -12,7 +13,7 @@ type Props = { children?: Node, allowGifs: boolean, claim: ?StreamClaim, - doResolveUri: string => void, + doResolveUri: (string) => void, className?: string, }; @@ -23,6 +24,7 @@ function FileThumbnail(props: Props) { uri && claim && claim.value && claim.value.thumbnail ? claim.value.thumbnail.url : undefined; const thumbnail = passedThumbnail || thumbnailFromClaim; const hasResolvedClaim = claim !== undefined; + const thumbnailRef = React.useRef(null); React.useEffect(() => { if (!hasResolvedClaim && uri) { @@ -30,6 +32,8 @@ function FileThumbnail(props: Props) { } }, [hasResolvedClaim, uri, doResolveUri]); + useLazyLoading(thumbnailRef); + if (!allowGifs && thumbnail && thumbnail.endsWith('gif')) { return ( @@ -46,9 +50,12 @@ function FileThumbnail(props: Props) { } // @endif + const thumnailUrl = url ? url.replace(/'/g, "\\'") : ''; + return (
} [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); +}