Pagination
reader.pagination describes what is visible: which spine items and pages are
on screen, where they are in the book and whether that position has resolved.
Use it for everything the reader sees: which page is being read, how many pages
are in a chapter, how far into the book they are. The
.pagination reference lists state$, state
and every field of a result; this page is about using them.
It is not what to save to reopen the book where the reader left it. That is the reading position, and the next section explains the difference.
Pagination or reading position
Section titled “Pagination or reading position”Two values describe where the reader is, and they answer different questions:
reader.pagination |
reader.navigation.readingPosition$ |
|
|---|---|---|
| Answers | What is on screen now | Where the reader is in the book |
| Moves when | The reader navigates, and whenever the book is laid out again: a resize, a rotation, a font size change, a chapter loading nearby | The reader navigates, and the page it goes to is laid out. Never on a relayout after that |
| Use it for | Page numbers, progress bars, the current chapter's title | Saving progress, reopening the book, syncing the position to another device |
Pagination moves on a relayout because its cfi is the first visible character
of the page, and a relayout cuts the pages differently. Say the reader turned
to a page that starts at the word "Alice", then rotates the phone. The page
holding "Alice" now starts a few words earlier, at "said", and pagination's
begin.cfi becomes "said", while the reader has not moved. Save that and
reopen the book in the first orientation, and it opens on the page holding
"said", which is the page before. Every save after a rotation moves the reader
back again. The reading position stays "Alice", so the book reopens on the
page the reader turned to, at any size.
Save the reading position, and open the book at it:
reader.navigation.readingPosition$.subscribe((readingPosition) => { localStorage.setItem( `reading-position-${bookId}`, JSON.stringify(readingPosition), )})
// the next time this book is openedconst saved = localStorage.getItem(`reading-position-${bookId}`)const readingPosition: ReadingPosition | undefined = saved ? JSON.parse(saved) : undefined
const reader = createReader({ manifest, target: readingPosition ? { type: "cfi", value: readingPosition.cfi } : undefined,})
reader.mount(document.getElementById("reader")!)The navigation page says exactly when the
reading position changes, and why every value is the one to
save, whatever its status.
The reading position carries its own percentageEstimateOfBook, the progress
to save for a library screen: it moves with the position it describes, where
pagination's moves with every relayout and lags each navigation until the new
result settles. Anything else you save from pagination has to come from a
settled result, as explained in Settlement.
Reading the stream
Section titled “Reading the stream”reader.pagination.state$ emits the current result as soon as you subscribe,
then every new one. reader.pagination.state is its latest result, read
synchronously.
The reader smooths this stream on purpose. A result is built from several
sources (layout, navigation, the loaded documents), and while the reader
changes state they do not all update at once. Rather than emit each
intermediate combination, the stream waits for them to agree, so it can briefly
lag behind the reader. A result is only settled once it describes the reader's
current position. state lags the same way: it is the stream's latest result,
not a fresher one.
Settlement
Section titled “Settlement”A pagination result describes a moment that may still be resolving. While a
document loads, or during a navigation or a layout, an edge's cfi can describe
the start of an item rather than the page actually being read.
isSettled tells the two apart. A settled result describes the visible pages of
the current layout over content that is ready, or failed to load, and it is the
only variant whose edges have their cfi typed as present, so TypeScript makes
you establish settlement before reading one:
reader.pagination.state$.subscribe((state) => { if (!state.isSettled) return
saveProgress(bookId, state.percentageEstimateOfBook)})Settlement ends as soon as a navigation starts, a layout starts, or a visible
item stops being ready, or is unloaded after failing, and returns once a
replacement result has resolved over the new state. A layout is not only one
you request: the reader lays the spine out again whenever an item finishes
loading, fails to load or unloads, so settlement can drop briefly while the
book loads around the page being read. A visible item whose load failed does
not keep pagination waiting: it settles on it, with the item's start as its
edge's cfi, as the
error handling page explains. Page
metrics such as begin.pageIndexInSpineItem stay available throughout, so
navigation controls keep working on estimates while a result is pending.
It describes the current visible position, not the loading state of the whole book: a settled result does not mean every item has been loaded.
The reading position needs no such check: every value it takes is the closest
known position of the reader's latest navigation, and says with status
whether the reader has found its page yet, or can't, as the
navigation page explains.
The two move in a set order. The reading position moves to where a navigation
goes before pagination settles there, and once it has moved, every settled
result describes the pages the navigation shows, never pages it moved away
from. A navigation that keeps the pages shown, such as one to a cfi on the
second page of a spread, is no exception: the reading position moves, and
pagination settles again on the same pages. An app that keeps pagination's
estimate next to the reading position can therefore let the latest value win:
the reading position's own percentageEstimateOfBook stands until the settled
estimate for the pages holding it replaces it.