Single pages & thumbnails
<PageView> shows one page on its own, without scrolling or zoom: a preview in a card, a page
picker in a dialog, the page a comment belongs to. Give it a page and a width:
<script setup lang="ts">
import { PageView } from '@embedpdf/vue/page-view';
import { RenderLayer } from '@embedpdf/vue/render';
import type { PageRef } from '@embedpdf/vue/runtime';
defineProps<{ page: PageRef }>();
</script>
<template>
<PageView :page="page" :width="240">
<RenderLayer />
</PageView>
</template>Loading live preview…
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { renderPlugin } from '@embedpdf/vue/render';
import { localEngine } from '@embedpdf/engine';
import PageCard from './PageCard.vue';
import '../basic.css';
const engine = localEngine();
// No Stage: a page on its own needs only the render plugin.
const plugins = [renderPlugin()];
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>
<template>
<Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
<DocumentGate>
<template #fallback><p class="loading">Loading…</p></template>
<PageCard />
</DocumentGate>
</Viewer>
</template>Every layer works in <PageView> as it does on the Stage: selection, annotations, search
highlights. So does a menu anchored to the page. The page keeps its rotation and redraws when it
changes.
Pick the page#
page takes a page’s ref or its index, so 0 is the first page:
<script setup lang="ts">
import { PageView } from '@embedpdf/vue/page-view';
import { RenderLayer } from '@embedpdf/vue/render';
</script>
<template>
<PageView :page="0" :width="320">
<RenderLayer />
</PageView>
</template>usePageList() gives you every page of the document, in order, to loop over:
<script setup lang="ts">
import { PageView } from '@embedpdf/vue/page-view';
import { RenderLayer } from '@embedpdf/vue/render';
import { usePageList, type PageRef } from '@embedpdf/vue/runtime';
const emit = defineEmits<{ pick: [page: PageRef] }>();
const pages = usePageList();
</script>
<template>
<button v-for="page in pages" :key="page.index" @click="emit('pick', page.ref)">
<PageView :page="page.ref" :width="120">
<RenderLayer />
</PageView>
{{ page.label ?? page.index + 1 }}
</button>
</template>Loading live preview…
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { renderPlugin } from '@embedpdf/vue/render';
import { localEngine } from '@embedpdf/engine';
import PagePicker from './PagePicker.vue';
import '../picker.css';
const engine = localEngine();
const plugins = [renderPlugin()];
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>
<template>
<Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
<DocumentGate>
<template #fallback><p class="loading">Loading…</p></template>
<PagePicker />
</DocumentGate>
</Viewer>
</template>A page’s label is the page number the PDF defines, such as 'iv' or 'A-2', when it has one.
A thumbnail strip#
For a strip next to the main view, with many pages that scroll, use a second Stage rather than many
<PageView>s: it mounts only the thumbnails you can see, and it can follow the page the reader is on.
import { createCapabilityToken } from '@embedpdf/vue/runtime';
import { stagePlugin, type StageCapability } from '@embedpdf/vue/stage';
export const ThumbsToken = createCapabilityToken<StageCapability>('stage-thumbs');
export const plugins = [
stagePlugin(),
stagePlugin({
id: 'stage-thumbs',
token: ThumbsToken,
interaction: false, // a drag doesn't select text or draw
zoomGestures: false, // a pinch doesn't resize the thumbnails
zoom: { pageWidth: 120 },
gap: { px: 12 },
pageFrame: { bottom: 20 }, // room for the page number
}),
];<script setup lang="ts">
import { watch } from 'vue';
import { RenderLayer } from '@embedpdf/vue/render';
import { Stage, useStage, useStageState } from '@embedpdf/vue/stage';
import { ThumbsToken } from './thumbnail-strip-token';
const main = useStage();
const thumbs = useStage(ThumbsToken);
const { currentPageIndex } = useStageState();
// Keep the current page's thumbnail in view as the reader moves
watch(currentPageIndex, (index) => thumbs.reveal(index), { immediate: true });
</script>
<template>
<Stage :token="ThumbsToken">
<template #page="{ page }">
<button
:aria-current="page.pageIndex === currentPageIndex"
@click="main.goToPage(page.ref)"
>
<RenderLayer />
</button>
</template>
<template #page-chrome="{ page }">
<span class="label">{{ page.pageIndex + 1 }}</span>
</template>
</Stage>
</template>Loading live preview…
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { localEngine } from '@embedpdf/engine';
import Thumbnails from './Thumbnails.vue';
import { ThumbsToken } from './thumbs-token';
import '../thumbnail-strip.css';
const engine = localEngine();
const plugins = [
stagePlugin(), // the main view
stagePlugin({
id: 'stage-thumbs',
token: ThumbsToken,
interaction: false, // a drag doesn't select text or draw
zoomGestures: false, // a pinch doesn't resize the thumbnails
zoom: { pageWidth: 96 },
gap: { px: 12 },
padding: 10,
pageFrame: { bottom: 20 }, // room for the page number
// A wide, short strip (on a phone) lines the thumbnails up in a row.
responsive: [{ when: { orientation: 'landscape' }, settings: { layout: 'horizontal' } }],
}),
renderPlugin(),
];
const ebook = async (): Promise<OpenInput> => {
const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>
<template>
<Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
<DocumentGate>
<template #fallback><p class="loading">Loading…</p></template>
<div class="reader">
<Thumbnails />
<Stage class="stage">
<template #page><RenderLayer /></template>
</Stage>
</div>
</DocumentGate>
</Viewer>
</template>- Turning off
interactionin the strip’s settings keeps a drag on it from selecting text in the document. - Turning off
zoomGestureskeeps the thumbnails their size under a pinch. reveal()scrolls the strip only as far as needed, so it follows without jumping.
See Two views of one document for everything a second Stage can do.
Props#
| Prop | What it is | Default |
|---|---|---|
page | The page, as its ref or its index | required |
width | The width of the page upright, in pixels: a page turned a quarter is that tall | 240 |
document-id | Which document | the one in scope, else the active one |
fallback | What to show until the page is ready | nothing |
page-frame | Space reserved around the page for your own labels, in pixels per side | 0 |
page-chrome | What to draw in that space | — |
fallback and page-chrome are slots, <template #fallback> and <template #page-chrome>, and
class and style go on the outer box.
Good to know#
How pages look#
A page here has the same background and shadow as on the Stage: the viewer’s page settings, or
--epdf-page-background and --epdf-page-shadow from CSS (see Colors & theming).
Menus inside a page view#
A menu anchored to the page, such as the selection menu, is placed above everything else on the page, so a card or a scrolling list can’t cut it off.
Pointer input#
A <PageView> takes pointer input for the tools when the interaction plugin is registered, so text
selection and annotation editing work in it too. On a touch screen, the Stage is the better choice:
it has the gestures.
Your feedback goes directly to the documentation team.