|
Download docs/EMBED.md from SharpMeow/unroll: direct link, hf CLI and curl.
- Browser
- Download file 5.28 kB
-
https://huggingface.co/spaces/SharpMeow/unroll/resolve/main/docs/EMBED.md
- Command line
-
hf download hf://spaces/SharpMeow/unroll/docs/EMBED.md
-
curl -L -o EMBED.md https://huggingface.co/spaces/SharpMeow/unroll/resolve/main/docs/EMBED.md
5.28 kB
| # Embedding Unroll | |
| Unroll can live inside any web page as an iframe, fully interactive: visitors can unroll, scrub, | |
| switch layers, open close-ups, use Destroy and every other tool right in the frame. | |
| ## The snippet | |
| ```html | |
| <iframe src="https://sharpmeow-unroll.static.hf.space/index.html?embed=1" | |
| title="Unroll: a Herculaneum scroll in 3D" width="100%" height="480" | |
| style="border:0;max-width:100%" allow="fullscreen; clipboard-write" allowfullscreen loading="lazy"></iframe> | |
| ``` | |
| The **</>** button in the viewer builds this for the current view, with options for autoplay and hidden controls, | |
| and copies it. `window.unroll.embed.snippet(options)` returns the same `{ url, html }`. | |
| oEmbed consumers can read [`/oembed.json`](../oembed.json); the page links it with `rel="alternate"`. | |
| Use the direct static URL (`sharpmeow-unroll.static.hf.space`), not the huggingface.co Space page. The static host | |
| sends no `X-Frame-Options` or `frame-ancestors` header, so any site can frame it. | |
| ## Sizing | |
| The viewer fills its frame and follows it when it resizes. For a responsive embed, give the frame a box and let it fill it: | |
| ```html | |
| <div style="position:relative;width:100%;aspect-ratio:16/10"> | |
| <iframe src="https://sharpmeow-unroll.static.hf.space/index.html?embed=1" title="Unroll" | |
| style="position:absolute;inset:0;width:100%;height:100%;border:0" | |
| allow="fullscreen; clipboard-write" allowfullscreen loading="lazy"></iframe> | |
| </div> | |
| ``` | |
| The layout gets more compact as the frame gets smaller; below 420 px of height the title is hidden. | |
| ## URL parameters | |
| | Parameter | Meaning | | |
| | --- | --- | | |
| | `embed=1` | Embed layout: compact header, an **Open full viewer** link, a fullscreen button, click to interact | | |
| | `autoplay=1` | Play the unroll once the scroll is loaded | | |
| | `controls=0` | Hide the controls (the scene stays interactive) | | |
| | `scroll`, `t`, `layer`, `focus`, `view`, `compare`, `panel`, `destroy`, `tool`, `sandbox`, `cam` | Start state, as in the full viewer (see [AGENTS.md](../AGENTS.md)) | | |
| Example: `index.html?embed=1&autoplay=1&layer=overlay&t=0.4`. | |
| ## Click to interact | |
| Until the visitor clicks (or taps, or presses Enter on) the "Click to interact" layer, the mouse wheel and touch | |
| scrolling go to the host page, so the frame never traps a reader scrolling past it. After the click the scene takes | |
| drag, pinch and wheel. Clicking anywhere on the host page hands them back. The toolbar, scrubber and chips work | |
| without activating. `embed.activate(true|false)` does the same from code, and `getState().embed.active` reports it. | |
| ## postMessage API | |
| Host pages drive the viewer with `postMessage`. It mirrors `window.unroll`: every method in [`/api.json`](../api.json) | |
| is available by its name, with the same arguments and the same `{ ok, ... }` results. | |
| Send to the frame: | |
| ```js | |
| frame.contentWindow.postMessage({ unroll: 1, id: 7, method: 'setLayer', args: ['xray'] }, 'https://sharpmeow-unroll.static.hf.space'); | |
| ``` | |
| | Message from the host | Fields | | |
| | --- | --- | | |
| | Call a method | `{ unroll: 1, id, method, args }`. `method` is an API name such as `getState`, `setUnroll`, `camera.orbit`, `destroy.tear`, `bench.run`; `args` is an array | | |
| | Subscribe to an event | `{ unroll: 1, id, method: 'subscribe', args: [eventName] }` | | |
| | Unsubscribe | `{ unroll: 1, id, method: 'unsubscribe', args: [eventName] }` | | |
| | Message from the viewer | Fields | | |
| | --- | --- | | |
| | Ready | `{ unroll: 1, type: 'ready', version: { version, date, sha, api }, methods: [names] }`, sent when the scroll is loaded | | |
| | Result | `{ unroll: 1, type: 'result', id, result }`. `result` is `{ ok: true, ... }` or `{ ok: false, error, code }` | | |
| | Event | `{ unroll: 1, type: 'event', event, payload }` | | |
| Events: `change`, `unroll`, `layer`, `focus`, `panel`, `destroy`, `camera`, `settled`, `scroll`, `extension`, `error`. | |
| Not available over postMessage: `on`, `off` and `register` (use subscribe), and `open.listLocal` and `open.removeLocal`, | |
| which would let a host read or delete scrolls the visitor keeps in their own browser (code `forbidden`). | |
| `listScrolls` leaves out the visitor's local scrolls. Check `event.origin` on your side as usual. | |
| A small client: | |
| ```js | |
| const frame = document.querySelector('iframe'); | |
| const ORIGIN = 'https://sharpmeow-unroll.static.hf.space'; | |
| let seq = 0; const pending = {}; | |
| const unroll = (method, ...args) => new Promise((res) => { | |
| const id = ++seq; pending[id] = res; | |
| frame.contentWindow.postMessage({ unroll: 1, id, method, args }, ORIGIN); | |
| }); | |
| addEventListener('message', (e) => { | |
| if (e.origin !== ORIGIN || e.data?.unroll !== 1) return; | |
| if (e.data.type === 'ready') { unroll('subscribe', 'layer'); unroll('setUnroll', 0.5); } | |
| if (e.data.type === 'result') { pending[e.data.id]?.(e.data.result); delete pending[e.data.id]; } | |
| if (e.data.type === 'event') console.log(e.data.event, e.data.payload); | |
| }); | |
| ``` | |
| ## Test page | |
| [`tests/embed-host.html`](../tests/embed-host.html) frames the viewer next to buttons that drive it with postMessage. | |
| `?src=` points it at another viewer URL. `node tests/embed.mjs <viewer URL> <host URL>` serves both on different | |
| origins and checks click to interact, wheel handling, drag, controls, fullscreen, resizing, autoplay, `controls=0` | |
| and the postMessage API in headless Chrome. | |