Skip to content

Commit

Permalink
feat: IsInViewport (#181)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte authored Dec 18, 2024
1 parent f21a320 commit e9f1090
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-candles-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"runed": minor
---

feat: `IsInViewport`
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { MaybeGetter } from "$lib/internal/types.js";
import {
useIntersectionObserver,
type UseIntersectionObserverOptions,
} from "../useIntersectionObserver/useIntersectionObserver.svelte.js";

export type IsInViewportOptions = UseIntersectionObserverOptions;

/**
* Tracks if an element is visible within the current viewport.
*
* @see {@link https://runed.dev/docs/utilities/is-in-viewport}
*/
export class IsInViewport {
#isInViewport = $state(false);

constructor(node: MaybeGetter<HTMLElement | null | undefined>, options?: IsInViewportOptions) {
useIntersectionObserver(
node,
(intersectionObserverEntries) => {
let isIntersecting = this.#isInViewport;
let latestTime = 0;
for (const entry of intersectionObserverEntries) {
if (entry.time >= latestTime) {
latestTime = entry.time;
isIntersecting = entry.isIntersecting;
}
}
this.#isInViewport = isIntersecting;
},
options
);
}

get current() {
return this.#isInViewport;
}
}
1 change: 1 addition & 0 deletions packages/runed/src/lib/utilities/IsInViewport/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./IsInViewport.svelte.js";
1 change: 1 addition & 0 deletions packages/runed/src/lib/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export * from "./FiniteStateMachine/index.js";
export * from "./PersistedState/index.js";
export * from "./useGeolocation/index.js";
export * from "./Context/index.js";
export * from "./IsInViewport/index.js";
49 changes: 49 additions & 0 deletions sites/docs/src/content/utilities/is-in-viewport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
title: IsInViewport
description: N/A
category: Utilities
---

<script>
import Demo from '$lib/components/demos/is-in-viewport.svelte';
</script>

`IsInViewport` uses the [`useIntersectionObserver`](/docs/utilities/use-intersection-observer)
utility to track if an element is visible within the current viewport.

It accepts an element or getter that returns an element and an optional `options` object that aligns
with the [`useIntersectionObserver`](/docs/utilities/use-intersection-observer) utility options.

## Demo

<Demo />

## Usage

```svelte
<script lang="ts">
import { IsInViewport } from "runed";
let targetNode = $state<HTMLElement>()!;
const inViewport = new IsInViewport(() => targetNode);
</script>
<p bind:this={targetNode}>Target node</p>
<p>Target node in viewport: {inViewport.current}</p>
```

## Type Definition

```ts
import { type UseIntersectionObserverOptions } from "runed";
export type IsInViewportOptions = UseIntersectionObserverOptions;

export declare class IsInViewport {
constructor(node: MaybeGetter<HTMLElement | null | undefined>, options?: IsInViewportOptions);
get current(): boolean;
}
```

<!-- Ensure the page can scroll so the target can be outside of the viewport -->
<div class="h-80"></div>
26 changes: 26 additions & 0 deletions sites/docs/src/lib/components/demos/is-in-viewport.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script lang="ts">
import { IsInViewport } from "runed";
import { DemoContainer } from "@svecodocs/kit";
let targetNode = $state<HTMLElement>()!;
const inViewport = new IsInViewport(() => targetNode);
</script>

<DemoContainer>
<p bind:this={targetNode}>Target node</p>
<p class="text-muted-foreground text-sm italic">Scroll down to observe the behavior</p>
</DemoContainer>

<div
class="bg-background fixed bottom-8 right-8 z-20 flex items-center rounded-lg border p-4 text-sm"
>
<p>
Target node is <span
class="font-medium text-red-500 data-[in-viewport]:text-green-500"
data-in-viewport={inViewport.current ? "" : undefined}
>
{inViewport.current ? " in " : " out of "}
</span>
viewport
</p>
</div>

0 comments on commit e9f1090

Please sign in to comment.