Easily create high resolution thumbnail images from selected document pages.
This demo allows you to:
Implementation steps
To add Thumbnail Creation 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.
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// GitHub Copilot v1.0, GPT-4.1, October 15, 2025
3// File: index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
8
9// Create Thumbnail Demo
10// This code demonstrates how to create a high resolution JPG or PNG thumbnail for a PDF document using the loadCanvas API.
11
12function initializeWebViewer() {
13
14 // This code initializes the WebViewer with the basic settings
15 WebViewer({
16 path: '/lib',
17 licenseKey: licenseKey,
18 enableFilePicker: true,
19 loadAsPDF: true, // Ensure files are loaded as PDF documents for best thumbnail quality
20 }, document.getElementById('viewer')).then((instance) => {
21 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
22 const cloudyTools = [
23 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
24 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
25 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
26 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
27 ];
28 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
29 instance.UI.disableTools(cloudyTools);
30 // Set default toolbar group to Annotate
31 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
32 // Set default tool on mobile devices to Pan.
33 if (UIElements.isMobileDevice()) {
34 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
35 }
36
37 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
38 if (searchParams.has('file')) {
39 searchParams.delete('file');
40 history.replaceState(null, '', '?' + searchParams.toString());
41 }
42 });
43
44 instance.Core.annotationManager.enableAnnotationNumbering();
45 instance.UI.NotesPanel.enableAttachmentPreview();
46 // Add the demo-specific functionality
47 customizeUI(instance).then(() => {
48 // Create UI controls after demo is initialized
49 UIElements.createUIControls(instance);
50 });
51 });
52}
53
54const searchParams = new URLSearchParams(window.location.search);
55const history = window.history || window.parent.history || window.top.history;
56
57window.pageNum = 1;
58window.scaleNum = 1.0;
59window.thumbnailName = 'thumbnail';
60window.thumbnailType = 'png';
61
62window.thumbnailOptions = ['PNG', 'JPEG'];
63
64
65const customizeUI = async (instance) => {
66 // Load the default document
67 await instance.UI.loadDocument('https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf');
68};
69
70// Function to handle the Create Thumbnail button click
71window.onThumbnailButtonClick = (instance) => {
72 // Get the document
73 const doc = instance.Core.documentViewer.getDocument();
74
75 // Get the page number from user input
76 let pageNumber = parseInt(window.pageNum, 10);
77
78 // Validate page number
79 if (isNaN(pageNumber) || pageNumber < 1 || pageNumber > doc.getPageCount()) {
80 alert('Please enter a valid page number between 1 and ' + doc.getPageCount());
81 return; // Exit if page number is invalid
82 }
83
84 // Clamp the page number to valid range
85 pageNumber = isNaN(pageNumber) ? 1 : Math.min(Math.max(pageNumber, 1), doc.getPageCount());
86
87 // Get the scale (zoom level) from user input
88 const zoom = parseFloat(window.scaleNum);
89 if (isNaN(zoom) || zoom <= 0 || zoom > 10) {
90 alert('Please enter a valid zoom level between 0.1 and 10');
91 return; // Exit if zoom is invalid
92 }
93
94 // Compensate for device pixel ratio to ensure consistent output across devices
95 // Normalize to standard DPR of 1.0 by dividing by actual DPR
96 const devicePixelRatio = window.devicePixelRatio || 1;
97 const adjustedZoom = zoom / devicePixelRatio;
98
99 console.log(`Device Pixel Ratio: ${devicePixelRatio}`);
100 console.log(`Original zoom: ${zoom}, Adjusted zoom: ${adjustedZoom}`);
101 console.log(`This should produce thumbnails equivalent to DPR=1.0 environment`);
102
103 // Get the file name and type from user input
104 const name = window.thumbnailName;
105 const type = window.thumbnailType;
106
107 // Save to blob using the loadCanvas API
108 doc.loadCanvas({
109 pageNumber,
110 zoom: adjustedZoom, // Use DPI-adjusted zoom for consistent output
111 drawComplete: async (thumbnail) => {
112 // Optionally, comment out "drawAnnotations" to exclude annotations
113 await instance.Core.documentViewer
114 .getAnnotationManager()
115 .drawAnnotations(pageNumber, thumbnail);
116 // thumbnail is a HTMLCanvasElement or HTMLImageElement
117 thumbnail.toBlob(
118 (blob) => {
119 saveAs(blob, name + '.' + type);
120 },
121 'image/' + type,
122 1
123 );
124 },
125 });
126};
127
128// Cleanup function for when the demo is closed or page is unloaded
129const cleanup = (instance) => {
130 if (typeof instance !== 'undefined' && instance.UI) {
131 if (instance.Core.documentViewer.getDocument()) {
132 // Insert any other cleanup code here
133 }
134 console.log('Cleaning up demo');
135 }
136};
137
138// Register cleanup for page unload
139window.addEventListener('beforeunload', () => cleanup(instance));
140window.addEventListener('unload', () => cleanup(instance));
141
142// UI Elements Script Loader
143// Loads the ui-elements.js script
144function loadUIElementsScript() {
145 return new Promise((resolve, reject) => {
146 if (window.UIElements) {
147 console.log('UIElements already loaded');
148 resolve();
149 return;
150 }
151 const script = document.createElement('script');
152 script.src = '/showcase-demos/create-thumbnail/ui-elements.js';
153 script.onload = function () {
154 console.log('✅ UIElements script loaded successfully');
155 resolve();
156 };
157 script.onerror = function () {
158 console.error('Failed to load UIElements script');
159 reject(new Error('Failed to load ui-elements.js'));
160 };
161 document.head.appendChild(script);
162 });
163}
164
165// Load UIElements script first, then initialize WebViewer
166loadUIElementsScript().then(() => {
167 initializeWebViewer();
168}).catch((error) => {
169 console.error('Failed to load UIElements:', error);
170});
171
1// ES6 Compliant Syntax
2// GitHub Copilot v1.0, GPT-4.1, October 15, 2025
3// File: ui-elements.js
4
5// UI Elements class to create and manage custom UI controls
6//
7// Helper code to add controls to the viewer holding the buttons
8// This code creates a container for the buttons, styles them, and adds them to the viewer
9//
10class UIElements {
11 // Function to check if the user is on a mobile device
12 static isMobileDevice = () => {
13 return (
14 /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
15 window.navigator.userAgent
16 ) ||
17 /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
18 window.navigator.userAgent.substring(0, 4)
19 )
20 );
21 }
22
23 // Create and return the page number input field
24 static pageNumberInputField = () => {
25 // Create a container for the input field
26 const container = document.createElement('div');
27 container.className = 'input-container';
28 container.style.display = 'flex';
29
30 // Create and append the label
31 const label = document.createElement('label');
32 label.className = 'input-label';
33 label.textContent = 'Page Number';
34 container.appendChild(label);
35
36 // Create and append the input field
37 const input = document.createElement('input');
38 input.className = 'number-input-field';
39 input.type = 'number';
40 input.value = window.pageNum;
41 input.onchange = (e) => {
42 window.pageNum = e.target.value;
43 };
44 input.min = 1;
45 input.pattern = "\d+";
46 container.appendChild(input);
47
48 return container;
49 }
50
51 // Create and return the scale factor input field
52 static scaleFactorInputField = () => {
53 // Create a container for the input field
54 const container = document.createElement('div');
55 container.className = 'input-container';
56
57 // Create and append the label
58 const label = document.createElement('label');
59 label.className = 'input-label';
60 label.textContent = 'Scale Factor';
61 container.appendChild(label);
62
63 // Create and append the input field
64 const input = document.createElement('input');
65 input.className = 'number-input-field';
66 input.type = 'number';
67 input.value = window.scaleNum;
68 input.onchange = (e) => {
69 window.scaleNum = e.target.value;
70 }
71 input.min = 0.1;
72 input.step = 0.1;
73 input.max = 10;
74 container.appendChild(input);
75
76 return container;
77 }
78
79 // Create and return the file name input field
80 static fileNameInputField = () => {
81 // Create a container for the input field
82 const container = document.createElement('div');
83 container.className = 'input-container';
84
85 // Create and append the label
86 const label = document.createElement('label');
87 label.className = 'input-label';
88 label.textContent = 'File Name';
89 container.appendChild(label);
90
91 // Create and append the input field
92 const input = document.createElement('input');
93 input.className = 'text-input-field';
94 input.type = 'text';
95 input.value = window.thumbnailName;
96 input.onchange = (e) => {
97 window.thumbnailName = e.target.value;
98 };
99 input.required = true;
100 input.minLength = 1;
101 container.appendChild(input);
102
103 return container;
104 }
105
106 // Create and return the image type dropdown field
107 static imageTypeInputField = () => {
108 // Create a container for the dropdown
109 const container = document.createElement('div');
110 container.className = 'input-container';
111
112 // Create and append the label
113 const label = document.createElement('label');
114 label.className = 'input-label';
115 label.textContent = 'Image Type';
116 container.appendChild(label);
117
118 // Create and append the dropdown
119 const select = document.createElement('select');
120 select.className = 'dropdown-input-field';
121 select.value = window.thumbnailType;
122 select.onchange = (e) => {
123 window.thumbnailType = e.target.value;
124 };
125
126 // Add options to the dropdown
127 window.thumbnailOptions.forEach((option) => {
128 const optionElement = document.createElement('option');
129 optionElement.value = option.toLowerCase();
130 optionElement.textContent = option;
131 select.appendChild(optionElement);
132 });
133 container.appendChild(select);
134
135 return container;
136 }
137
138 // Create and return the download button
139 static downloadButton = (instance) => {
140 // Create the button element
141 const button = document.createElement('button');
142 button.className = 'button';
143 button.textContent = 'Download Thumbnail';
144 button.onclick = () => window.onThumbnailButtonClick(instance);
145
146 return button;
147 }
148
149 static createUIControls = (instance) => {
150 // Create a container for all controls
151 const controlsContainer = document.createElement('div');
152 controlsContainer.className = 'controls-container';
153
154 controlsContainer.appendChild(this.pageNumberInputField());
155 controlsContainer.appendChild(this.scaleFactorInputField());
156 controlsContainer.appendChild(this.fileNameInputField());
157 controlsContainer.appendChild(this.imageTypeInputField());
158 controlsContainer.appendChild(this.downloadButton(instance));
159
160 // Add the controls container to the viewer element
161 const element = document.getElementById('viewer');
162 element.insertBefore(controlsContainer, element.firstChild);
163
164 console.log('✅ UI controls created');
165 };
166}
167
168// Make UIElements globally available
169window.UIElements = UIElements;
1/* CSS standard Compliant Syntax */
2/* GitHub Copilot v1.0, GPT-4.1, October 15, 2025 */
3/* File: index.css */
4
5/* Button Styles */
6.button {
7 background-color: #007bff;
8 margin: 0 10px;
9 padding: 5px 10px;
10 border: 1px solid #ccc;
11 border-radius: 4px;
12 cursor: pointer;
13 font-size: 14px;
14 font-weight: bold;
15 transition: all 0.2s ease;
16 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
17 color: white;
18}
19
20.button:hover {
21 background-color: #0056b3;
22 transform: translateY(-1px);
23 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
24}
25
26.button:active {
27 transform: translateY(1px);
28 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
29}
30
31/* Text Inputs Container */
32.text-inputs-container {
33 display: flex;
34 flex-direction: row;
35 flex-wrap: wrap;
36 gap: 10px;
37}
38
39/* Input Controls */
40.input-container {
41 display: flex;
42 flex-direction: column;
43 align-items: flex-start;
44 margin-bottom: 15px;
45}
46
47.input-label {
48 display: block;
49 text-align: start;
50 font-size: 14px;
51 margin-inline-end: 12px;
52 margin-bottom: 8px;
53 font-weight: bold;
54 transition: background-color border-color color fill stroke opacity box-shadow transform;
55 transition-duration: 200ms;
56 opacity: 1;
57 color: #485056;
58 line-height: 1.5;
59 width: 50%;
60 white-space: nowrap;
61}
62
63.number-input-field,
64.text-input-field,
65.dropdown-input-field {
66 width: 100%;
67 min-width: 0px;
68 outline: transparent solid 2px;
69 outline-offset: 2px;
70 position: relative;
71 appearance: none;
72 transition: background-color border-color color fill stroke opacity box-shadow transform;
73 transition-duration: 200ms;
74 border-radius: 6px;
75 background-color: white;
76 color: #485056;
77 border-width: 1px;
78 border-style: solid;
79 border-image: initial;
80 border-color: #cfd4da;
81 box-sizing: border-box;
82 font-size: 14px;
83 padding-inline-start: 16px;
84 padding-inline-end: 16px;
85 height: 40px;
86}
87
88.number-input-field:focus,
89.text-input-field:focus,
90.dropdown-input-field:focus {
91 border-color: #007bff;
92 box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
93}
94
95/* Controls Container */
96.controls-container {
97 display: flex;
98 flex-direction: row;
99 align-items: center;
100 gap: 15px;
101 margin: 5px 0;
102 padding: 16px;
103 padding-bottom: 5px;
104 border-bottom: 1px solid #DFE1E6;
105 background-color: rgba(112, 198, 255, 0.2);
106}
107
108
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales