Folio3D

A 3D flipbook viewer for PDF and image books. Written in plain JavaScript, with no frameworks and no jQuery. This page covers the JavaScript library.

Quick start

Copy dist/folio3d.min.js and dist/folio3d.min.css to your site, then:

<link rel="stylesheet" href="folio3d.min.css">
<script src="folio3d.min.js"></script>

<div id="book" style="height: 640px"></div>

<script>
  const book = new Folio3D('#book', {
    pdf: 'files/catalog.pdf',
    title: 'Spring Catalog',
  });
</script>

For a book made of images, pass pages instead of pdf:

new Folio3D('#book', {
  pages: [
    'pages/01.jpg',
    { src: 'pages/02.jpg', thumb: 'pages/02-small.jpg', caption: 'Contents' },
    { src: 'pages/03.jpg', text: 'Text of the page, used for search and reader mode' },
  ],
});

As an ES module:

import Folio3D from './folio3d.esm.js';
The container needs a height. The viewer fills it completely. PDFs on other domains need CORS headers, because browsers block reading them otherwise.

No-code embed

Elements with these attributes are initialised automatically when the page loads.

<!-- inline viewer: a PDF URL or a JSON options object -->
<div data-folio3d="catalog.pdf" style="height: 70vh"></div>
<div data-folio3d='{"pdf": "catalog.pdf", "theme": "dark"}' style="height: 70vh"></div>

<!-- opens in a lightbox on click -->
<a href="catalog.pdf" data-folio3d-lightbox data-title="Catalog">Read the catalog</a>

To disable auto-initialisation, set window.Folio3DAutoInit = false before the script loads. Content added later can be initialised with Folio3D.autoInit(container).

Options

OptionDefaultDescription
pdfnullPDF URL, ArrayBuffer or Uint8Array.
pagesnullArray of image URLs or {src, thumb, caption, text} objects.
idnullUnique id. Used for bookmarks, “continue reading” and the address bar hash.
title''Shown in the top bar and used for sharing and accessibility.
mode'auto''webgl' (realistic curl), 'css3d', 'swipe' or 'auto'. If WebGL is unavailable, the viewer falls back to CSS 3D.
layout'auto''double', 'single' or 'auto', which chooses by container shape.
singleCovertrueShow the first page alone, like a book cover.
centerSingletrueCenter the closed book (cover or back page).
startPage1Page to open at.
rtlfalseRight-to-left books (Arabic, Hebrew, manga).
theme'auto''light', 'dark' or 'auto' (follows the visitor’s system).
accentnullAccent color, for example '#0f766e'.
colorsnullOverride design tokens, for example {stage: '#111', paper: '#fffdf8'}. See Theming.
heightnullSets the container height, for example 640 or '80vh'.
flipDuration900Page turn duration in ms. Visitors who prefer reduced motion get instant turns.
sound, soundVolume, soundUrltrue, 0.5, nullPage turn sound. It is synthesized by default; set soundUrl to use your own file.
autoplay, autoplayInterval, autoplayLoopfalse, 4000, trueTurn pages automatically.
toolbar, arrows, progressBartrueShow the toolbar, the side arrows and the reading progress bar.
thumbnails, toc, search, bookmarkstrueSide panel tabs. toc also accepts an array: [{title, page, items}].
share, download, print, fullscreen, readerModetrueToolbar and menu actions.
autoplayButton, soundButton, modeSwitch, themeSwitchtrueItems in the “more” menu.
downloadUrlnullFile offered by the download button. Defaults to the PDF URL.
captionsnullArray (by page) or object {pageNumber: text} shown under the book.
hotspots[]Interactive areas. See Hotspots.
pdfLinkstrueMake links inside the PDF clickable.
deepLinktrueKeep the page in the URL hash (#page=5, or #book-ID=5 when id is set).
resumetrueOffer to continue from the last page read.
zoomMax, zoomStep4, 0.5Zoom limits.
clickToFliptrueClick on a page side to turn it.
wheelFliptrueMouse wheel turns pages in fullscreen and the lightbox.
lang, strings'auto', nullInterface language and string overrides. See Languages.
pdfjsSrc, pdfjsWorkerSrccdnjsWhere to load pdf.js from. Point these at your own copy to self-host it.
pdfPasswordnullPassword of a protected PDF.
crossOrigin'anonymous'CORS mode for page images.
onReady, onPagechange, …Callbacks for every event (on + capitalised event name).

Methods

const book = new Folio3D('#book', options);
await book.ready;            // resolves when the first spread is shown

book.next();  book.prev();   // reading direction (respects rtl)
book.flip(1); book.flip(-1); // visual direction: right / left
book.goToPage(12);           // 1-based
book.first(); book.last();

book.setZoom(2); book.zoomIn(); book.zoomOut();
book.setMode('css3d');       // 'webgl' | 'css3d' | 'swipe'
book.setLayout('single');    // 'single' | 'double' | 'auto'
book.setTheme('dark');       // 'light' | 'dark' | 'auto'
book.toggleFullscreen();
book.play(); book.pause();

const results = await book.search('price');  // [{page, snippet}]
book.setHighlight('price');  // '' clears

book.toggleBookmark(5); book.getBookmarks();
book.share(); book.download(); book.printPage(); book.openReader();

book.currentPage;  book.visiblePages;  book.pageCount;  book.mode;
book.destroy();

Folio3D.open(options);       // lightbox, returns the viewer
Folio3D.autoInit(container); // initialise data attributes in a container

Events

The viewer is an EventTarget. Event data is in event.detail.

book.addEventListener('pagechange', (e) => console.log(e.detail.page));
book.on('ready', ({ pages }) => console.log(pages + ' pages')); // shorthand, chainable
EventDetail
ready{pages}
pagechange{page, pages, total}
zoom{zoom}
modechange{mode}
themechange{theme}
autoplay{playing}
bookmarks{bookmarks}
download{url}
error{error}: the book could not be loaded.
destroy{}

Hotspots & links

Links inside PDFs work automatically. Hotspots add interactive areas to any page. Coordinates are fractions of the page size, from 0 to 1.

hotspots: [
  { page: 3, type: 'video',  url: 'https://www.youtube.com/watch?v=VIDEO_ID', x: .1, y: .4, w: .8, h: .3, title: 'Watch' },
  { page: 5, type: 'link',   url: 'https://example.com/product', x: .6, y: .1, w: .3, h: .2 },
  { page: 2, type: 'page',   target: 12, x: .05, y: .3, w: .9, h: .08 },
  { page: 7, type: 'iframe', url: 'https://example.com/map', x: 0, y: .5, w: 1, h: .5 },
  { page: 9, type: 'html',   html: '<button class="buy">Buy now</button>', x: .1, y: .8, w: .3, h: .08 },
]

Video hotspots accept YouTube, Vimeo or a direct video file. YouTube uses the privacy-friendly youtube-nocookie.com player.

Theming

Every color, radius and blur is a CSS custom property on .f3d. Override them in CSS or with the colors option, where keys drop the --f3d- prefix. The theme builder demo (packages/core/demo/builder.html) generates both.

.f3d {
  --f3d-accent: #0f766e;           /* buttons, progress, highlights */
  --f3d-stage: #eef1f4;            /* background (any CSS background) */
  --f3d-surface: rgba(255,255,255,.78); /* toolbar, panels (glass) */
  --f3d-text: #161a23;
  --f3d-muted: #646b7a;
  --f3d-paper: #ffffff;            /* blank page color */
  --f3d-radius: 16px;
  --f3d-blur: 18px;
  --f3d-font: 'Inter', sans-serif;
}

Dark values live in .f3d[data-theme="dark"], and the same values apply automatically for theme: 'auto' when the system is dark.

Languages

English and Russian are included. The language is picked from lang, or from the page’s <html lang> when lang is 'auto'. To translate, pass the strings that differ from English:

new Folio3D('#book', { pdf: 'a.pdf', lang: 'de', strings: { next: 'Weiter', prev: 'Zurück', search: 'Suche' } });

All keys are in src/i18n/strings.js.

Keyboard

← → turn pages · PgUp PgDn previous/next · Home End first/last · + − 0 zoom · F fullscreen · Esc closes panels, resets zoom and closes the lightbox. On touch screens: swipe to turn pages and pinch to zoom.

Using WordPress?

Folio3D is also available as a separate WordPress plugin from Codestan, with an admin UI, a block and shortcodes.

Building from source

The sources in src/ are ES modules that run directly in the browser, so no build is needed to develop. To rebuild the distribution files (Node 18+, no npm packages), run in the library folder:

node build.mjs            # rebuild dist/ from src/
node serve.mjs            # http://localhost:5173/ → demos

Browser support

Current Chrome, Edge, Firefox and Safari, on desktop, iOS 15+ and Android. WebGL mode falls back to CSS 3D when WebGL is unavailable. On iPhone, where the Fullscreen API is not available for elements, fullscreen fills the window instead.

FAQ

The PDF does not load

Open the browser console. Usually the PDF is on another domain without CORS headers, or the URL is wrong. Upload the PDF to the same site, or enable CORS on the file server.

Pages look blurry when zoomed

Pages are re-rendered at a higher resolution shortly after zooming stops. With image books, upload images at least 1600 px wide.

Can I use it with React, Vue or another framework?

Yes. Create the viewer in a mount hook and call destroy() on unmount. It has no dependencies of its own.

Credits