mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
track image viewport state in sessionStorage
This commit is contained in:
@@ -26,7 +26,7 @@ export interface ImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 's
|
|||||||
imageContainerProps?: Omit<ImageContainerProps, 'children'>;
|
imageContainerProps?: Omit<ImageContainerProps, 'children'>;
|
||||||
includeLoader?: boolean;
|
includeLoader?: boolean;
|
||||||
includeUnloader?: boolean;
|
includeUnloader?: boolean;
|
||||||
src: string | string[] | undefined;
|
src: string | undefined;
|
||||||
thumbHash?: string;
|
thumbHash?: string;
|
||||||
unloaderIcon?: keyof typeof AppIcon;
|
unloaderIcon?: keyof typeof AppIcon;
|
||||||
}
|
}
|
||||||
@@ -147,20 +147,31 @@ function ImageWithDebounce({
|
|||||||
const hasBeenInViewportRef = useRef(false);
|
const hasBeenInViewportRef = useRef(false);
|
||||||
const prevDebouncedSrcRef = useRef(debouncedSrc);
|
const prevDebouncedSrcRef = useRef(debouncedSrc);
|
||||||
|
|
||||||
|
const srcInDisplayedCache = isInDisplayedCache(src);
|
||||||
|
|
||||||
|
if (srcInDisplayedCache) {
|
||||||
|
hasBeenInViewportRef.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (prevDebouncedSrcRef.current !== debouncedSrc) {
|
if (prevDebouncedSrcRef.current !== debouncedSrc) {
|
||||||
prevDebouncedSrcRef.current = debouncedSrc;
|
prevDebouncedSrcRef.current = debouncedSrc;
|
||||||
hasBeenInViewportRef.current = false;
|
if (!srcInDisplayedCache) hasBeenInViewportRef.current = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inViewport && debouncedSrc) {
|
if (inViewport && debouncedSrc) {
|
||||||
hasBeenInViewportRef.current = true;
|
hasBeenInViewportRef.current = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const effectiveSrc = debouncedSrc ?? (srcInDisplayedCache ? src : undefined);
|
||||||
const shouldShowImage = enableViewport
|
const shouldShowImage = enableViewport
|
||||||
? (inViewport || hasBeenInViewportRef.current) && debouncedSrc
|
? (inViewport || hasBeenInViewportRef.current) && effectiveSrc
|
||||||
: debouncedSrc;
|
: effectiveSrc;
|
||||||
|
|
||||||
if (enableViewport) {
|
if (enableViewport) {
|
||||||
|
if (shouldShowImage && effectiveSrc) {
|
||||||
|
addToDisplayedCache(effectiveSrc);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageContainer
|
<ImageContainer
|
||||||
className={clsx(containerClassName, containerPropsClassName)}
|
className={clsx(containerClassName, containerPropsClassName)}
|
||||||
@@ -168,7 +179,7 @@ function ImageWithDebounce({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
{...restContainerProps}
|
{...restContainerProps}
|
||||||
>
|
>
|
||||||
{shouldShowImage && debouncedSrc ? (
|
{shouldShowImage && effectiveSrc ? (
|
||||||
<Img
|
<Img
|
||||||
className={clsx(styles.image, className, {
|
className={clsx(styles.image, className, {
|
||||||
[styles.animated]: enableAnimation,
|
[styles.animated]: enableAnimation,
|
||||||
@@ -176,7 +187,7 @@ function ImageWithDebounce({
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
fetchPriority={fetchPriority}
|
fetchPriority={fetchPriority}
|
||||||
loader={includeLoader ? <ImageLoader className={className} /> : null}
|
loader={includeLoader ? <ImageLoader className={className} /> : null}
|
||||||
src={debouncedSrc}
|
src={effectiveSrc}
|
||||||
unloader={
|
unloader={
|
||||||
includeUnloader ? (
|
includeUnloader ? (
|
||||||
<ImageUnloader className={className} icon={unloaderIcon} />
|
<ImageUnloader className={className} icon={unloaderIcon} />
|
||||||
@@ -193,13 +204,14 @@ function ImageWithDebounce({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (effectiveSrc) addToDisplayedCache(effectiveSrc);
|
||||||
return (
|
return (
|
||||||
<ImageContainer
|
<ImageContainer
|
||||||
className={clsx(containerClassName, containerPropsClassName)}
|
className={clsx(containerClassName, containerPropsClassName)}
|
||||||
enableAnimation={enableAnimation}
|
enableAnimation={enableAnimation}
|
||||||
{...restContainerProps}
|
{...restContainerProps}
|
||||||
>
|
>
|
||||||
{debouncedSrc ? (
|
{effectiveSrc ? (
|
||||||
<Img
|
<Img
|
||||||
className={clsx(styles.image, className, {
|
className={clsx(styles.image, className, {
|
||||||
[styles.animated]: enableAnimation,
|
[styles.animated]: enableAnimation,
|
||||||
@@ -207,7 +219,7 @@ function ImageWithDebounce({
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
fetchPriority={fetchPriority}
|
fetchPriority={fetchPriority}
|
||||||
loader={includeLoader ? <ImageLoader className={className} /> : null}
|
loader={includeLoader ? <ImageLoader className={className} /> : null}
|
||||||
src={debouncedSrc}
|
src={effectiveSrc}
|
||||||
unloader={
|
unloader={
|
||||||
includeUnloader ? (
|
includeUnloader ? (
|
||||||
<ImageUnloader className={className} icon={unloaderIcon} />
|
<ImageUnloader className={className} icon={unloaderIcon} />
|
||||||
@@ -242,9 +254,14 @@ function ImageWithViewport({
|
|||||||
const hasBeenInViewportRef = useRef(false);
|
const hasBeenInViewportRef = useRef(false);
|
||||||
const prevSrcRef = useRef(src);
|
const prevSrcRef = useRef(src);
|
||||||
|
|
||||||
|
const srcInDisplayedCache = isInDisplayedCache(src);
|
||||||
|
if (srcInDisplayedCache) {
|
||||||
|
hasBeenInViewportRef.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (prevSrcRef.current !== src) {
|
if (prevSrcRef.current !== src) {
|
||||||
prevSrcRef.current = src;
|
prevSrcRef.current = src;
|
||||||
hasBeenInViewportRef.current = false;
|
if (!srcInDisplayedCache) hasBeenInViewportRef.current = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inViewport && src) {
|
if (inViewport && src) {
|
||||||
@@ -253,6 +270,7 @@ function ImageWithViewport({
|
|||||||
|
|
||||||
const shouldShowImage = (inViewport || hasBeenInViewportRef.current) && src;
|
const shouldShowImage = (inViewport || hasBeenInViewportRef.current) && src;
|
||||||
|
|
||||||
|
if (shouldShowImage && src) addToDisplayedCache(src);
|
||||||
return (
|
return (
|
||||||
<ImageContainer
|
<ImageContainer
|
||||||
className={clsx(containerClassName, containerPropsClassName)}
|
className={clsx(containerClassName, containerPropsClassName)}
|
||||||
@@ -285,6 +303,46 @@ function ImageWithViewport({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DISPLAYED_SRC_CACHE_KEY = 'feishin-displayed-src-cache';
|
||||||
|
const MAX_DISPLAYED_SRC_CACHE = 500;
|
||||||
|
|
||||||
|
function addToDisplayedCache(src: string | undefined) {
|
||||||
|
if (!src) return;
|
||||||
|
try {
|
||||||
|
const cache = getDisplayedSrcCache();
|
||||||
|
if (cache.includes(src)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cache.length >= MAX_DISPLAYED_SRC_CACHE) {
|
||||||
|
cache.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.push(src);
|
||||||
|
sessionStorage.setItem(DISPLAYED_SRC_CACHE_KEY, JSON.stringify(cache));
|
||||||
|
} catch {
|
||||||
|
// ignore error if sessionStorage is unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayedSrcCache(): string[] {
|
||||||
|
try {
|
||||||
|
const raw = sessionStorage.getItem(DISPLAYED_SRC_CACHE_KEY);
|
||||||
|
return raw ? (JSON.parse(raw) as string[]) : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInDisplayedCache(src: string | undefined): boolean {
|
||||||
|
if (!src) return false;
|
||||||
|
try {
|
||||||
|
return getDisplayedSrcCache().includes(src);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const Image = memo(BaseImage);
|
export const Image = memo(BaseImage);
|
||||||
|
|
||||||
const ImageContainer = forwardRef(
|
const ImageContainer = forwardRef(
|
||||||
|
|||||||
Reference in New Issue
Block a user