hypnagaga/src/components/Visible/Visible.svelte
MinamiFunakoshiTR b7ca532195
updates visible
2025-03-27 10:23:20 -07:00

81 lines
2.7 KiB
Svelte

<!-- @component `Visible` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-utilities-visible--docs) -->
<script lang="ts">
import { onMount, type Snippet } from 'svelte';
interface Props {
/**
* Whether to change visibility just once.
*
* Useful for loading expensive images or other media and then keeping them around once they're first loaded.
*/
once?: boolean;
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `top`. */
top?: number;
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `bottom`. */
bottom?: number;
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `left`. */
left?: number;
/** Set Intersection Observer [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#rootmargin) `right`. */
right?: number;
/** Set the Intersection Observer [threshold](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#threshold). */
threshold?: number;
children?: Snippet<[boolean]>;
}
let {
once = false,
top = 0,
bottom = 0,
left = 0,
right = 0,
threshold = 0,
children,
}: Props = $props();
let visible = $state(false);
let container: HTMLElement | undefined = $state(undefined);
onMount(() => {
if (typeof IntersectionObserver !== 'undefined') {
const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`;
const observer = new IntersectionObserver(
(entries) => {
visible = entries[0].isIntersecting;
if (visible && once && container) {
observer.unobserve(container);
}
},
{
rootMargin,
threshold,
}
);
if (container) observer.observe(container);
return () => {
if (container) observer.observe(container);
};
}
function handler() {
if (container) {
const bcr = container.getBoundingClientRect();
visible =
bcr.bottom + bottom > 0 &&
bcr.right + right > 0 &&
bcr.top - top < window.innerHeight &&
bcr.left - left < window.innerWidth;
}
if (visible && once) {
window.removeEventListener('scroll', handler);
}
}
window.addEventListener('scroll', handler);
return () => window.removeEventListener('scroll', handler);
});
</script>
<div bind:this={container}>
{#if children}
{@render children(visible)}
{/if}
</div>