pinch-type
v0.2.6
Published
Pinch to zoom text, not the page. Canvas-based text scaling for mobile web.
Maintainers
Readme
pinch-type
Three canvas-based text effects for mobile web, built on @chenglou/pretext.
- Pinch Type — intercepts pinch-to-zoom and scales text size instead of zooming the page
- Scroll Morph — fisheye effect: text near the viewport center is large and bright, edges are small and dim
- Combined — both effects together
Install
npm install pinch-type @chenglou/pretextQuick Start
import { createPinchType, createScrollMorph, createPinchMorph } from 'pinch-type';
// 1. Pinch Type — uniform text, pinch gestures scale all text
const pt = createPinchType(document.getElementById('reader'));
pt.setText('Your long article text here…');
// 2. Scroll Morph — fisheye effect, no pinch zoom
const sm = createScrollMorph(document.getElementById('reader'));
sm.setText('Your long article text here…');
// 3. Combined — fisheye + pinch-to-zoom (the original behavior)
const pm = createPinchMorph(document.getElementById('reader'));
pm.setText('Your long article text here…');
// Clean up when done:
instance.destroy();The container element should have a defined width and height (e.g. 100vw × 100vh). Each function creates a fullscreen <canvas> inside it.
API
createPinchType(element, options?)
Uniform text rendering with pinch-to-zoom scaling.
| Option | Type | Default | Description |
|---|---|---|---|
| fontSize | number | 18 | Base font size |
| minFontSize | number | 8 | Smallest size reachable via pinch |
| maxFontSize | number | 60 | Largest size reachable via pinch |
| fontFamily | string | "Inter", system-ui, sans-serif | CSS font-family |
| lineHeight | number | 1.57 | Line-height ratio |
| padding | number | 28 | Content padding (px) |
| background | string | #0a0a0a | Canvas background color |
| friction | number | 0.95 | Scroll momentum friction (0–1) |
| onZoom | (fontSize) => void | — | Callback after each pinch zoom |
createScrollMorph(element, options?)
Fisheye scroll effect. No pinch-to-zoom.
| Option | Type | Default | Description |
|---|---|---|---|
| centerFontSize | number | 26 | Font size at viewport center |
| edgeFontSize | number | 11 | Font size at viewport edges |
| morphRadius | number | 300 | Radius (px) of the center→edge gradient |
| fontFamily | string | "Inter", system-ui, sans-serif | CSS font-family |
| lineHeight | number | 1.57 | Line-height ratio |
| padding | number | 28 | Content padding (px) |
| background | string | #0a0a0a | Canvas background color |
| friction | number | 0.95 | Scroll momentum friction (0–1) |
createPinchMorph(element, options?)
Combined: fisheye scroll effect + pinch-to-zoom.
| Option | Type | Default | Description |
|---|---|---|---|
| centerFontSize | number | 26 | Font size at viewport center |
| edgeFontSize | number | 11 | Font size at viewport edges |
| minFontSize | number | 8 | Smallest size reachable via pinch |
| maxFontSize | number | 60 | Largest size reachable via pinch |
| morphRadius | number | 300 | Radius (px) of the center→edge gradient |
| fontFamily | string | "Inter", system-ui, sans-serif | CSS font-family |
| lineHeight | number | 1.57 | Line-height ratio |
| padding | number | 28 | Content padding (px) |
| background | string | #0a0a0a | Canvas background color |
| friction | number | 0.95 | Scroll momentum friction (0–1) |
| onZoom | (center, edge) => void | — | Callback after each pinch zoom |
Instance Methods (all three)
| Method | Description |
|---|---|
| setText(text) | Update displayed text and re-layout |
| resize() | Force re-layout (auto-called on window resize) |
| destroy() | Remove canvas, listeners, and animation loop |
| canvas | The underlying <canvas> element (read-only) |
Lightweight Mode
Just want pinch-to-zoom on your existing page? Use the lightweight API — no canvas, no dependencies, ~1KB. Want the full canvas rendering experience? Use createPinchType / createScrollMorph / createPinchMorph above.
pinchZoom(options?) — Vanilla JS
import { pinchZoom } from 'pinch-type';
const cleanup = pinchZoom({
target: document.getElementById('article'), // default: document.documentElement
min: 12, // min font size, default 12
max: 32, // max font size, default 32
initial: 16, // starting size, default 16
step: 1, // px per zoom step, default 1
onZoom: (size) => console.log(size),
});
// Later: remove all listeners
cleanup();Detects two-finger touch pinch and trackpad pinch (ctrl+wheel / meta+wheel). Single-finger scroll is unaffected. Applies font-size directly to the target element.
usePinchZoom(options?) — React Hook
Requires react as a peer dependency.
import { usePinchZoom } from 'pinch-type';
function Reader() {
const { fontSize, ref } = usePinchZoom({ min: 12, max: 32 });
return <article ref={ref} style={{ fontSize }}>...</article>;
}| Option | Type | Default | Description |
|---|---|---|---|
| min | number | 12 | Minimum font size |
| max | number | 32 | Maximum font size |
| initial | number | 16 | Starting font size |
| step | number | 1 | Pixels per zoom step |
| onZoom | (size) => void | — | Callback after each zoom |
How It Works
Text is measured and wrapped using @chenglou/pretext for accurate segment-aware line breaking. Each frame, lines are drawn to a canvas. For scroll morph, font size and opacity are interpolated based on distance from the viewport center (ease-out cubic). Touch events drive momentum scrolling with configurable friction, and two-finger pinch gestures scale the font size range in real time.
License
MIT — Lucas Crespo
