leafthrough 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -25
- package/dist/LeafThroughObject.d.ts +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ Vue, Svelte, and a Web Component for everything else (Angular, Solid, Lit, plain
|
|
|
6
6
|
can also be [one object in your own three.js scene](#in-your-own-threejs-scene).
|
|
7
7
|
|
|
8
8
|
**[Live demo](https://dmtrbrl.github.io/leafthrough/)**, with one page per binding and a dev panel
|
|
9
|
-
for every option, method and event
|
|
9
|
+
for every option, method and event, and
|
|
10
|
+
[a three.js scene](https://dmtrbrl.github.io/leafthrough/three.html) with the magazine in it.
|
|
10
11
|
|
|
11
12
|
- **Two layouts.** `spread` is an open magazine with sheets turning over the spine.
|
|
12
13
|
`single` shows one page at a time, and read pages roll up behind the magazine. `auto`
|
|
@@ -139,19 +140,19 @@ error trackers listen. To register the element under another tag, call
|
|
|
139
140
|
|
|
140
141
|
## In your own three.js scene
|
|
141
142
|
|
|
142
|
-
`LeafThroughObject` is the magazine as a `THREE.Group`, for a scene you already have
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
`
|
|
143
|
+
`LeafThroughObject` is the magazine as a `THREE.Group`, for a scene you already have. It
|
|
144
|
+
draws with your renderer and takes pointer input through your camera, so it turns wherever
|
|
145
|
+
it lies, however it's moved, turned or scaled. Every option, method, property and event of
|
|
146
|
+
`LeafThrough` works the same, apart from those about its own canvas and DOM: the options
|
|
147
|
+
`bleed`, `maxPixelRatio`, `plain`, `label` and `formatPageLabel`, and the properties
|
|
148
|
+
`plain` and `element`.
|
|
148
149
|
|
|
149
150
|
```ts
|
|
150
151
|
import { LeafThroughObject } from 'leafthrough'
|
|
151
152
|
|
|
152
|
-
const magazine = new LeafThroughObject({ renderer, camera }, { pages, layout: 'spread'
|
|
153
|
+
const magazine = new LeafThroughObject({ renderer, camera }, { pages, layout: 'spread' })
|
|
153
154
|
magazine.rotation.x = -Math.PI / 2 // lay it on the table
|
|
154
|
-
magazine.scale.setScalar(0.
|
|
155
|
+
magazine.scale.setScalar(0.21) // a page 21 cm wide, in a scene measured in metres
|
|
155
156
|
scene.add(magazine)
|
|
156
157
|
|
|
157
158
|
renderer.setAnimationLoop((now) => {
|
|
@@ -161,26 +162,38 @@ renderer.setAnimationLoop((now) => {
|
|
|
161
162
|
```
|
|
162
163
|
|
|
163
164
|
- **Units.** A page is 1 wide and `pageAspect` tall. The spine runs up the y axis at
|
|
164
|
-
x = 0 and the pages lie on the z = 0 plane, facing +z. A closed
|
|
165
|
-
at x 0 to 1
|
|
166
|
-
|
|
165
|
+
x = 0 and the pages lie on the z = 0 plane, facing +z. A spread closed on its front
|
|
166
|
+
cover shows it at x 0 to 1, and closed on its back cover at x -1 to 0.
|
|
167
|
+
`magazine.bounds.pages` is the pages at rest, the rectangle to frame with your camera;
|
|
168
|
+
`magazine.bounds.reach` is everything a turning page sweeps through.
|
|
167
169
|
- **Input.** The magazine listens for pointer events on `renderer.domElement` (or the
|
|
168
|
-
`domElement` you pass
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
`domElement` you pass, which must show exactly the camera's view). It takes presses in
|
|
171
|
+
its hit area: the pages in `spread` (only the cover's side while closed), and in
|
|
172
|
+
`single` a box round the page with a strip left of the spine for turning back. It
|
|
173
|
+
doesn't look at your scene, so objects in front of the magazine don't block it.
|
|
174
|
+
`magazine.covers(clientX, clientY)` tells you whether it takes a press there; check that
|
|
175
|
+
in your own handlers to leave such presses alone. Hide the magazine (`visible = false`,
|
|
176
|
+
on it or a parent) and it takes none. With `interaction.keyboard: true` the keys work
|
|
177
|
+
while that element has focus; a canvas only takes focus with a `tabIndex`, so give it
|
|
178
|
+
one or use `'global'`. Drags scale with the magazine's size on screen, so they feel the
|
|
179
|
+
same near or far. For touch drags, give the element `touch-action: pan-y pinch-zoom`
|
|
180
|
+
(or `none`), so the browser doesn't take them for scrolling.
|
|
173
181
|
- **Light.** The paper is a `MeshStandardMaterial`, lit by your scene's lights,
|
|
174
|
-
environment, fog and tone mapping. Of the `lighting` options, `
|
|
175
|
-
|
|
176
|
-
|
|
182
|
+
environment, fog and tone mapping. Of the `lighting` options, `shadows` makes the
|
|
183
|
+
sheets cast and receive shadows when your renderer draws them, and `sun.position`
|
|
184
|
+
should point towards your shadow-casting directional light, in the magazine's own
|
|
185
|
+
frame (only the direction counts). It places the shadow a turning sheet casts right by
|
|
186
|
+
the spine, where the shadow map can't.
|
|
177
187
|
- **Rendering on demand.** If you only draw when something changes, listen for `change`:
|
|
178
188
|
it fires when input, loading or a method call needs a frame. Then keep drawing while
|
|
179
189
|
`tick()` returns true.
|
|
190
|
+
- **Accessibility is yours.** With no DOM of its own, it has no live region, no page
|
|
191
|
+
descriptions and no plain view: pages' `alt` and `text` aren't read. Describe the pages
|
|
192
|
+
in your own page, and announce `pagechange` if readers should hear it.
|
|
180
193
|
|
|
181
194
|
```ts
|
|
182
195
|
magazine.tick(now) // advance; true while the pages still move
|
|
183
|
-
magazine.covers(e.clientX, e.clientY) // whether a press there
|
|
196
|
+
magazine.covers(e.clientX, e.clientY) // whether the magazine takes a press there
|
|
184
197
|
magazine.bounds // { pages, reach }, in the magazine's own units
|
|
185
198
|
magazine.camera = otherCamera // cast input from another camera
|
|
186
199
|
magazine.enabled = false // cancel active gestures and stop taking pointer/key input
|
|
@@ -191,8 +204,9 @@ magazine.dispose() // free everything and leave the scene
|
|
|
191
204
|
|
|
192
205
|
The [three.js demo](https://dmtrbrl.github.io/leafthrough/three.html) takes a magazine
|
|
193
206
|
out of a portrait paperboard mailer on a table; its source is
|
|
194
|
-
[`demo/main-three.ts`](demo/main-three.ts),
|
|
195
|
-
|
|
207
|
+
[`demo/main-three.ts`](https://github.com/dmtrbrl/leafthrough/blob/main/demo/main-three.ts),
|
|
208
|
+
with the native three.js mailer geometry in
|
|
209
|
+
[`demo/envelope.ts`](https://github.com/dmtrbrl/leafthrough/blob/main/demo/envelope.ts).
|
|
196
210
|
|
|
197
211
|
## Sizing
|
|
198
212
|
|
|
@@ -352,7 +366,8 @@ off screen or the tab is hidden.
|
|
|
352
366
|
The 3D canvas is hidden from screen readers, so give pages an `alt` and their `text` (see
|
|
353
367
|
[Pages](#pages)). The magazine keeps the visible pages' descriptions and text in the page,
|
|
354
368
|
visually hidden, where screen readers can read them; each page is a `figure` named by
|
|
355
|
-
`formatPageLabel`.
|
|
369
|
+
`formatPageLabel`. `LeafThroughObject` has no DOM, so none of this comes with it (see
|
|
370
|
+
[In your own three.js scene](#in-your-own-threejs-scene)).
|
|
356
371
|
|
|
357
372
|
**Plain view.** `plain: true` shows the pages as flat images, with the same layouts,
|
|
358
373
|
navigation, keyboard and events, and no 3D. Clicking the right half turns forward, the left
|
|
@@ -403,4 +418,4 @@ The page deformation and interaction model are reverse-engineered from the magaz
|
|
|
403
418
|
|
|
404
419
|
## License
|
|
405
420
|
|
|
406
|
-
[MIT](LICENSE)
|
|
421
|
+
[MIT](https://github.com/dmtrbrl/leafthrough/blob/main/LICENSE)
|
|
@@ -4,8 +4,9 @@ import type { GoToOptions } from './LeafThrough.js';
|
|
|
4
4
|
import { type LeafThroughOptions, type Layout, type PageChangeDetail } from './options.js';
|
|
5
5
|
/**
|
|
6
6
|
* The magazine's options, less those about its own canvas and DOM. Of `lighting`, only
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
7
|
+
* `shadows` (whether sheets cast and receive them) and `sun.position` apply: the lights are
|
|
8
|
+
* your scene's. Point `sun.position` towards your shadow-casting directional light, in the
|
|
9
|
+
* magazine's own frame; it places the shadow a turning sheet casts right by the spine.
|
|
9
10
|
*/
|
|
10
11
|
export type LeafThroughObjectOptions = Omit<LeafThroughOptions, 'bleed' | 'maxPixelRatio' | 'plain' | 'label' | 'formatPageLabel'>;
|
|
11
12
|
/** What the magazine draws with, from your scene. */
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/LeafThroughObject.ts"],"sourcesContent":["import * as THREE from 'three'\nimport { sourceKey } from './content.js'\nimport { floorPoint, rayThrough, type Engine } from './engine.js'\nimport { framingFor, plainFramingFor, type Framing, type Rect } from './framing.js'\nimport type { GoToOptions } from './LeafThrough.js'\nimport { bindKeyboard, pageDetail, pagesAt, pageToPosition, Riffle } from './navigation.js'\nimport {\n changedOptions,\n DEFAULT_SPREAD_QUERY,\n LIVE_OPTIONS,\n pageImage,\n preloadRadius,\n resolveSettings,\n snapshot,\n type LeafThroughOptions,\n type Layout,\n type PageChangeDetail,\n type ResolvedSettings,\n} from './options.js'\nimport { PaperTextures } from './paper/paperTextures.js'\nimport { SingleEngine } from './single/SingleEngine.js'\nimport { SpreadEngine } from './spread/SpreadEngine.js'\nimport { PageLoader } from './textures.js'\n\n/**\n * The magazine's options, less those about its own canvas and DOM. Of `lighting`, only\n * `sun.position` (which way the paper is shaded) and `shadows` (whether sheets cast and\n * receive them) apply: the lights are your scene's.\n */\nexport type LeafThroughObjectOptions = Omit<LeafThroughOptions, 'bleed' | 'maxPixelRatio' | 'plain' | 'label' | 'formatPageLabel'>\n\n/** What the magazine draws with, from your scene. */\nexport interface LeafThroughObjectContext {\n /** The renderer drawing your scene. Pages load into it. */\n renderer: THREE.WebGLRenderer\n /** The camera your scene is drawn with. Pointer input is cast from it; change it later through `camera`. */\n camera: THREE.Camera\n /**\n * Takes pointer input, and keys with `interaction.keyboard: true` while it has focus. It must\n * show the camera's whole view. Default `renderer.domElement`.\n */\n domElement?: HTMLElement\n}\n\nexport interface LeafThroughObjectEventMap extends THREE.Object3DEventMap {\n pagechange: { detail: PageChangeDetail }\n ready: {}\n pageload: { detail: { page: number } }\n error: { detail: { error: unknown; page?: number } }\n /** Something changed between frames: draw one, and keep drawing while `tick()` returns true. */\n change: {}\n}\n\n/** Where the magazine lies in its own units. */\nexport interface LeafThroughBounds {\n /** The pages at rest: what to frame. */\n pages: Rect\n /** Everything a turning page can sweep through. */\n reach: Rect\n}\n\nconst isClient = typeof window !== 'undefined'\n\n/**\n * A 3D magazine to add to your own three.js scene. It draws nothing itself: call `tick()`\n * every frame you render, and render again whenever it fires `change`.\n *\n * In its own units a page is 1 wide and `pageAspect` tall, the spine runs up the y axis at\n * x = 0, and the pages lie on the z = 0 plane facing +z. Move, turn and scale it like any\n * object. Pointer input is cast through `camera` onto the pages, wherever they are.\n *\n * ```ts\n * const magazine = new LeafThroughObject({ renderer, camera }, { pages, layout: 'spread' })\n * scene.add(magazine)\n * renderer.setAnimationLoop((now) => {\n * magazine.tick(now)\n * renderer.render(scene, camera)\n * })\n * ```\n */\nexport class LeafThroughObject extends THREE.Group<LeafThroughObjectEventMap> {\n /** The camera your scene is drawn with; pointer input is cast from it. */\n camera: THREE.Camera\n readonly domElement: HTMLElement\n private inputEnabled = true\n\n /**\n * Whether it takes pointer and key input. Turn it off while it's tucked away or moving,\n * and on again; the pages stay as they are. Turning it off lets go of a page being held.\n * Default `true`.\n */\n get enabled() {\n return this.inputEnabled\n }\n set enabled(value: boolean) {\n if (this.inputEnabled && !value) this.view?.engine.cancelInput?.()\n this.inputEnabled = value\n }\n\n private readonly renderer: THREE.WebGLRenderer\n private options: LeafThroughObjectOptions\n private readonly paper: PaperTextures\n private loader: PageLoader | null = null\n private view: { engine: Engine; settings: ResolvedSettings; framing: Framing; dispose: () => void } | null = null\n\n private spreadMedia: { list: MediaQueryList; abort: AbortController } | null = null\n private readonly motionMedia: MediaQueryList\n private readonly abort = new AbortController()\n private readonly riffle = new Riffle()\n private readonly metrics: { readonly dragWidth: number; dragDelta(dx: number, dy: number): number }\n private readonly ray = new THREE.Ray()\n private readonly point = new THREE.Vector3()\n private readonly ends = [new THREE.Vector3(), new THREE.Vector3()]\n private needsRender = true\n private lastPagesKey = ''\n private lastDetailKey = ''\n private inView = ''\n private ready = false\n private readyFired = false\n private disposed = false\n\n constructor({ renderer, camera, domElement = renderer.domElement }: LeafThroughObjectContext, options: LeafThroughObjectOptions) {\n super()\n if (!isClient) throw new Error('LeafThrough needs a browser; create it on the client.')\n this.renderer = renderer\n this.camera = camera\n this.domElement = domElement\n this.options = snapshot(options)\n const object = this\n this.metrics = {\n get dragWidth() {\n return object.dragWidth()\n },\n dragDelta(dx, dy) {\n const axis = object.pageAxis()\n return (dx * axis.x + dy * axis.y) / (axis.length() || 1)\n },\n }\n\n this.paper = new PaperTextures(options.paper?.backColor ?? '#fcfcf9', {\n onGrain: () => this.remount(),\n onError: (error) => this.fail(error),\n })\n\n const signal = this.abort.signal\n this.watchSpreadQuery()\n this.motionMedia = window.matchMedia('(prefers-reduced-motion: reduce)')\n this.motionMedia.addEventListener('change', () => this.remount(), { signal })\n window.addEventListener('online', () => this.retry(), { signal })\n bindKeyboard(\n domElement,\n () => (this.enabled && this.shown() ? this.view?.settings.interaction.keyboard : false),\n { next: () => this.next(), prev: () => this.prev(), first: () => this.goTo(0), last: () => this.goTo(this.pageCount - 1) },\n signal,\n )\n\n this.remount(options.startPage ?? 0)\n }\n\n // --- public API -----------------------------------------------------------------\n\n /** First visible page, 0-based. */\n get page(): number {\n return this.detail().page\n }\n\n get pageCount(): number {\n return this.options.pages.length\n }\n\n get layout(): Layout {\n return this.view?.settings.layout ?? this.pickLayout()\n }\n\n get visiblePages(): number[] {\n return this.detail().visiblePages\n }\n\n /** True while a page is being dragged or is completing its turn. */\n get turning(): boolean {\n return this.view?.engine.turning ?? false\n }\n\n /**\n * Where the magazine lies in its own units: frame `pages` with your camera. Changes with\n * the layout, which `'auto'` can switch as the window resizes.\n */\n get bounds(): LeafThroughBounds {\n const layout = this.layout\n const aspect = this.view?.settings.pageAspect ?? resolveSettings(this.options, layout, false).pageAspect\n return { pages: plainFramingFor(layout, aspect).box, reach: framingFor(layout, aspect).view }\n }\n\n /** Turns to the next page. Returns false at the end. */\n next(): boolean {\n this.riffle.stop()\n return this.view?.engine.flip(true) ?? false\n }\n\n /** Turns to the previous page. Returns false at the start. */\n prev(): boolean {\n this.riffle.stop()\n return this.view?.engine.flip(false) ?? false\n }\n\n /** Shows `page` (0-based), jumping there or turning the pages one by one. */\n goTo(page: number, { animate = false }: GoToOptions = {}) {\n if (!this.view || Number.isNaN(page)) return\n this.riffle.stop()\n const engine = this.view.engine\n const target = pageToPosition(page, this.view.settings.layout, this.pageCount)\n if (!animate) {\n engine.jumpTo(target)\n return\n }\n if (engine.position === target) return\n this.riffle.start(target, this.view.settings.motion.speed)\n this.requestFrame()\n }\n\n /** Loads the pages that failed again. Also happens by itself when the browser comes back online. */\n retry() {\n this.loader?.retry()\n }\n\n /** Changes some options and keeps the rest. Structural changes rebuild the pages and keep the current page. */\n update(options: Partial<LeafThroughObjectOptions>) {\n this.applyOptions({ ...this.options, ...options })\n }\n\n /** Replaces every option at once: anything left out goes back to its default. */\n setOptions(options: LeafThroughObjectOptions) {\n this.applyOptions(options)\n }\n\n /**\n * Whether a press at this point on screen lands on the magazine, which then takes it. Check\n * it in your own pointer handlers to leave such presses alone. False while it's hidden or\n * not `enabled`.\n */\n covers(clientX: number, clientY: number): boolean {\n const view = this.view\n if (!view || !this.enabled || !this.shown()) return false\n if (!rayThrough(this.camera, this.domElement, clientX, clientY, this.ray)) return false\n const point = floorPoint(view.engine.group, this.ray, this.point)\n if (!point) return false\n // a closed spread only turns from its cover side\n const box = view.framing.box\n const side = view.engine.activeSide\n const left = side === 'right' ? box.x + box.width / 2 : box.x\n const right = side === 'left' ? box.x + box.width / 2 : box.x + box.width\n return point.x >= left && point.x <= right && point.y >= box.y && point.y <= box.y + box.height\n }\n\n /**\n * Advances the pages to `now` (a `requestAnimationFrame` timestamp). Call it every frame\n * before rendering. Returns true while the pages are moving and need another frame.\n */\n tick(now = performance.now()): boolean {\n const view = this.view\n if (!view) return false\n this.riffle.step(now, view.engine)\n const moved = view.engine.update(now)\n this.followReader(view.engine, view.settings.layout)\n const draw = moved || this.needsRender\n this.needsRender = false\n if (this.ready && !this.readyFired) {\n this.readyFired = true\n this.options.onReady?.()\n this.dispatchEvent({ type: 'ready' })\n }\n this.emitPageChange()\n return draw || this.riffle.active\n }\n\n /** Frees the pages, textures and listeners, and takes the magazine out of its parent. */\n dispose() {\n if (this.disposed) return\n this.disposed = true\n this.riffle.stop()\n this.abort.abort()\n this.spreadMedia?.abort.abort()\n this.view?.dispose()\n this.view = null\n this.loader?.dispose()\n this.paper.dispose()\n this.removeFromParent()\n }\n\n // --- options and layout -----------------------------------------------------------\n\n private applyOptions(options: LeafThroughObjectOptions) {\n if (this.disposed) return\n const previous = this.options\n const next = (this.options = snapshot(options))\n const changed = changedOptions(previous, next)\n if (!changed.length) return\n if (changed.includes('spreadQuery')) this.watchSpreadQuery()\n\n const page = this.page\n let rebuild = changed.some((key) => !LIVE_OPTIONS.has(key))\n if (changed.includes('pages') && this.pagesKey(next) !== this.lastPagesKey) {\n this.loader?.dispose()\n this.loader = null\n rebuild = true\n }\n if (changed.includes('preload')) this.loader?.setRadius(preloadRadius(this.options.preload))\n if (rebuild) this.remount(page)\n }\n\n private pickLayout(): Layout {\n const layout = this.options.layout ?? 'auto'\n if (layout !== 'auto') return layout\n return this.spreadMedia?.list.matches ? 'spread' : 'single'\n }\n\n private watchSpreadQuery() {\n this.spreadMedia?.abort.abort()\n const list = window.matchMedia(this.options.spreadQuery ?? DEFAULT_SPREAD_QUERY)\n const abort = new AbortController()\n list.addEventListener('change', () => this.remount(), { signal: abort.signal })\n this.spreadMedia = { list, abort }\n }\n\n private remount(page = this.page) {\n if (this.disposed) return\n this.riffle.stop()\n this.view?.dispose()\n\n const layout = this.pickLayout()\n const settings = resolveSettings(this.options, layout, this.motionMedia.matches)\n this.lastPagesKey = this.pagesKey(this.options)\n if (!this.loader) this.loadPages(page, layout)\n this.loader?.setWrap(layout === 'single' && settings.loop)\n\n const domElement = this.domElement\n const blank = this.paper.blank\n const ctx = {\n input: {\n element: domElement,\n ray: (x: number, y: number, ray: THREE.Ray) => rayThrough(this.camera, domElement, x, y, ray),\n covers: (x: number, y: number) => this.covers(x, y),\n },\n settings,\n textures: this.options.pages.map((_, i) => this.loader?.textures[i] ?? blank),\n blank: layout === 'single' ? this.paper.back(settings.paper.backColor) : blank,\n pattern: this.paper.grain(settings, this.renderer),\n position: pageToPosition(page, layout, this.pageCount),\n metrics: this.metrics,\n shading: {\n spineContact: { value: new THREE.Vector3(1, 0, 0) },\n lightDirection: { value: new THREE.Vector3(...settings.lighting.sun.position).normalize() },\n },\n requestFrame: () => this.requestFrame(),\n }\n const engine: Engine = layout === 'spread' ? new SpreadEngine(ctx) : new SingleEngine(ctx)\n this.add(engine.group)\n this.view = {\n engine,\n settings,\n framing: framingFor(layout, settings.pageAspect),\n dispose: () => {\n this.remove(engine.group)\n engine.dispose()\n },\n }\n this.requestFrame()\n }\n\n // A drag across this many pixels turns a page all the way: the magazine's drag width, at the\n // size it shows on screen now.\n private readonly axis = new THREE.Vector2()\n\n private pageAxis() {\n const group = this.view?.engine.group ?? this\n group.updateWorldMatrix(true, false)\n this.camera.updateWorldMatrix(true, false)\n const rect = this.domElement.getBoundingClientRect()\n const [spine, edge] = this.ends.map((end, x) => end.set(x, 0, 0).applyMatrix4(group.matrixWorld).project(this.camera))\n return this.axis.set(((edge.x - spine.x) * rect.width) / 2, -((edge.y - spine.y) * rect.height) / 2)\n }\n\n private dragWidth() {\n return Math.max(1, (this.view?.framing.dragWidth ?? 1) * this.pageAxis().length())\n }\n\n // whether this and every parent are visible\n private shown() {\n for (let object: THREE.Object3D | null = this; object; object = object.parent) if (!object.visible) return false\n return true\n }\n\n private requestFrame() {\n this.needsRender = true\n this.dispatchEvent({ type: 'change' })\n }\n\n // --- pages ---------------------------------------------------------------------\n\n // what the pages show; their alt and text don't count\n private pagesKey(options: LeafThroughObjectOptions) {\n return options.pages.map((p) => sourceKey(pageImage(p))).join('\\n')\n }\n\n private loadPages(around: number, layout: Layout) {\n this.loader?.dispose()\n const loader = new PageLoader(this.options.pages.map(pageImage), this.renderer, preloadRadius(this.options.preload), {\n onLoad: (page, texture) => {\n if (this.loader !== loader) return\n this.view?.engine.setPageTexture(page, texture)\n this.options.onPageLoad?.(page)\n this.dispatchEvent({ type: 'pageload', detail: { page } })\n },\n onUnload: (page) => {\n if (this.loader !== loader) return\n this.view?.engine.setPageTexture(page, this.paper.blank)\n },\n onError: (page, error) => this.fail(error, page),\n })\n this.loader = loader\n this.inView = ''\n this.ready = this.readyFired = false\n // the pages around the start, so the first turn either way has its next page ready\n const count = this.pageCount\n const first = [around - 1, around, around + 1, around + 2]\n const visible = pagesAt(pageToPosition(around, layout, count), layout, count)\n loader.start(visible[0] ?? around, visible[visible.length - 1] ?? around, first).then(() => {\n if (this.loader !== loader || this.disposed) return\n this.ready = true\n this.requestFrame()\n })\n }\n\n // loads around what's on screen, pages mid-turn included, the page being read first\n private followReader(engine: Engine, layout: Layout) {\n if (!this.loader) return\n const { from, to } = engine.pagesInView()\n const around = pagesAt(engine.position, layout, this.pageCount)[0] ?? to\n const key = `${from}:${to}:${around}`\n if (key === this.inView) return\n this.inView = key\n this.loader.focus(from, to, around)\n }\n\n private fail(error: unknown, page?: number) {\n if (this.options.onError) this.options.onError(error, page)\n else console.warn('[leafthrough]', error)\n this.dispatchEvent({ type: 'error', detail: { error, page } })\n }\n\n private detail(): PageChangeDetail {\n return pageDetail(this.view?.engine.position ?? 0, this.layout, this.pageCount)\n }\n\n private emitPageChange() {\n const detail = this.detail()\n const key = `${detail.layout}:${detail.visiblePages.join(',')}/${detail.pageCount}`\n if (key === this.lastDetailKey) return\n const first = !this.lastDetailKey\n this.lastDetailKey = key\n if (first) return\n this.options.onPageChange?.(detail)\n this.dispatchEvent({ type: 'pagechange', detail })\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/LeafThroughObject.ts"],"sourcesContent":["import * as THREE from 'three'\nimport { sourceKey } from './content.js'\nimport { floorPoint, rayThrough, type Engine } from './engine.js'\nimport { framingFor, plainFramingFor, type Framing, type Rect } from './framing.js'\nimport type { GoToOptions } from './LeafThrough.js'\nimport { bindKeyboard, pageDetail, pagesAt, pageToPosition, Riffle } from './navigation.js'\nimport {\n changedOptions,\n DEFAULT_SPREAD_QUERY,\n LIVE_OPTIONS,\n pageImage,\n preloadRadius,\n resolveSettings,\n snapshot,\n type LeafThroughOptions,\n type Layout,\n type PageChangeDetail,\n type ResolvedSettings,\n} from './options.js'\nimport { PaperTextures } from './paper/paperTextures.js'\nimport { SingleEngine } from './single/SingleEngine.js'\nimport { SpreadEngine } from './spread/SpreadEngine.js'\nimport { PageLoader } from './textures.js'\n\n/**\n * The magazine's options, less those about its own canvas and DOM. Of `lighting`, only\n * `shadows` (whether sheets cast and receive them) and `sun.position` apply: the lights are\n * your scene's. Point `sun.position` towards your shadow-casting directional light, in the\n * magazine's own frame; it places the shadow a turning sheet casts right by the spine.\n */\nexport type LeafThroughObjectOptions = Omit<LeafThroughOptions, 'bleed' | 'maxPixelRatio' | 'plain' | 'label' | 'formatPageLabel'>\n\n/** What the magazine draws with, from your scene. */\nexport interface LeafThroughObjectContext {\n /** The renderer drawing your scene. Pages load into it. */\n renderer: THREE.WebGLRenderer\n /** The camera your scene is drawn with. Pointer input is cast from it; change it later through `camera`. */\n camera: THREE.Camera\n /**\n * Takes pointer input, and keys with `interaction.keyboard: true` while it has focus. It must\n * show the camera's whole view. Default `renderer.domElement`.\n */\n domElement?: HTMLElement\n}\n\nexport interface LeafThroughObjectEventMap extends THREE.Object3DEventMap {\n pagechange: { detail: PageChangeDetail }\n ready: {}\n pageload: { detail: { page: number } }\n error: { detail: { error: unknown; page?: number } }\n /** Something changed between frames: draw one, and keep drawing while `tick()` returns true. */\n change: {}\n}\n\n/** Where the magazine lies in its own units. */\nexport interface LeafThroughBounds {\n /** The pages at rest: what to frame. */\n pages: Rect\n /** Everything a turning page can sweep through. */\n reach: Rect\n}\n\nconst isClient = typeof window !== 'undefined'\n\n/**\n * A 3D magazine to add to your own three.js scene. It draws nothing itself: call `tick()`\n * every frame you render, and render again whenever it fires `change`.\n *\n * In its own units a page is 1 wide and `pageAspect` tall, the spine runs up the y axis at\n * x = 0, and the pages lie on the z = 0 plane facing +z. Move, turn and scale it like any\n * object. Pointer input is cast through `camera` onto the pages, wherever they are.\n *\n * ```ts\n * const magazine = new LeafThroughObject({ renderer, camera }, { pages, layout: 'spread' })\n * scene.add(magazine)\n * renderer.setAnimationLoop((now) => {\n * magazine.tick(now)\n * renderer.render(scene, camera)\n * })\n * ```\n */\nexport class LeafThroughObject extends THREE.Group<LeafThroughObjectEventMap> {\n /** The camera your scene is drawn with; pointer input is cast from it. */\n camera: THREE.Camera\n readonly domElement: HTMLElement\n private inputEnabled = true\n\n /**\n * Whether it takes pointer and key input. Turn it off while it's tucked away or moving,\n * and on again; the pages stay as they are. Turning it off lets go of a page being held.\n * Default `true`.\n */\n get enabled() {\n return this.inputEnabled\n }\n set enabled(value: boolean) {\n if (this.inputEnabled && !value) this.view?.engine.cancelInput?.()\n this.inputEnabled = value\n }\n\n private readonly renderer: THREE.WebGLRenderer\n private options: LeafThroughObjectOptions\n private readonly paper: PaperTextures\n private loader: PageLoader | null = null\n private view: { engine: Engine; settings: ResolvedSettings; framing: Framing; dispose: () => void } | null = null\n\n private spreadMedia: { list: MediaQueryList; abort: AbortController } | null = null\n private readonly motionMedia: MediaQueryList\n private readonly abort = new AbortController()\n private readonly riffle = new Riffle()\n private readonly metrics: { readonly dragWidth: number; dragDelta(dx: number, dy: number): number }\n private readonly ray = new THREE.Ray()\n private readonly point = new THREE.Vector3()\n private readonly ends = [new THREE.Vector3(), new THREE.Vector3()]\n private needsRender = true\n private lastPagesKey = ''\n private lastDetailKey = ''\n private inView = ''\n private ready = false\n private readyFired = false\n private disposed = false\n\n constructor({ renderer, camera, domElement = renderer.domElement }: LeafThroughObjectContext, options: LeafThroughObjectOptions) {\n super()\n if (!isClient) throw new Error('LeafThrough needs a browser; create it on the client.')\n this.renderer = renderer\n this.camera = camera\n this.domElement = domElement\n this.options = snapshot(options)\n const object = this\n this.metrics = {\n get dragWidth() {\n return object.dragWidth()\n },\n dragDelta(dx, dy) {\n const axis = object.pageAxis()\n return (dx * axis.x + dy * axis.y) / (axis.length() || 1)\n },\n }\n\n this.paper = new PaperTextures(options.paper?.backColor ?? '#fcfcf9', {\n onGrain: () => this.remount(),\n onError: (error) => this.fail(error),\n })\n\n const signal = this.abort.signal\n this.watchSpreadQuery()\n this.motionMedia = window.matchMedia('(prefers-reduced-motion: reduce)')\n this.motionMedia.addEventListener('change', () => this.remount(), { signal })\n window.addEventListener('online', () => this.retry(), { signal })\n bindKeyboard(\n domElement,\n () => (this.enabled && this.shown() ? this.view?.settings.interaction.keyboard : false),\n { next: () => this.next(), prev: () => this.prev(), first: () => this.goTo(0), last: () => this.goTo(this.pageCount - 1) },\n signal,\n )\n\n this.remount(options.startPage ?? 0)\n }\n\n // --- public API -----------------------------------------------------------------\n\n /** First visible page, 0-based. */\n get page(): number {\n return this.detail().page\n }\n\n get pageCount(): number {\n return this.options.pages.length\n }\n\n get layout(): Layout {\n return this.view?.settings.layout ?? this.pickLayout()\n }\n\n get visiblePages(): number[] {\n return this.detail().visiblePages\n }\n\n /** True while a page is being dragged or is completing its turn. */\n get turning(): boolean {\n return this.view?.engine.turning ?? false\n }\n\n /**\n * Where the magazine lies in its own units: frame `pages` with your camera. Changes with\n * the layout, which `'auto'` can switch as the window resizes.\n */\n get bounds(): LeafThroughBounds {\n const layout = this.layout\n const aspect = this.view?.settings.pageAspect ?? resolveSettings(this.options, layout, false).pageAspect\n return { pages: plainFramingFor(layout, aspect).box, reach: framingFor(layout, aspect).view }\n }\n\n /** Turns to the next page. Returns false at the end. */\n next(): boolean {\n this.riffle.stop()\n return this.view?.engine.flip(true) ?? false\n }\n\n /** Turns to the previous page. Returns false at the start. */\n prev(): boolean {\n this.riffle.stop()\n return this.view?.engine.flip(false) ?? false\n }\n\n /** Shows `page` (0-based), jumping there or turning the pages one by one. */\n goTo(page: number, { animate = false }: GoToOptions = {}) {\n if (!this.view || Number.isNaN(page)) return\n this.riffle.stop()\n const engine = this.view.engine\n const target = pageToPosition(page, this.view.settings.layout, this.pageCount)\n if (!animate) {\n engine.jumpTo(target)\n return\n }\n if (engine.position === target) return\n this.riffle.start(target, this.view.settings.motion.speed)\n this.requestFrame()\n }\n\n /** Loads the pages that failed again. Also happens by itself when the browser comes back online. */\n retry() {\n this.loader?.retry()\n }\n\n /** Changes some options and keeps the rest. Structural changes rebuild the pages and keep the current page. */\n update(options: Partial<LeafThroughObjectOptions>) {\n this.applyOptions({ ...this.options, ...options })\n }\n\n /** Replaces every option at once: anything left out goes back to its default. */\n setOptions(options: LeafThroughObjectOptions) {\n this.applyOptions(options)\n }\n\n /**\n * Whether a press at this point on screen lands on the magazine, which then takes it. Check\n * it in your own pointer handlers to leave such presses alone. False while it's hidden or\n * not `enabled`.\n */\n covers(clientX: number, clientY: number): boolean {\n const view = this.view\n if (!view || !this.enabled || !this.shown()) return false\n if (!rayThrough(this.camera, this.domElement, clientX, clientY, this.ray)) return false\n const point = floorPoint(view.engine.group, this.ray, this.point)\n if (!point) return false\n // a closed spread only turns from its cover side\n const box = view.framing.box\n const side = view.engine.activeSide\n const left = side === 'right' ? box.x + box.width / 2 : box.x\n const right = side === 'left' ? box.x + box.width / 2 : box.x + box.width\n return point.x >= left && point.x <= right && point.y >= box.y && point.y <= box.y + box.height\n }\n\n /**\n * Advances the pages to `now` (a `requestAnimationFrame` timestamp). Call it every frame\n * before rendering. Returns true while the pages are moving and need another frame.\n */\n tick(now = performance.now()): boolean {\n const view = this.view\n if (!view) return false\n this.riffle.step(now, view.engine)\n const moved = view.engine.update(now)\n this.followReader(view.engine, view.settings.layout)\n const draw = moved || this.needsRender\n this.needsRender = false\n if (this.ready && !this.readyFired) {\n this.readyFired = true\n this.options.onReady?.()\n this.dispatchEvent({ type: 'ready' })\n }\n this.emitPageChange()\n return draw || this.riffle.active\n }\n\n /** Frees the pages, textures and listeners, and takes the magazine out of its parent. */\n dispose() {\n if (this.disposed) return\n this.disposed = true\n this.riffle.stop()\n this.abort.abort()\n this.spreadMedia?.abort.abort()\n this.view?.dispose()\n this.view = null\n this.loader?.dispose()\n this.paper.dispose()\n this.removeFromParent()\n }\n\n // --- options and layout -----------------------------------------------------------\n\n private applyOptions(options: LeafThroughObjectOptions) {\n if (this.disposed) return\n const previous = this.options\n const next = (this.options = snapshot(options))\n const changed = changedOptions(previous, next)\n if (!changed.length) return\n if (changed.includes('spreadQuery')) this.watchSpreadQuery()\n\n const page = this.page\n let rebuild = changed.some((key) => !LIVE_OPTIONS.has(key))\n if (changed.includes('pages') && this.pagesKey(next) !== this.lastPagesKey) {\n this.loader?.dispose()\n this.loader = null\n rebuild = true\n }\n if (changed.includes('preload')) this.loader?.setRadius(preloadRadius(this.options.preload))\n if (rebuild) this.remount(page)\n }\n\n private pickLayout(): Layout {\n const layout = this.options.layout ?? 'auto'\n if (layout !== 'auto') return layout\n return this.spreadMedia?.list.matches ? 'spread' : 'single'\n }\n\n private watchSpreadQuery() {\n this.spreadMedia?.abort.abort()\n const list = window.matchMedia(this.options.spreadQuery ?? DEFAULT_SPREAD_QUERY)\n const abort = new AbortController()\n list.addEventListener('change', () => this.remount(), { signal: abort.signal })\n this.spreadMedia = { list, abort }\n }\n\n private remount(page = this.page) {\n if (this.disposed) return\n this.riffle.stop()\n this.view?.dispose()\n\n const layout = this.pickLayout()\n const settings = resolveSettings(this.options, layout, this.motionMedia.matches)\n this.lastPagesKey = this.pagesKey(this.options)\n if (!this.loader) this.loadPages(page, layout)\n this.loader?.setWrap(layout === 'single' && settings.loop)\n\n const domElement = this.domElement\n const blank = this.paper.blank\n const ctx = {\n input: {\n element: domElement,\n ray: (x: number, y: number, ray: THREE.Ray) => rayThrough(this.camera, domElement, x, y, ray),\n covers: (x: number, y: number) => this.covers(x, y),\n },\n settings,\n textures: this.options.pages.map((_, i) => this.loader?.textures[i] ?? blank),\n blank: layout === 'single' ? this.paper.back(settings.paper.backColor) : blank,\n pattern: this.paper.grain(settings, this.renderer),\n position: pageToPosition(page, layout, this.pageCount),\n metrics: this.metrics,\n shading: {\n spineContact: { value: new THREE.Vector3(1, 0, 0) },\n lightDirection: { value: new THREE.Vector3(...settings.lighting.sun.position).normalize() },\n },\n requestFrame: () => this.requestFrame(),\n }\n const engine: Engine = layout === 'spread' ? new SpreadEngine(ctx) : new SingleEngine(ctx)\n this.add(engine.group)\n this.view = {\n engine,\n settings,\n framing: framingFor(layout, settings.pageAspect),\n dispose: () => {\n this.remove(engine.group)\n engine.dispose()\n },\n }\n this.requestFrame()\n }\n\n // A drag across this many pixels turns a page all the way: the magazine's drag width, at the\n // size it shows on screen now.\n private readonly axis = new THREE.Vector2()\n\n private pageAxis() {\n const group = this.view?.engine.group ?? this\n group.updateWorldMatrix(true, false)\n this.camera.updateWorldMatrix(true, false)\n const rect = this.domElement.getBoundingClientRect()\n const [spine, edge] = this.ends.map((end, x) => end.set(x, 0, 0).applyMatrix4(group.matrixWorld).project(this.camera))\n return this.axis.set(((edge.x - spine.x) * rect.width) / 2, -((edge.y - spine.y) * rect.height) / 2)\n }\n\n private dragWidth() {\n return Math.max(1, (this.view?.framing.dragWidth ?? 1) * this.pageAxis().length())\n }\n\n // whether this and every parent are visible\n private shown() {\n for (let object: THREE.Object3D | null = this; object; object = object.parent) if (!object.visible) return false\n return true\n }\n\n private requestFrame() {\n this.needsRender = true\n this.dispatchEvent({ type: 'change' })\n }\n\n // --- pages ---------------------------------------------------------------------\n\n // what the pages show; their alt and text don't count\n private pagesKey(options: LeafThroughObjectOptions) {\n return options.pages.map((p) => sourceKey(pageImage(p))).join('\\n')\n }\n\n private loadPages(around: number, layout: Layout) {\n this.loader?.dispose()\n const loader = new PageLoader(this.options.pages.map(pageImage), this.renderer, preloadRadius(this.options.preload), {\n onLoad: (page, texture) => {\n if (this.loader !== loader) return\n this.view?.engine.setPageTexture(page, texture)\n this.options.onPageLoad?.(page)\n this.dispatchEvent({ type: 'pageload', detail: { page } })\n },\n onUnload: (page) => {\n if (this.loader !== loader) return\n this.view?.engine.setPageTexture(page, this.paper.blank)\n },\n onError: (page, error) => this.fail(error, page),\n })\n this.loader = loader\n this.inView = ''\n this.ready = this.readyFired = false\n // the pages around the start, so the first turn either way has its next page ready\n const count = this.pageCount\n const first = [around - 1, around, around + 1, around + 2]\n const visible = pagesAt(pageToPosition(around, layout, count), layout, count)\n loader.start(visible[0] ?? around, visible[visible.length - 1] ?? around, first).then(() => {\n if (this.loader !== loader || this.disposed) return\n this.ready = true\n this.requestFrame()\n })\n }\n\n // loads around what's on screen, pages mid-turn included, the page being read first\n private followReader(engine: Engine, layout: Layout) {\n if (!this.loader) return\n const { from, to } = engine.pagesInView()\n const around = pagesAt(engine.position, layout, this.pageCount)[0] ?? to\n const key = `${from}:${to}:${around}`\n if (key === this.inView) return\n this.inView = key\n this.loader.focus(from, to, around)\n }\n\n private fail(error: unknown, page?: number) {\n if (this.options.onError) this.options.onError(error, page)\n else console.warn('[leafthrough]', error)\n this.dispatchEvent({ type: 'error', detail: { error, page } })\n }\n\n private detail(): PageChangeDetail {\n return pageDetail(this.view?.engine.position ?? 0, this.layout, this.pageCount)\n }\n\n private emitPageChange() {\n const detail = this.detail()\n const key = `${detail.layout}:${detail.visiblePages.join(',')}/${detail.pageCount}`\n if (key === this.lastDetailKey) return\n const first = !this.lastDetailKey\n this.lastDetailKey = key\n if (first) return\n this.options.onPageChange?.(detail)\n this.dispatchEvent({ type: 'pagechange', detail })\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leafthrough",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A 3D magazine you can flip through, with paper that bends, rolls and casts shadows. Built on three.js, with React, Vue, Svelte and Web Component bindings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"magazine",
|