Skip to content

With Target Observer, you can easily trigger animations, lazy load content, or handle visibility-based logic in your application.

License

Notifications You must be signed in to change notification settings

rawjson/target-observer

Repository files navigation

Target Observer

Table of Contents

Target Observer is designed to help developers efficiently monitor and manage the visibility of elements within the viewport.

It provides a set of tools including the useInView hook, InViewProvider, Target, and ObserveZone components. These tools enable seamless tracking of when specific elements, identified as "targets" enter or exit the viewport.

With Target Observer, you can easily trigger animations, lazy load content, or handle visibility-based logic in your application. The library is optimized for performance and ease of use, making it a robust solution for building responsive and dynamic user interfaces.

Checkout the demo here.

Component API

InViewProvider

The InViewProvider component is a wrapper for tracking the visibility of multiple Target components within the viewport. The component monitors when each target enters or exits the viewport, enabling you to manage and respond to the in-view state of these elements within your application.

Property Default Description
targetIds - An array of strings that specifies the IDs of the Target components to be tracked by the InViewProvider.
firstTargetActiveOnMount true By default, the first target is activated when the component mounts. If set to false, it will only update once the user starts scrolling and the target enters the viewport.

useInView

The useInView hook tracks when a target element enters the viewport, making it ideal for dynamically updating the styling of items or chapters in a table of contents on a documentation site.

ObserveZone

The ObserveZone is an invisible component that defines a specific area within which visibility of child elements is tracked.

Property Default Description
height 50vh An optional property for adjusting the observing area's height.
className - An optional property for defining class names. Use only for testing the component.

Target

The Target component represents an element whose visibility is tracked within the ObserveZone. Each Target should have a unique ID that matches an entry in the targetIds array provided to the InViewProvider.

The component’s visibility state can be used to trigger actions, such as animations or lazy loading, based on whether the target is in view or out of view.

Property Default Description
id - The id of the component to track and trigger state updates.
as div The element or component the Target should render as.
entryThreshold 0.5 The percentage of observing zone height that should be used to trigger the observer. The target element when inside this area will update the inView property to true. Accepts value between 0 and 1. Min is 0.1 and Max is 0.9.

Examples:

React with CSS

import clsx from 'clsx'
import { useInView, InViewProvider, ObserveZone, Target } from 'target-observer'

const targetIds = [
    'section#1',
    'section#2',
    'section#3',
    'section#4',
    'section#5'
]
export default function Example() {
    return (
        <InViewProvider targetIds={targetIds}>
            <div className='container'>
                <Navigation />

                {/* must use position:relative in parent for ObserveZone to work */}
                <div className='target-wrapper'>
                    {/* add this invisible component to track the target */}
                    <ObserveZone
                        // optional height property, default is 50vh
                        height='70vh'
                        // optional className property, use only for testing
                        className='observer'
                    />

                    {targetIds.map((targetId) => (
                        <Target
                            key={targetId}
                            // must specify the id property for target to work
                            id={targetId}
                            // the html element you want to render
                            as='section'
                            // add styling to your target
                            className='target'
                            // the height in percent of observing zone to trigger inView state
                            // default is 0.5 (50 percent)
                            entryThreshold={0.3}
                        >
                            Target: {targetId}
                        </Target>
                    ))}
                </div>
            </div>
        </InViewProvider>
    )
}

function Navigation() {
    const inView = useInView() // returns a record with boolean values

    return (
        <nav>
            <ul>
                {targetIds.map((targetId) => {
                    return (
                        <li
                            key={targetId}
                            className={clsx({
                                'list-active': inView[targetId]
                            })}
                        >
                            <a href={'#' + targetId}>{targetId}</a>
                        </li>
                    )
                })}
            </ul>
        </nav>
    )
}
nav {
    position: sticky;
    top: 2.5rem;
    height: 100vh;
}

ul {
    list-style-type: none;
}

li {
    width: 8rem;
    text-align: center;
    color: black;
}

a {
    text-decoration: none;
    font-size: 18px;
    color: inherit;
}

li:not(:first-child):not(:last-child),
section:not(:first-child):not(:last-child) {
    margin-top: 16px;
    margin-bottom: 16px;
}

.list-active {
    font-weight: 700;
    padding: 0.25rem 0 0.25rem 0;
    background-color: #ef4444;
    color: white;
}

.container {
    max-width: 80rem;
    width: 100%;
    margin-left: auto;
    margin-right: auto;
    padding: 1.25rem;
    display: flex;
    gap: 2.5rem;
}

.target-wrapper {
    position: relative;
    width: 100%;
}

.target {
    height: 90vh;
    border: 4px solid #e5e7eb;
    padding: 1.25rem;
}

.observer {
    outline: 4px solid rgba(239, 68, 68, 0.2);
    outline-offset: 8px;
}

React with Tailwind CSS

import clsx from 'clsx'
import { useInView, InViewProvider, ObserveZone, Target } from 'target-observer'

const targetIds = [
    'section#1',
    'section#2',
    'section#3',
    'section#4',
    'section#5'
]
export default function Example() {
    return (
        <InViewProvider targetIds={targetIds}>
            <div className='h-screen w-full'>
                <div className='max-w-7xl mx-auto w-full p-5 flex gap-10'>
                    <Navigation />
                    {/* must use position:relative in parent for ObserveZone to work */}
                    <div className='relative w-full space-y-5'>
                        {/* add this invisible component to track the target */}
                        <ObserveZone
                            // optional height property, default is 50vh
                            height='70vh'
                            // optional className property, use only for testing
                            className='ring-4 ring-offset-8 ring-red-500/20'
                        />

                        {targetIds.map((targetId) => (
                            <Target
                                key={targetId}
                                // must specify the id property for target to work
                                id={targetId}
                                // the html element you want to render
                                as='section'
                                // add styling to your target
                                className='h-[90vh] border-4 p-5 w-full'
                                // the height in percent of observing zone to trigger inView state
                                // default is 0.5 (50 percent)
                                entryThreshold={0.3}
                            >
                                Target: {targetId}
                            </Target>
                        ))}
                    </div>
                </div>
            </div>
        </InViewProvider>
    )
}

function Navigation() {
    const inView = useInView() // returns a record with boolean values
    return (
        <div className='sticky top-10 h-screen'>
            <ul className='space-y-4'>
                {targetIds.map((targetId) => (
                    <li
                        key={targetId}
                        className={clsx('text-center w-32', {
                            'font-bold py-1 bg-red-500 text-white':
                                inView[targetId]
                        })}
                    >
                        <a href={'#' + targetId}>{targetId}</a>
                    </li>
                ))}
            </ul>
        </div>
    )
}

Issues

Please file an issue for bugs, missing documentation, or unexpected behavior. Don't hesitate to make a PR for any issue.

File an issue

About

With Target Observer, you can easily trigger animations, lazy load content, or handle visibility-based logic in your application.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published