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/pretext
Quick 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