Easily enhance your online content with an interactive, page-turning flipbook experience.
This demo allows you to:
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
Once you generate your license key, it will automatically be included in your sample code below.
Apryse collects some data regarding your usage of the SDK for product improvement.
The data that Apryse collects include:
For clarity, no other data is collected by the SDK and Apryse has no access to the contents of your documents.
If you wish to continue without data collection, contact us and we will email you a no-tracking trial key for you to get started.
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// Set 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
1// ES6 Compliant Syntax
2// File: showcase-demos/online-flipbook/ui-elements.js
3
4class UIElements {
5
6 static init() {
7 console.log(`UIElements initialized}`);
8
9 }
10
11 static createFilePickerButton(documentViewer, loadingMessage) {
12 const filePickerButton = document.createElement('button');
13 filePickerButton.className = 'btn-style';
14 filePickerButton.id = 'file-picker';
15 filePickerButton.innerHTML = 'Pick File';
16 filePickerButton.onclick = () => {
17 // Open file picker dialog.
18 const fileInput = document.createElement('input');
19 fileInput.type = 'file';
20 fileInput.accept = '.pdf,.doc,.docx,.ppt,.pptx';// Accept Office file formats.
21 fileInput.onchange = async (event) => {
22 const file = event.target.files[0];
23 if (file) {
24 try {
25 const url = URL.createObjectURL(file);
26
27 // Extract file extension from filename.
28 const fileName = file.name;
29 const extension = fileName.split('.').pop().toLowerCase();
30
31 console.log(`Loading file: ${file.name} (extension: ${extension}) blob URL: ${url}`);
32
33 // Add loading feedback.
34 loadingMessage.innerHTML = `Loading ${fileName}...`;
35 loadingMessage.style.color = '#666';
36
37 // Load document with explicit extension and error handling.
38 await documentViewer.loadDocument(url, {
39 extension: extension,
40 // Add additional options for better compatibility.
41 withCredentials: false,
42 customHeaders: {}
43 });
44
45 } catch (error) {
46 console.error('Error loading file:', error);
47 loadingMessage.innerHTML = `Error loading file: ${error.message || 'Network failure'}. Please try again.`;
48 loadingMessage.style.color = 'red';
49
50 // Clean up blob URL on error.
51 if (url) {
52 URL.revokeObjectURL(url);
53 }
54 }
55 }
56 };
57 fileInput.click();
58 };
59
60 return filePickerButton;
61 }
62}
1/* Base Button Styles */
2.btn-style {
3 margin: 10px;
4 padding: 5px 10px;
5 border: 1px solid #ccc;
6 border-radius: 4px;
7 cursor: pointer;
8 font-size: 14px;
9 font-weight: bold;
10 transition: all 0.2s ease;
11 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
12 color: white;
13}
14
15/* Load Button Specific Styles */
16.btn-style {
17 background-color: #007bff;
18}
19
20.btn-style:hover {
21 background-color: #0056b3;
22 transform: translateY(-1px);
23 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
24}
25
26.btn-style:active {
27 transform: translateY(1px);
28 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
29}
30
31/* Button Container */
32.button-container {
33 display: flex;
34 flex-direction: row;
35 align-items: center;
36 gap: 15px;
37 margin: 5px 0;
38 padding-bottom: 5px;
39 border-bottom: 1px solid #e0e0e0;
40 background-color: rgba(112, 198, 255, 0.2
41 /* Light blue background for contrast */
42 );
43}
44
45/* Responsive Design */
46@media (max-width: 768px) {
47 .btn-style {
48 margin: 5px;
49 padding: 8px 12px;
50 font-size: 16px;
51 }
52
53 .button-container {
54 align-items: center;
55 }
56}
57
58/* Pick File Button Specific Styles */
59#file-picker {
60 background-color: #28a745; /* Green color to distinguish from navigation buttons */
61 border-color: #1e7e34;
62}
63
64#file-picker:hover {
65 background-color: #1e7e34;
66 border-color: #155724;
67}
68
69#file-picker:active {
70 background-color: #155724;
71}
72
73/* Navigation Button Spacing */
74#previous, #next {
75 background-color: #007bff; /* Keep blue for navigation */
76}
77
78/* Enhanced Button Container Layout */
79.button-container {
80 justify-content: flex-start; /* Align buttons to the left */
81 padding: 10px 15px;
82 background: linear-gradient(135deg, rgba(112, 198, 255, 0.2), rgba(112, 198, 255, 0.1));
83 border-radius: 8px;
84 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
85}
86
87/* Flipbook Container - minimal styling to avoid turn.js conflicts */
88#flipbook {
89 margin: 20px auto;
90}
91
92/* Turn.js Page Styles - let turn.js handle all visual effects */
93.turn-page {
94 background-color: #ffffff;
95}
96
97/* Remove all custom hover and animation effects - let turn.js handle everything */
98
99/* Remove all hard page and shadow overrides - let turn.js handle these */
100
101/* Removed hover effects to prevent interference with turn.js */
102
103/* Loading state styling */
104#loading-message {
105 text-align: center;
106 font-size: 16px;
107 color: #666;
108 padding: 40px;
109 background-color: #f8f9fa;
110 border-radius: 4px;
111 margin: 20px;
112}
113
114/* Page number indicator (optional) */
115.page-number {
116 position: absolute;
117 bottom: 10px;
118 right: 15px;
119 font-size: 12px;
120 color: #888;
121 background: rgba(255, 255, 255, 0.8);
122 padding: 2px 6px;
123 border-radius: 3px;
124 z-index: 10;
125}
126
127/* Canvas styling within pages */
128canvas {
129 width: 100% !important;
130 height: 100% !important;
131 object-fit: contain;
132 background-color: white;
133}
134
135/* Remove blank page styling - let turn.js handle all page states */
136
137/* Simplified page styling - removed transform conflicts */
138
139/* Remove all turn.js interference - let turn.js handle all visual effects */
140
141/* Removed custom turn animation to prevent conflicts with turn.js */
142
143/* Mobile responsiveness for flipbook */
144@media (max-width: 768px) {
145 #flipbook {
146 margin: 10px auto;
147 }
148
149 .turn-page {
150 border-width: 1px;
151 }
152}
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales