A buildless ccmjs component for displaying one PDF page at a time, powered by locally bundled PDF.js 6.3.289. Includes previous/next navigation, direct page entry, zoom, optional links, text selection and download.
With focus inside the viewer, use Left/Right arrow keys to move to the previous or next page. Click the PDF area or focus it with Tab first. Input fields and shortcuts with modifier keys keep their normal behavior; navigation stops at the first and last page.
Serve this repository over HTTP(S) and open index.html for the three-page demo.
Choose Password-protected PDF (or open index.html?demo=protected) to open
the password prompt. Its password is viewer-test. Both English configurations
are exported from resources/configs.mjs as demo and protectedDemo; the latter
loads resources/protected.pdf without a preset password so the prompt is shown
on the first visit in a tab.
Opening the HTML directly with file:// is not supported by browser module/worker loading.
<script src="./libs/framework/ccm-28.0.0.min.js"></script>
<script type="module">
const viewer = await ccm.start("./ccm.pdf_viewer.mjs", {
pdf: "./resources/demo.pdf",
links: true,
download: true,
textSelection: true,
page: 1,
zoom: "page-width",
}, document.body);
</script>The component uses the bundled ccmjs 28 framework and Shadow DOM encapsulation.
Framework, library, worker and stylesheet paths use the ././ prefix. In this
repository they remain relative to the embedding page, so the included demo works
as a self-contained ZIP distribution when served over HTTP(S). The ccmjs versioning
process replaces these prefixes with absolute GitHub Pages URLs for published versions.
The configured PDF URL is relative to the embedding page unless absolute.
Cross-origin servers must allow CORS.
Dependencies are declared via ccm.load; no npm install or build is needed to use the viewer.
<script
src="https://cdn.jsdelivr.net/gh/ccmjs/pdf_viewer@v1.0.0/libs/framework/ccm-28.0.0.min.js"
integrity="sha384-HDMeDDgKlR2OFJ3ECMwmA6wknqpfpeCiSZYlUhQaFg9FKrvHJp8MMSwrxibvWJ2G"
crossorigin="anonymous"
></script>
<script type="module">
const viewer = await ccm.start(
"https://cdn.jsdelivr.net/gh/ccmjs/pdf_viewer@v1.0.0/ccm.pdf_viewer-1.0.0.min.mjs#sha384-1NwNCUojyNg7BPq0Odf5LRpb3kJ1qlCXpZHd1vHHHYNzm4AONSwujk5CH5TFcDlE",
{},
document.body
);
</script>Place this example inside the document body. It uses the default configuration,
including the bundled demo PDF and German labels. Set pdf in the configuration
to display your own document. No local copy of the component or its libraries is needed.
The framework script uses the browser's integrity attribute; ccmjs verifies the
component using the #sha384-β¦ URL fragment. These hashes cover the framework and
component files, respectively, not every resource loaded by the viewer.
Update the component URL and its integrity hash together when changing versions.
Configuration is also documented directly in ccm.pdf_viewer.mjs.
| Property | Default | Meaning |
|---|---|---|
pdf |
"././resources/demo.pdf" |
PDF URL; HTTP(S) and Blob URLs are accepted. Set to "" to display an empty-viewer hint. |
password |
"" |
Optional initial PDF password. Missing or incorrect passwords trigger an in-viewer prompt. |
rememberPassword |
true |
Reuse successful form entries from sessionStorage for the same PDF URL. Set to false to disable reading and writing cached passwords. |
links |
true |
Enable existing external and internal PDF links. External links open a new tab. |
onLink |
null |
Optional async ({ app, page }) callback for internal PDF links. Receives the resolved one-based page and replaces default navigation. Called after releasing the viewer lock, so the host can await app.goToPage(page). External links remain unchanged. |
download |
true |
Show the download button and enable downloadPdf(). |
textSelection |
true |
Allow selection/copying through PDF.js's text layer. |
navigation |
true |
Show page controls and enable arrow-key navigation. Set to false when an embedding component controls the sequence. goToPage() remains available. |
page |
1 |
Initial page, clamped to the document's range. |
zoom |
"page-width" |
Fit available width, or a number between 0.25 and 4. 1 means 100%. |
filename |
"document.pdf" |
Suggested filename for downloading the original PDF. |
labels |
German labels | UI strings; individual keys can be overridden. |
extensions |
[] |
Sequential extension functions, following the quiz convention. |
pdfjs, css, worker, cMaps, fonts, wasm |
Bundled resources | Override dependency locations if needed; keep API and worker versions identical. |
Fit-to-width follows container resizing, bounded by the same 25β400% zoom range. Large pages scroll inside the component. Only one page canvas is displayed; raster resolution is capped at 16 megapixels and twice the CSS resolution.
download: false disables the viewer's download action, not browser access to
the original PDF. Similarly, disabling selection is not copy protection.
Scanned PDFs without embedded text have no selectable text; OCR is not included.
await viewer.goToPage(3); // One-based integer; rejects out-of-range input
await viewer.setZoom(1.25); // 125%
await viewer.setZoom("page-width");
await viewer.downloadPdf(); // No-op if download is disabled
const state = viewer.state; // { page, pages, zoom, scale }, or null
viewer.pdf = "./another.pdf";
await viewer.start(); // Release old PDF and load the new configuration
await viewer.destroy(); // Release worker, resize observer and Blob URLs before removing the hostAsync actions expose gui.busy. As in the quiz component, actions requested while
busy are ignored. Public methods reject on failures; DOM handlers consume those
rejections after showing a status message. error contains the original error.
destroy() cancels a pending password prompt and waits for the current action;
an instance can be started again afterwards.
viewer.state exposes the current state directly, with these fields:
| Field | Meaning |
|---|---|
page |
Current physical page number, starting at 1. |
pages |
Total page count. |
zoom |
Requested numeric zoom or "page-width". |
scale |
Effective numeric zoom, including the result of width fitting. |
state is initialized after loading and updated after successful rendering;
loaded extensions therefore see the initial state before the first page is drawn.
Use goToPage() and setZoom() to change the displayed page rather than assigning
state fields. Changing page, zoom or other configuration properties takes effect
on the next start(); ordinary navigation updates state, not those configuration defaults.
Set password in the configuration to open a protected PDF directly, or leave it
empty to ask the user when necessary. Incorrect passwords show a retry message.
The prompt supports Enter to submit and a Cancel button. Cancelling releases the
loading task, emits cancel and resolves start() without a document; call
start() again to retry. While waiting for a password, start() remains pending.
To retain access to the instance during that wait, use ccm.instance() followed
by instance.start().
Passwords entered in the form are cleared after submission and are not copied to
config, state or extension events. A password explicitly supplied
in configuration remains part of that configuration and is visible to page code.
By default, successful form entries are kept in sessionStorage per absolute PDF
URL (excluding the fragment), scoped to the embedding origin and browser tab.
They survive reloads for that tab's session and are readable by same-origin page
scripts. Incorrect saved passwords are removed and the prompt reappears. Set
rememberPassword: false to ignore and stop saving cached passwords. If storage
is blocked or full, the viewer continues with the interactive prompt. Explicit
password configuration takes precedence and is not automatically saved.
Disabling remembrance does not delete existing entries. To forget one cached password:
const url = new URL(viewer.pdf, document.baseURI);
url.hash = "";
sessionStorage.removeItem(`ccm.pdf_viewer.password:${url.href}`);Downloads retain the original PDF's encryption.
Extensions receive { app, type }, are awaited in configuration order and can
read app.state, app.error and app.element. There is no onaction callback.
export async function rememberPage({ app, type }) {
if (type === "page") {
console.log("Current page:", app.state.page);
}
}Load this function through a dependency:
extensions: [["ccm.load", "./extensions.mjs#rememberPage"]]| Event | When it runs |
|---|---|
init, ready |
ccmjs instance lifecycle |
before-start |
Before releasing/loading the document |
loaded |
PDF metadata and initial state are available |
render |
The new page, text and link layers have been displayed |
start |
Initial rendering is complete, or the empty-URL hint is shown |
page |
A page navigation has rendered, including internal links |
zoom |
A zoom or fit-to-width resize has rendered |
download |
The browser download has been requested |
error |
An action failed; inspect app.error |
cancel |
Opening a protected PDF was cancelled |
destroy |
Resources and component content have been released |
Typical initial sequence: init β ready β before-start β loaded β render β start.
Extensions run within the active action. They should not await destroy() from
inside an event, or trigger another navigation while the viewer is busy.
render runs after the DOM/state update but before the controls are refreshed and
the busy lock is released. Extension errors propagate and stop later extensions;
an error extension should avoid throwing so it does not mask the original failure.
The private PDF.js document proxy is not exposed to extensions.
| File or directory | Responsibility |
|---|---|
ccm.pdf_viewer.mjs |
Configuration, instance API, lifecycle, DOM construction and PDF.js integration. |
index.html |
English demo shell; selects a named configuration from the query string. |
resources/configs.mjs |
Standard and protected demo configurations, including English labels. |
resources/styles.css |
Component layout and link overlay; complements PDF.js's text-layer CSS. |
resources/demo.pdf, resources/protected.pdf |
Three-page English examples with external and internal links. |
libs/framework/ |
Bundled ccmjs 28.0.0 and its MIT license. |
libs/pdfjs/ |
Unmodified PDF.js distribution files, auxiliary assets and upstream notices. |
LICENSE |
MIT license for this component. |
Start reading the component at config, then start(), run() and render().
The remaining methods implement navigation, downloads, cleanup and UI details.
Each instance keeps its own PDF document, DOM references and interaction state.
run() prevents overlapping actions; a resize during an action is deferred until
the action finishes. Password form submissions resume the pending PDF.js request
directly because start() holds that lock while waiting for input.
Rendering builds a canvas plus optional text and link overlays off-DOM, then
replaces the visible page. All layers use the same PDF viewport transformation.
When changing layout, keep the horizontal padding in styles.css consistent with
the width calculation in render(). PDF.js CSS variables also control text-layer
alignment; keep them in sync with canvas scaling and PDF user units.
The bundled libraries are upstream code, not places for component-specific changes. When updating PDF.js, replace its display API, worker, styles and auxiliary assets from the same upstream release and retain all license notices.
Existing PDF links are overlaid on the rendered page; internal links navigate to their target page while preserving the chosen zoom. Destination-specific scrolling coordinates and zoom instructions are not applied. Only HTTP(S), mail and telephone external links are enabled; embedded scripts and launch actions are not executed.
The viewer uses PDF.js's text layer and a link-only annotation overlay. Other annotations may appear in the rendered PDF, but there is no interactive comment, form or annotation editor. Password-protected PDFs support an initial configured password and an interactive password prompt.
Modern browsers with module workers and PDF.js support are required.
Component code and demo: MIT, see LICENSE.
Bundled PDF.js: Apache-2.0, see libs/pdfjs/LICENSE; additional font and codec
notices remain in their respective resource directories. Bundled ccmjs: MIT,
see libs/framework/LICENSE.