Merge pull request #262 from reuters-graphics/mf-visible
Updates Visible
This commit is contained in:
commit
6fc30bc419
4 changed files with 65 additions and 61 deletions
31
src/components/Visible/Visible.mdx
Normal file
31
src/components/Visible/Visible.mdx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { Meta, Canvas } from '@storybook/blocks';
|
||||||
|
|
||||||
|
import * as VisibleStories from './Visible.stories.svelte';
|
||||||
|
|
||||||
|
<Meta of={VisibleStories} />
|
||||||
|
|
||||||
|
# Visible
|
||||||
|
|
||||||
|
The `Visible` component wraps around other components or HTML elements and uses the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to determine if they are visible on the page.
|
||||||
|
|
||||||
|
This is useful for lazy loading elements, especially expensive media files or components that fetch lots of data. You can also use it trigger animations or play media once a reader scrolls a component into view.
|
||||||
|
|
||||||
|
By default, `visible` will switch between `false` and `true` whenever the element is in our out of view. To trigger this just once, set the prop `once` to `true`. This is useful for loading expensive media when they first come into view and then keeping them around once they're loaded.
|
||||||
|
|
||||||
|
> **Note:** Don't use this for content that's "above the fold" at the top of the page. That'll just slow down the first load of important visible content.
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
import { Visible } from '@reuters-graphics/graphics-components';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Visible let:visible>
|
||||||
|
{#if visible}
|
||||||
|
<p>Visible!</p>
|
||||||
|
{:else}
|
||||||
|
<p>Not yet visible.</p>
|
||||||
|
{/if}
|
||||||
|
</Visible>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Canvas of={VisibleStories.Demo} />
|
||||||
|
|
@ -1,36 +1,22 @@
|
||||||
<script module lang="ts">
|
<script module lang="ts">
|
||||||
// @ts-ignore raw
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
import componentDocs from './stories/docs/component.md?raw';
|
|
||||||
// @ts-ignore raw
|
|
||||||
import defaultSnippet from './stories/snippets/default.svelte?raw';
|
|
||||||
|
|
||||||
import Visible from './Visible.svelte';
|
import Visible from './Visible.svelte';
|
||||||
|
|
||||||
import { withSource, withComponentDocs } from '$lib/docs/utils/withParams.js';
|
const { Story } = defineMeta({
|
||||||
|
|
||||||
export const meta = {
|
|
||||||
title: 'Components/Utilities/Visible',
|
title: 'Components/Utilities/Visible',
|
||||||
component: Visible,
|
component: Visible,
|
||||||
...withComponentDocs(componentDocs),
|
});
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<Story name="Demo" tags={['!autodocs', '!dev']}>
|
||||||
import { Template, Story } from '@storybook/addon-svelte-csf';
|
<Visible>
|
||||||
</script>
|
{#snippet children(visible)}
|
||||||
|
{#if visible}
|
||||||
<Template>
|
<p>Visible!</p>
|
||||||
{#snippet children({ args })}
|
{:else}
|
||||||
<Visible {...args}>
|
<p>Not yet visible.</p>
|
||||||
{#snippet children({ visible })}
|
{/if}
|
||||||
{#if visible}
|
{/snippet}
|
||||||
<p>Visible!</p>
|
</Visible>
|
||||||
{:else}
|
</Story>
|
||||||
<p>Not yet visible.</p>
|
|
||||||
{/if}
|
|
||||||
{/snippet}
|
|
||||||
</Visible>
|
|
||||||
{/snippet}
|
|
||||||
</Template>
|
|
||||||
|
|
||||||
<Story name="Default" {...withSource({ svelte: defaultSnippet })} />
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<!-- @component `Visible` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-utilities-visible--docs) -->
|
<!-- @component `Visible` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-utilities-visible--docs) -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount, type Snippet } from 'svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/**
|
/**
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
right?: number;
|
right?: number;
|
||||||
/** Set the Intersection Observer [threshold](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#threshold). */
|
/** Set the Intersection Observer [threshold](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#threshold). */
|
||||||
threshold?: number;
|
threshold?: number;
|
||||||
children?: import('svelte').Snippet<[any]>;
|
children?: Snippet<[boolean]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|
@ -33,15 +33,16 @@
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let visible = $state(false);
|
let visible = $state(false);
|
||||||
let container: HTMLElement = $state();
|
let container: HTMLElement | undefined = $state(undefined);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (typeof IntersectionObserver !== 'undefined') {
|
if (typeof IntersectionObserver !== 'undefined') {
|
||||||
const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`;
|
const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`;
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
visible = entries[0].isIntersecting;
|
visible = entries[0].isIntersecting;
|
||||||
if (visible && once) {
|
if (visible && once && container) {
|
||||||
observer.unobserve(container);
|
observer.unobserve(container);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -50,16 +51,20 @@
|
||||||
threshold,
|
threshold,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
observer.observe(container);
|
if (container) observer.observe(container);
|
||||||
return () => observer.unobserve(container);
|
return () => {
|
||||||
|
if (container) observer.observe(container);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
function handler() {
|
function handler() {
|
||||||
const bcr = container.getBoundingClientRect();
|
if (container) {
|
||||||
visible =
|
const bcr = container.getBoundingClientRect();
|
||||||
bcr.bottom + bottom > 0 &&
|
visible =
|
||||||
bcr.right + right > 0 &&
|
bcr.bottom + bottom > 0 &&
|
||||||
bcr.top - top < window.innerHeight &&
|
bcr.right + right > 0 &&
|
||||||
bcr.left - left < window.innerWidth;
|
bcr.top - top < window.innerHeight &&
|
||||||
|
bcr.left - left < window.innerWidth;
|
||||||
|
}
|
||||||
if (visible && once) {
|
if (visible && once) {
|
||||||
window.removeEventListener('scroll', handler);
|
window.removeEventListener('scroll', handler);
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +75,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={container}>
|
<div bind:this={container}>
|
||||||
<!-- An element or component -->
|
{#if children}
|
||||||
{@render children?.({ visible })}
|
{@render children(visible)}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
Wrap components or other HTML elements to determine if they are visible on the page using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
|
|
||||||
|
|
||||||
This is really useful for lazy loading elements, especially expensive media files or components that fetch lots of data. You can also use it trigger animations or play media once a reader scrolls a component into view.
|
|
||||||
|
|
||||||
> **Pro tip:** Don't use this for content that's "above the fold" at the top of the page. That'll just slow down the first load of important visible content.
|
|
||||||
|
|
||||||
```svelte
|
|
||||||
<script>
|
|
||||||
import { Visible } from '@reuters-graphics/graphics-components';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Visible let:visible>
|
|
||||||
{#if visible}
|
|
||||||
<p>Visible!</p>
|
|
||||||
{:else}
|
|
||||||
<p>Not yet visible.</p>
|
|
||||||
{/if}
|
|
||||||
</Visible>
|
|
||||||
```
|
|
||||||
Loading…
Reference in a new issue