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 🙏

© 2026 – Pkg Stats / Ryan Hefner

drifttype

v0.1.1

Published

Spatial text layouts for the web. Text on paths and pointer-reactive typography with zero dependencies.

Readme

drifttype

Spatial text layouts for the web. Text that follows paths and responds to pointer input.

Zero dependencies. Accessibility built in.

Live demo

Repel

repel

Attract

attract

Orbit

orbit

Path layout

Text flows along an SVG bezier path. Each character is positioned and rotated to follow the curve tangent.

import { computePathLayout, createCanvasMeasure, renderToSvg, mountSemantic } from 'drifttype'

const measure = createCanvasMeasure('24px Georgia')
const layout = computePathLayout('Hello world', 'M 0 100 Q 150 0 300 100', measure)

renderToSvg(svgElement, layout, { font: '24px Georgia' })
mountSemantic(container, 'Hello world')

The layout engine is pure — it takes a string, a path, and a measurement function, and returns positioned glyphs. No DOM access, no side effects. Rendering is separate.

Pointer-reactive layout

Characters repel, attract, or orbit the cursor. Spring physics with inertia.

import { initPointerLayout, stepPointerLayout, isAtRest } from 'drifttype'

const measure = createCanvasMeasure('24px Georgia')
let state = initPointerLayout('Hello world', measure, 'repel')

// in your animation loop
state = stepPointerLayout(state, { x: mouseX, y: mouseY }, 120, 0.6, dt)

Three modes: repel, attract, orbit. Pass null as the pointer to let characters settle back.

React

import { PathText, PointerText } from 'drifttype/react'

<PathText path="M 0 100 Q 150 0 300 100" font="24px Georgia">
  Hello world
</PathText>

<PointerText mode="repel" font="24px Georgia">
  Hello world
</PointerText>

Canvas renderer

import { renderPathToCanvas, renderPointerToCanvas } from 'drifttype'

renderPathToCanvas(ctx, layout, { font: '24px Georgia' })
renderPointerToCanvas(ctx, pointerState, { font: '24px Georgia' })

Accessibility

Every visual output should have a semantic counterpart. mountSemantic creates a visually-hidden DOM node with the text content so screen readers can access it. The SVG renderer marks visual output aria-hidden="true" automatically.

const handle = mountSemantic(container, 'Hello world')
handle.update('New text')   // update content
handle.destroy()            // clean up

The React components handle this for you.

How it works

The layout engine parses SVG path data into bezier segments, builds an arc-length lookup table for each segment, then places characters at evenly-spaced distances along the curve. Character widths come from an injected measurement function — the engine itself never touches the DOM.

The pointer-reactive engine runs spring physics per character. Forces are computed from the character's base position relative to the pointer, with quadratic falloff inside a configurable radius. The animation loop is frame-rate independent.

API

computePathLayout(text, pathData, measure, spacing?, startOffset?, align?)

Returns a PathLayoutResult with positioned glyphs.

| Param | Type | Default | | |---|---|---|---| | text | string | | The text to lay out | | pathData | string | | SVG path data | | measure | MeasureFn | | Character measurement function | | spacing | number | 1 | Letter-spacing multiplier | | startOffset | number | 0 | Arc-length offset to start | | align | 'start' \| 'center' \| 'end' | 'start' | Text alignment along path |

initPointerLayout(text, measure, mode, spacing?)

Returns initial PointerLayoutState.

stepPointerLayout(state, pointer, radius, strength, dt?)

Advances the physics simulation by one frame. Returns new state.

createCanvasMeasure(font)

Creates a MeasureFn from a CSS font string using an offscreen canvas. Results are cached per character.

parsePath(d)

Parses an SVG path data string into a ParsedPath. Supports M, L, H, V, C, Q, S, T, Z (absolute and relative).

Install

npm install drifttype

React components require react >= 18 as a peer dependency.

Development

npm test

License

MIT