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
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
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