npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@dexq/svelte-scrollspy

v0.0.1

Published

Svelte store to tracks the scroll position of elements registered using a provided Svelte action

Downloads

4

Readme

Svelte ScrollSpy

Svelte ScrollSpy is a Svelte store that tracks the intersecting state of a set of elements. The store provides a Svelte action that allows you to easily register any element for tracking.

Installation

NPM

npm i -D svelte-scrollspy

Yarn

yarn add -D svelte-scrollspy

bun

bun i -d svelte-scrollspy

Why Svelte ScrollSpy?

Although there are many intersection observer libraries out there, this library leverages the power of Svelte actions to provide a simple and intuitive API. Here are some of the benefits of using this library in a Svelte project.

  • Have a cleaner DOM without needing to add wrapper elements.
  • Easily register any elements, even nested elements, for tracking.
  • No need to worry about cleaning up when elements being tracked are removed from the DOM. Callbacks in Svelte action automatically do that.

API

The object stored in the ScrollSpy store has the following properties:

| Property | Description | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | amount | The number of targets being spied on. | | targets | The ordered set of all targets being spied on, in the order of them being added as a target. | | activeTargets | The ordered set of all intersecting targets, in the order of entering intersection. | | activeTarget | The target that became active most recently and is still active. I.e. the last item in activeTargets. | | lastActiveTarget | The target that became active most recently. It may or may not be active now. If this target is no longer being spied on, this value is null. | | activeId | The id of activeTarget. | | lastActiveId | The id of lastActiveTarget. | | isActive | A function that checks if an element is active. Returns null if the given element is not a target being spied on. |

The ScrollSpy store also has the following methods besides the subscribe method of a Svelte store.

| Method | Description | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | spy | A svelte action to add an element to the target list and start spying on it. | | unspy | Stop spying on a target and remove it from the target list. Accepts either the element itself or the id as the argument. This method is automatically called on all registered targets after they are removed from the DOM. | | unspyAll | Stop spying on all existing targets and remove them from the target list. |

Usage

Basic

To use ScrollSpy to track the current section in view on the page. First create a scroll spy store with the imported createScrollSpy function. You can pass in an IntersectionObserverInit object to configure the IntersectionObserver used for tracking.

In the example, we set the rootMargin to -50% 0px so that a section starts intersecting when it touches the vertical center of the viewport.

import createScrollSpy from "svelte-scrollspy";
export const scrollSpy = createScrollSpy({ rootMargin: "-50% 0px" });

Then in any component file you can import the created store and use its spy method as a Svelte action to start spying on that element.

<script>
  import scrollSpy from "$lib/stores/scroll-spy";
</script>

<section id="my-section" use:scrollSpy.spy><!-- ... --></section>
<!-- Other sections... -->

Element id is not required for spying. We gave an id to the element here only for scrolling with URL hash. Now, it is easy to make a navbar that highlights the current section.

<script>
  import scrollSpy from "$lib/stores/scroll-spy";
  import kebabCaseToCapWords from "$lib/utils.js";
</script>

<nav>
  <ul>
    {#each $scrollSpy.targets as section (section)}
      <li class={$scrollSpy.lastActiveTarget === section ? "active" : ""}>
        <a href={"#" + section.id}>{kebabCaseToCapWords(section.id)}</a>
      </li>
    {/each}
  </ul>
</nav>

<style>
  /* Styling... */
</style>

Restricting What Can Be Spied On

You can augment Scroll Spy with additional properties and methods. For example, we can enforce that all elements being spied on must have an ID, and we can assign an arbitrary label to each element when they are registered.

To do this, we create our custom Svelte store by extending the functionality of Scroll Spy.

import createScrollSpy from 'svelte-scrollspy';
import type { ActionReturn } from 'svelte/action';

export const sectionSpy = (() => {
  // use spy store's functionality
  const spy = createScrollSpy({ rootMargin: '-50% 0px' });

  return {
    ...spy,

    // Overwrite spy() to add restriction on targets and apply custom attribute

    /**
     * A svelte action to register an element as a section to spy on. The
     * element must have an id.
     *
     * @param [label] - an arbitrary label for the section. This action assigns
     *   the label value to the element's `data-section-label` attribute. If no
     *   label is given, the element's id is transformed into a capitalized
     *   string and used as the label.
     */
    spy(
      target: string | Element,
      label?: string,
    ): ActionReturn<string, { id: string }> {
      const elem =
        target instanceof Element ? target : document.getElementById(target);

      if (!elem || !elem.id) return {};

      label ??= elem.id // kebab-case to Capitalized Words
        .replace(/-./g, (m) => " " + m[1].toUpperCase())
        .replace(/^(.)/, (m) => m.toUpperCase());

      const { destroy } = spy.spy(elem);
      if (destroy) elem.setAttribute("data-section-label", label);
      return { destroy };
    },
  };
})();

We can then use our custom store to assign labels with Svelte action syntax.

<section id="my-section" use:sectionSpy.spy={"My Label"}><!-- ... --></section>
<!-- Other sections... -->
<footer>You're at the section: {$sectionSpy.activeLabel}</footer>

Adding Custom Properties to the Store

You can even add custom properties to the store.

export const sectionSpy = (() => {
  const spy = createSpy({ rootMargin: "-50% 0px" });

  // extend spy store's properties
  interface SectionSpy extends Spy {
    /** The label of the active target (attribute: "data-section-label") */
    activeLabel: string | null;

    /**
     * The label of the last active section (attribute: "data-section-label")
     */
    lastActiveLabel: string | null;
  }

  function getSectionSpy(): SectionSpy {
    return {
      ...get(spy),
      get activeLabel() {
        return this.activeTarget?.getAttribute("data-section-label") ?? null;
      },
      get lastActiveLabel() {
        return (
          this.lastActiveTarget?.getAttribute("data-section-label") ?? null
        );
      },
    };
  }

  // Create our own custom store
  const { subscribe, set } = writable<SectionSpy>(getSectionSpy());
  // Update our custom store whenever the spy store updates
  spy.subscribe(() => set(getSectionSpy()));

  return {
    ...spy,
    subscribe,

    // Implement our own spy() method to add restriction on spied targets and
    // add custom attribute to the targets
    spy(
      target: string | Element,
      label?: string,
    ): ActionReturn<string, { id: string }> {
      const elem =
        target instanceof Element ? target : document.getElementById(target);

      if (!elem || !elem.id) return {};

      label ??= elem.id // kebab-case to Capitalized Words
        .replace(/-./g, (m) => " " + m[1].toUpperCase())
        .replace(/^(.)/, (m) => m.toUpperCase());

      const { destroy } = spy.spy(elem);
      if (destroy) elem.setAttribute("data-section-label", label);
      return { destroy };
    },
  };
})();

License

MIT