53 lines
1.5 KiB
TypeScript
53 lines
1.5 KiB
TypeScript
import type { Component, JSX } from 'solid-js';
|
|
import { createSignal, onMount, onCleanup, Show } from 'solid-js';
|
|
|
|
interface LazyItemProps {
|
|
children: JSX.Element;
|
|
estimatedHeight?: number;
|
|
rootMargin?: string;
|
|
}
|
|
|
|
/**
|
|
* A component that only renders its children when it's within (or near) the viewport.
|
|
* This is a lightweight alternative to full list virtualization.
|
|
*/
|
|
const LazyItem: Component<LazyItemProps> = (props) => {
|
|
const [isVisible, setIsVisible] = createSignal(false);
|
|
let containerRef: HTMLDivElement | undefined;
|
|
|
|
onMount(() => {
|
|
const observer = new IntersectionObserver(([entry]) => {
|
|
setIsVisible(entry.isIntersecting);
|
|
}, {
|
|
rootMargin: props.rootMargin || '400px 0px', // Pre-render 400px before appearing
|
|
threshold: 0
|
|
});
|
|
|
|
if (containerRef) {
|
|
observer.observe(containerRef);
|
|
}
|
|
|
|
onCleanup(() => {
|
|
observer.disconnect();
|
|
});
|
|
});
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
class="lazy-item-container"
|
|
style={{
|
|
"min-height": isVisible() ? "auto" : `${props.estimatedHeight || 60}px`,
|
|
"contain-intrinsic-size": `auto ${props.estimatedHeight || 60}px`,
|
|
"content-visibility": "auto" // CSS-native optimization hint
|
|
}}
|
|
>
|
|
<Show when={isVisible()}>
|
|
{props.children}
|
|
</Show>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LazyItem;
|