简体   繁体   中英

How to set Intersection Observer for a specific Svelte component/element for whole App?

Lets assume I have Icon.svelte component in /elements folder. I have used that Icon component in various other components in whole application.

Is there any way to set intersection observer for that Icon component? So when that component comes in Viewport it mount and on outside of Viewport it destroys!

Basically thinking this approach for performance boost up of application.

First of all, you should measure whether any action is even necessary.

If so, you could try simply creating an observer in each component, and only if that is too expensive extract the observer instance.

To do that you can create the observer at the top level of your application and set a context that passes the observer on to all descendants. Then in the icon component you can get the context and call observe in onMount and unobserve in onDestroy . You would want to observe some container element in the component.

You may have to add something like an EventTarget to the context so the icons have something to get events from. In the observer callback a new event can then be dispatched to that to notify the components. The arguments from the callbacks have to be passed on, so the icon can check whether it was among the intersected elements. (Event subscription and unsubscription should be done in onMount / onDestroy .)


Example:

// In root
const context = setContext('intersection-observer', writable(null));

onMount(() => {
    const event = new EventTarget();
    const observer = new IntersectionObserver(
        entries => event.dispatchEvent(new CustomEvent('intersect', { detail: entries })),
        { root: null, rootMargin: '0px', threshold: 0 },
    );

    $context = {
        observe: element => observer.observe(element),
        unobserve: element => observer.unobserve(element),
        onIntersect: event,
    };

    return () => observer.disconnect();
});
<!-- Component that uses the observer -->
<script>
    import { getContext } from 'svelte';
    import { onDestroy } from 'svelte';

    const intersectionContext = getContext('intersection-observer');
    const cleanup = [];
    let root;
    let visible = false;

    onDestroy(() => cleanup.forEach(fn => fn()));

    $: if ($intersectionContext && root) {
        $intersectionContext.observe(root);
        cleanup.push(() => $intersectionContext.unobserve(root));

        $intersectionContext.onIntersect.addEventListener('intersect', onIntersect);
        cleanup.push(() =>
            $intersectionContext.onIntersect
                .removeEventListener('intersect', onIntersect)
        );
    }

    function onIntersect(e) {
        const entries = e.detail;
        const entry = entries.find(entry => entry.target === root);

        if (entry)
            visible = entry.isIntersecting;
    }
</script>

<div bind:this={root}>
    <!-- Render expensive content here using {#if visible} -->
    {visible ? 'Visible' : 'Invisible'}
</div>

REPL

(This is a bit more complicated than what I described because this is designed to support SSR. To support SSR, APIs like IntersectionObserver cannot be used outside of onMount .)


If you do not mind a bit more convention-based magic, you can just tag elements in some way, eg using a data attribute and then send events straight to those elements.

// In root
onMount(() => {
    const intersectionObserver = new IntersectionObserver(
        entries => entries.forEach(entry =>
            entry.target.dispatchEvent(
                new CustomEvent('intersect', { detail: entry })
            )
        ),
        { root: null, rootMargin: '0px', threshold: 0 },
    );

    const mutationObserver = new MutationObserver(mutations =>
        mutations.forEach(m => {
            m.addedNodes.forEach(node => {
                if (node instanceof HTMLElement &&
                    node.dataset.intersect != null &&
                    node.dataset.intersectInitialized == null) {
                    intersectionObserver.observe(node);
                    node.dataset.intersectInitialized = 'true';
                }
            });
            m.removedNodes.forEach(node => {
                if (node instanceof HTMLElement) {
                    intersectionObserver.unobserve(node);
                }
            });
        })
    );

    [...document.querySelectorAll('[data-intersect]')].forEach(node => {
        intersectionObserver.observe(node);
        node.dataset.intersectInitialized = 'true';
    });

    mutationObserver.observe(document.body, { childList: true, subtree: true });

    return () => {
        mutationObserver.disconnect();
        intersectionObserver.disconnect();
    };
});
<script>
    let visible = false;
</script>

<div data-intersect
    on:intersect={e => visible = e.detail.isIntersecting}>
    <!-- Render expensive content here using {#if visible} -->
    {visible ? 'Visible' : 'Invisible'}
</div>

REPL

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM