forked from akai/readest
7b60b1bb0c
On iOS, navigating to a book group in the library caused the WebKit GPU
process to exceed its 300 MB jetsam limit (peaking at ~328 MB), resulting
in a blank screen flash and broken scroll state.
Three changes reduce peak GPU memory usage:
- Add overscan={200} to VirtuosoGrid/Virtuoso so only items within 200px
of the viewport are rendered, limiting simultaneous image decoding
- Add loading="lazy" to both Image components in BookCover so the browser
defers decoding offscreen cover images
- Conditionally mount the <video> and <audio> elements in AtmosphereOverlay
only when atmosphere mode is active, eliminating idle H.264 decoder
memory overhead
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
62 lines
1.7 KiB
TypeScript
62 lines
1.7 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef } from 'react';
|
|
import { useAtmosphereStore } from '@/store/atmosphereStore';
|
|
import { useThemeStore } from '@/store/themeStore';
|
|
|
|
const AtmosphereOverlay = () => {
|
|
const active = useAtmosphereStore((s) => s.active);
|
|
const isDarkMode = useThemeStore((s) => s.isDarkMode);
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
const audioRef = useRef<HTMLAudioElement>(null);
|
|
const isInitialMount = useRef(true);
|
|
|
|
const audioSrc = isDarkMode ? '/assets/forest-crickets.mp3' : '/assets/forest-birds.mp3';
|
|
|
|
useEffect(() => {
|
|
const video = videoRef.current;
|
|
const audio = audioRef.current;
|
|
|
|
if (active) {
|
|
document.body.classList.add('atmosphere');
|
|
video?.play()?.catch(() => {});
|
|
if (!isInitialMount.current) {
|
|
audio?.play()?.catch(() => {});
|
|
}
|
|
} else {
|
|
document.body.classList.remove('atmosphere');
|
|
}
|
|
isInitialMount.current = false;
|
|
}, [active]);
|
|
|
|
useEffect(() => {
|
|
const audio = audioRef.current;
|
|
if (!audio || !active) return;
|
|
audio.src = audioSrc;
|
|
audio.play()?.catch(() => {});
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [audioSrc]);
|
|
|
|
return (
|
|
<>
|
|
{active && (
|
|
<video
|
|
ref={videoRef}
|
|
id='atmosphere-overlay'
|
|
src='/assets/komorebi.mp4'
|
|
loop
|
|
muted
|
|
playsInline
|
|
preload='none'
|
|
/>
|
|
)}
|
|
{active && (
|
|
// biome-ignore lint/a11y/useMediaCaption: ambient background audio, no spoken content
|
|
<audio ref={audioRef} id='forest-audio' src={audioSrc} loop preload='none' />
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default AtmosphereOverlay;
|