Online Flipbook Showcase Demo Code Sample

Requirements
View Demo

Easily enhance your online content with an interactive, page-turning flipbook experience.

This demo allows you to:

  • Choose your own PDF file
  • Add flipbook animation to online content

Implementation steps

To add online flipbook capability with WebViewer:
Step 1: Get started with WebViewer in your preferred web stack
Step 2: Add the ES6 JavaScript sample code provided in this guide

Requires

1// ES6 Compliant Syntax
2// Copilot name: GitHub Copilot, version: 1.0.0, model: GPT-4, version: 2024-06, date: 2025-10-16
3// File: showcase-demos/online-flipbook/index.js
4
5// Setting the path to the Web Worker JS file relative to the html file of the viewer
6Core.setWorkerPath('../../lib/core');
7//Set license key, source, and viewer element
8const licenseKey = 'YOUR_LICENSE_KEY';
9const source = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf';
10const viewer = document.getElementById('viewer');
11
12// Create flipbook container
13const flipbook = document.createElement('div');
14flipbook.id = 'flipbook';
15
16// Loading message
17const loadingMessage = document.createElement('div');
18loadingMessage.id = 'loading-message';
19loadingMessage.style.position = 'absolute';
20loadingMessage.innerHTML = 'Preparing document...';
21
22flipbook.appendChild(loadingMessage);
23viewer.appendChild(flipbook);
24
25// Create navigation buttons for previous and next page
26const previousButton = document.createElement('button');
27previousButton.className = 'btn-style';
28previousButton.id = 'previous';
29previousButton.innerHTML = 'Previous Page';
30previousButton.onclick = () => $('#flipbook').turn('previous');
31
32const nextButton = document.createElement('button');
33nextButton.className = 'btn-style';
34nextButton.id = 'next';
35nextButton.innerHTML = 'Next Page';
36nextButton.onclick = () => $('#flipbook').turn('next');
37
38// Container for buttons (file picker will be added after DocumentViewer is created)
39const controlsContainer = document.createElement('div');
40controlsContainer.className = 'button-container';
41controlsContainer.appendChild(previousButton);
42controlsContainer.appendChild(nextButton);
43viewer.insertBefore(controlsContainer, viewer.firstChild);
44
45// Create an instance of the DocumentViewer
46const documentViewer = new Core.DocumentViewer();
47
48// Set the viewer and scrollview elements that DocumentViewer will append rendered pages to.
49const viewerElement = document.createElement('div');
50documentViewer.setViewerElement(viewerElement);
51documentViewer.setScrollViewElement(viewerElement);
52documentViewer.loadAsPDF = true;
53
54// Load the document with explicit extension and error handling
55try {
56 documentViewer.loadDocument(source, { extension: 'pdf' });
57} catch (error) {
58 console.error('Error loading default document:', error);
59 loadingMessage.innerHTML = `Error loading default document: ${error.message || 'Network failure'}. Please use the file picker to load a local document.`;
60 loadingMessage.style.color = 'red';
61}
62
63// Add error event listener for network failures
64documentViewer.addEventListener('documentLoadingProgress', (progress) => {
65 if (loadingMessage && flipbook.contains(loadingMessage)) {
66 loadingMessage.innerHTML = `Loading document... ${Math.round(progress * 100)}%`;
67 }
68});
69
70documentViewer.addEventListener('loaderror', (error) => {
71 console.error('Document load error:', error);
72 if (loadingMessage) {
73 loadingMessage.innerHTML = `Failed to load document: ${error.message || 'Network failure'}. Please check your connection and try again.`;
74 loadingMessage.style.color = 'red';
75 }
76});
77
78// Event listener for when the document is fully loaded
79documentViewer.addEventListener('documentLoaded', () => {
80 // Complete reset of flipbook container to virgin state
81 const $flipbook = $('#flipbook');
82
83 // Remove all turn.js data attributes
84 $flipbook.removeData(); // Clears all jQuery data
85
86 // Remove all turn.js CSS classes
87 $flipbook.removeClass(); // Remove all classes
88
89 // Remove all turn.js HTML attributes
90 const turnAttributes = [
91 'data-turn', 'data-turn-page', 'data-turn-pages', 'data-turn-size',
92 'data-turn-orig-width', 'data-turn-orig-height', 'data-turn-page-number',
93 'data-turn-direction', 'data-turn-when', 'data-turn-corners'
94 ];
95 turnAttributes.forEach(attr => $flipbook[0].removeAttribute(attr));
96
97 // Reset inline styles to original state
98 $flipbook[0].style.cssText = 'transition: margin-left 0.2s ease-in-out; margin: 0px auto; width: 90%; height: 90%;';
99
100 // Clear all existing content from flipbook container
101 while (flipbook.firstChild) {
102 flipbook.removeChild(flipbook.firstChild);
103 }
104
105 // Re-add loading message
106 loadingMessage.innerHTML = 'Preparing document...';
107 loadingMessage.style.color = '';
108 flipbook.appendChild(loadingMessage);
109 console.log('Added loading message');
110 const doc = documentViewer.getDocument();
111 const info = doc.getPageInfo(1);
112 const width = info.width;
113 const height = info.height;
114 const pageCount = doc.getPageCount();
115 const promises = [];
116 const canvases = [];
117 const boundingRect = flipbook.getBoundingClientRect();
118
119 // Calculate flipbook dimensions based on page aspect ratio
120 let flipbookHeight = boundingRect.height;
121 let flipbookWidth = boundingRect.width;
122 if (((flipbookHeight * width) / height) * 2 < flipbookWidth)
123 flipbookWidth = ((flipbookHeight * width) / height) * 2;
124 else
125 flipbookHeight = ((flipbookWidth / width) * height) / 2;
126
127 // Load each page's canvas
128 for (let i = 0; i < pageCount; i++) {
129 const pageNumber = i + 1;
130 promises.push(
131 doc.requirePage(pageNumber).then(() => {
132 return new Promise(resolve => {
133 doc.loadCanvas({
134 pageNumber,
135 drawComplete: (canvas, index) => {
136 canvases.push({ index, canvas });
137 loadingMessage.innerHTML = `Loading page canvas... (${canvases.length}/${pageCount})`;
138 resolve();
139 },
140 });
141 });
142 })
143 );
144 }
145
146 // Once all pages are loaded, initialize the flipbook
147 Promise.all(promises).then(() => {
148 // Safely remove loading message if it exists as a child
149 if (flipbook.contains(loadingMessage)) {
150 flipbook.removeChild(loadingMessage);
151 } else {
152 console.log('Loading message was already removed or not found in flipbook');
153 }
154
155 if (canvases.length === 1) {
156 // Handle single-page documents without turn.js to avoid range errors
157 console.log('Single page document - displaying without flipbook functionality');
158 const canvas = canvases[0].canvas;
159 flipbook.appendChild(canvas);
160
161 // Hide navigation buttons for single page
162 console.log('Hiding buttons for single page document');
163 if (previousButton) {
164 previousButton.style.display = 'none';
165 console.log('Previous button hidden');
166 }
167 if (nextButton) {
168 nextButton.style.display = 'none';
169 console.log('Next button hidden');
170 }
171
172 } else {
173 // Handle multi-page documents with turn.js flipbook
174 // Wrap each canvas in a div for proper turn.js page structure
175 canvases.sort((a, b) => a.index - b.index).forEach((o, index) => {
176 const pageDiv = document.createElement('div');
177 // Add page number indicator
178 const pageNumber = document.createElement('div');
179 pageNumber.className = 'page-number';
180 pageNumber.textContent = `${index + 1}`;
181 pageDiv.appendChild(pageNumber);
182 pageDiv.appendChild(o.canvas);
183 flipbook.appendChild(pageDiv);
184 });
185
186 // Initialize turn.js
187 $('#flipbook').turn({
188 width: flipbookWidth,
189 height: flipbookHeight,
190 autoCenter: true,
191 pages: canvases.length,
192 page: 1,
193 elevation: 50,
194 gradients: true,
195 acceleration: true,
196 duration: 400,
197 shadows: true,
198 shadowBlur: 6,
199 });
200
201 // Show navigation buttons for multi-page
202 console.log('Showing buttons for multi-page document');
203 if (previousButton) {
204 previousButton.style.display = 'inline-block';
205 console.log('Previous button shown');
206 }
207 if (nextButton) {
208 nextButton.style.display = 'inline-block';
209 console.log('Next button shown');
210 }
211 // Automatically advance to page 2 to demonstrate flipbook
212 setTimeout(() => {
213 try {
214 $('#flipbook').turn('next');
215 } catch (error) {
216 console.error('Error navigating to next page:', error);
217 }
218 }, 500);
219 }
220 }).catch(error => {
221 console.error('Error loading flipbook pages:', error);
222
223 // Provide specific error messages based on error type
224 let errorMessage = 'Error loading pages. ';
225 if (error.message && error.message.includes('Network failure')) {
226 errorMessage += 'Network connection issue. Please check your internet connection and try again.';
227 } else if (error.message && error.message.includes('CORS')) {
228 errorMessage += 'File access issue. Try using a local file instead.';
229 } else {
230 errorMessage += `${error.message || 'Unknown error'}. Please try again with a different file.`;
231 }
232 loadingMessage.innerHTML = errorMessage;
233 loadingMessage.style.color = 'red';
234 });
235});
236
237// UI Elements
238// ui-elements.js
239// Function to create and initialize UI elements
240function createUIElements() {
241 return new Promise((resolve, reject) => {
242 // Dynamically load ui-elements.js if not already loaded
243 if (window.UIElements) {
244 console.log('UIElements already loaded');
245 resolve();
246 return;
247 }
248 const script = document.createElement('script');
249 script.src = '/showcase-demos/online-flipbook/ui-elements.js';
250 script.onload = () => {
251 console.log('✅ UIElements script loaded successfully');
252 UIElements.init();
253 resolve();
254 };
255 script.onerror = () => {
256 console.error('Failed to load UIElements script');
257 reject(new Error('Failed to load ui-elements.js'));
258 };
259 document.head.appendChild(script);
260 });
261}
262
263// Create file picker button using UIElements class (async)
264createUIElements().then(() => {
265 console.log('UIElements loaded successfully');
266 const filePickerButton = UIElements.createFilePickerButton(documentViewer, loadingMessage);
267 controlsContainer.appendChild(filePickerButton); // Add Pick File button to the right of Next Page
268}).catch(error => {
269 console.error('Failed to load UIElements:', error);
270});
271

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales