How to Scan Documents Inside a Shadow DOM Web Component with Dynamic Web TWAIN
Shadow DOM is a web standard designed to encapsulate HTML and CSS into web components. It allows for the creation of separate DOM trees and specifies how these trees interact with each other in a document. Polymer is a library developed by Google to make it easier to create reusable web components. It utilizes Shadow DOM as a core part of its architecture. This article shares how to integrate Dynamic Web TWAIN into a Polymer project for implementing document scanning functions in web browsers.
What you’ll build: A browser-based document scanning application that runs inside a Shadow DOM web component, using Polymer and the Dynamic Web TWAIN SDK to acquire scanned images from TWAIN-compatible scanners.
Key Takeaways
- Dynamic Web TWAIN can be embedded inside Shadow DOM by binding the viewer to a
shadowRootelement instead of the globaldocument. - The
CreateDWTObjectExmethod withViewer.bind(this.shadowRoot.querySelector(...))is the key pattern for Shadow DOM integration. - Polymer’s
ready()lifecycle hook is the correct place to initialize the Dynamic Web TWAIN object so that the Shadow DOM tree is fully rendered. - This approach applies to any Shadow DOM-based framework, not only Polymer — the same
shadowRootbinding pattern works with Lit, vanilla Web Components, and other libraries.
Common Developer Questions
- How do I scan documents inside a Shadow DOM web component?
- Why does Dynamic Web TWAIN not render when used inside Shadow DOM, and how do I fix it?
- How do I bind the Dynamic Web TWAIN viewer to a shadowRoot element instead of the main document?
Prerequisites
-
Polymer CLI:
npm i -g polymer-cli -
Get a 30-day free trial license for Dynamic Web TWAIN
Step 1: Scaffold a Polymer Project
mkdir polymer-document-scan
cd polymer-document-scan
polymer init polymer-3-starter-kit
npm start

Step 2: Install the Dynamic Web TWAIN SDK
In the project directory, install the SDK:
npm install dwt
Step 3: Include the Dynamic Web TWAIN Script in index.html
<script src="node_modules/dwt/dist/dynamsoft.webtwain.min.js"></script>
Step 4: Add the Scanner UI to the Polymer Component Template
Add a div element and a button element to the HTML template in src/my-view1.js:
class MyView1 extends PolymerElement {
MyView1() {
this.dwtObj;
this.deviceList = [];
}
static get template() {
return html`
<style include="shared-styles">
:host {
display: block;
padding: 10px;
}
</style>
<div class="card">
<h1>Web Document Scan</h1>
<select size="1" id="source" style="position: relative; width: 220px;"></select>
<div id="dwtcontrolContainer"></div>
<button on-click="handleClick">scan</button>
</div>
`;
}
handleClick() {
if (DWObject) {
}
}
}
- The
divelement with the IDdwtcontrolContaineris used to initialize the Dynamic Web TWAIN object. - The
buttonelement with thehandleClickevent triggers the document scanning process.
Step 5: Initialize Dynamic Web TWAIN Inside the Shadow DOM
Instantiate a Dynamic Web TWAIN object with a valid license key and the div element in the ready() method:
ready() {
super.ready();
// TODO: initialization
this.initializeDWT();
}
initializeDWT() {
if (Dynamsoft) {
Dynamsoft.DWT.AutoLoad = true;
Dynamsoft.DWT.UseLocalService = true;
// https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform
Dynamsoft.DWT.ProductKey = 'LICENSE-KEY';
Dynamsoft.DWT.ResourcesPath = 'node_modules/dwt/dist/';
Dynamsoft.DWT.CreateDWTObjectEx({ WebTwainId: 'Viewer' }, (obj) => {
this.dwtObj = obj;
obj.Viewer.bind(this.shadowRoot.querySelector('#dwtcontrolContainer'));
obj.Viewer.width = 560;
obj.Viewer.height = 600;
obj.Viewer.show();
let select = this.shadowRoot.querySelector('#source');
let deviceList = this.deviceList = [];
obj.GetDevicesAsync().then(function (devices) {
for (var i = 0; i < devices.length; i++) {
let option = document.createElement('option');
option.value = devices[i].displayName;
option.text = devices[i].displayName;
deviceList.push(devices[i]);
select.appendChild(option);
}
}).catch(function (exp) {
alert(exp.message);
});
}, function (e) {
console.log(e)
});
}
}
Note: You must set the ResourcesPath to the path where the Dynamic Web TWAIN resources are located. The CreateDWTObjectEx method creates a Dynamic Web TWAIN object. The bindmethod binds the object to the div element. The GetDevicesAsync method is used to retrieve the list of available devices.
Step 6: Trigger Document Scanning on Button Click
Implement the handleClick method to trigger the document scanning process:
handleClick() {
var obj = this.dwtObj;
if (obj) {
obj.SelectDeviceAsync(this.deviceList[this.shadowRoot.querySelector('#source').selectedIndex]).then(function () {
return obj.AcquireImageAsync({
IfShowUI: false,
IfCloseSourceAfterAcquire: true
});
}).catch(function (exp) {
alert(exp.message);
});
}
}
Step 7: Run and Test the Scanning App
npm start
Open a web browser and navigate to http://localhost:8081/ to see the document scanning app implemented in Polymer.

Common Issues and Edge Cases
- Dynamic Web TWAIN viewer does not appear inside Shadow DOM: This happens when you use
document.getElementByIdinstead ofthis.shadowRoot.querySelector. Shadow DOM encapsulation prevents global DOM queries from finding elements inside the component. Always usethis.shadowRoot.querySelector('#dwtcontrolContainer')when binding the viewer. - Scanner list is empty or
GetDevicesAsyncreturns no devices: Ensure the Dynamsoft Service is installed and running on the client machine. Dynamic Web TWAIN requires its local service for TWAIN scanner communication. Check thatUseLocalServiceis set totrue. - Resource files fail to load (404 errors for
.wasmor worker files): Verify thatDynamsoft.DWT.ResourcesPathpoints to the correctnode_modules/dwt/dist/directory relative to your servedindex.html. Path mismatches are the most common setup failure.
Source Code
https://github.com/yushulx/web-twain-document-scan-management/tree/main/examples/polymer