Show image loading placeholder only when visible

The MUI spinner impacts the cpu usage.
While only a few of them don't matter, rendering multiple of them can impact the cpu usage.

This can be a problem in the reader in case a chapter with a lot of chapters is rendered and a continuous reading mode is being used.
In such a case the cpu usage stays increased until all pages have been loaded
This commit is contained in:
schroda
2025-02-08 19:48:50 +01:00
parent a4e9e8f264
commit a7df8eb0f4

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
import { useState, useEffect, forwardRef, ForwardedRef } from 'react'; import { useState, useEffect, forwardRef, ForwardedRef, useCallback, useRef } from 'react';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
@@ -19,6 +19,7 @@ import { SxProps, Theme } from '@mui/material/styles';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { Priority } from '@/lib/Queue.ts'; import { Priority } from '@/lib/Queue.ts';
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
import { useIntersectionObserver } from '@/modules/core/hooks/useIntersectionObserver.tsx';
interface IProps { interface IProps {
shouldLoad?: boolean; shouldLoad?: boolean;
@@ -42,7 +43,8 @@ interface IProps {
retryKeyPrefix?: string; retryKeyPrefix?: string;
} }
export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTMLImageElement | null>) => { export const SpinnerImage = forwardRef(
(props: IProps, imgRef: ForwardedRef<HTMLImageElement | HTMLDivElement | null>) => {
const { const {
shouldLoad = true, shouldLoad = true,
shouldDecode, shouldDecode,
@@ -61,12 +63,15 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
const { t } = useTranslation(); const { t } = useTranslation();
const loadingIndicatorRef = useRef<HTMLDivElement | null>(null);
const showMissingImageIcon = !src.length; const showMissingImageIcon = !src.length;
const [imageSourceUrl, setImageSourceUrl] = useState(''); const [imageSourceUrl, setImageSourceUrl] = useState('');
const [imgLoadRetryKey, setImgLoadRetryKey] = useState(0); const [imgLoadRetryKey, setImgLoadRetryKey] = useState(0);
const [isLoading, setIsLoading] = useState<boolean | undefined>(undefined); const [isLoading, setIsLoading] = useState<boolean | undefined>(undefined);
const [hasError, setHasError] = useState(false); const [hasError, setHasError] = useState(false);
const [isVisible, setIsVisible] = useState(false);
const updateImageState = (loading: boolean, error: boolean = false, aborted: boolean = false) => { const updateImageState = (loading: boolean, error: boolean = false, aborted: boolean = false) => {
setIsLoading(loading); setIsLoading(loading);
@@ -81,6 +86,11 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
} }
}; };
useIntersectionObserver(
loadingIndicatorRef,
useCallback((entries) => setIsVisible(entries[0].isIntersecting), []),
);
useEffect(() => { useEffect(() => {
if (showMissingImageIcon || !shouldLoad) { if (showMissingImageIcon || !shouldLoad) {
return () => {}; return () => {};
@@ -172,6 +182,7 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
{(isLoading || (src && !imageSourceUrl) || hasError) && ( {(isLoading || (src && !imageSourceUrl) || hasError) && (
<Stack <Stack
ref={loadingIndicatorRef}
sx={{ sx={{
height: '100%', height: '100%',
justifyContent: 'center', justifyContent: 'center',
@@ -186,7 +197,9 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
{(isLoading || (src && !imageSourceUrl && !hasError)) && <CircularProgress thickness={5} />} {isVisible && (isLoading || (src && !imageSourceUrl && !hasError)) && (
<CircularProgress thickness={5} />
)}
{hasError && isLoading === false && ( {hasError && isLoading === false && (
<> <>
<BrokenImageIcon /> <BrokenImageIcon />
@@ -208,4 +221,5 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTML
)} )}
</> </>
); );
}); },
);