diff --git a/README.md b/README.md index 1441ce5..d138ccd 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,9 @@ We are working continuously on adding support for more and more platforms. If yo ### Framework support The library can be easily used with several other frameworks, I have been adding examples for a few of them and would continue to add more. -|| | | | -| -------- | -------- | -------- | -------- | -------- | -| [Html5](./examples/html5) | [VueJs](./examples/vuejs) | [ElectronJs](./examples/electron) | [React](https://github.com/scanapp-org/html5-qrcode-react) | [Lit](./examples/lit) +|| | | | | +| -------- | -------- | -------- | -------- | -------- | -------- | +| [Html5](./examples/html5) | [VueJs](./examples/vuejs) | [ElectronJs](./examples/electron) | [React](https://github.com/scanapp-org/html5-qrcode-react) | [Lit](./examples/lit) | [Shadow DOM](./examples/html5-shadow-dom) ### Supported Code formats Code scanning is dependent on [Zxing-js](https://github.com/zxing-js/library) library. We will be working on top of it to add support for more types of code scanning. If you feel a certain type of code would be helpful to have, please file a feature request. diff --git a/examples/html5-shadow-dom/README.md b/examples/html5-shadow-dom/README.md new file mode 100644 index 0000000..f5bc525 --- /dev/null +++ b/examples/html5-shadow-dom/README.md @@ -0,0 +1,51 @@ +# html5-qrcode with HTML Element + +## Include the js library in your project +```html + +``` + +## Create the component in JavaScript +```js +class CustomComponent extends HTMLElement { + constructor() { + // Always call super first in constructor + super(); + + // Create a shadow root + const shadow = this.attachShadow({ mode: 'open' }); + + // Create elements + const wrapper = document.createElement('div'); + const title = document.createElement('h2'); + title.innerText = 'HTML5 qr-code Scanner with shadow dom components'; + const idReader = document.createElement('div'); + idReader.className = 'qr-reader'; + idReader.style.width = '500px'; + wrapper.appendChild(title); + wrapper.appendChild(idReader); + // Attach the created elements to the shadow dom + shadow.appendChild(wrapper); + } +} + +// Define the new element +customElements.define('custom-component', CustomComponent); +``` + +## Query an HTML element from DOM or Shadow DOM and pass it to the library + +```js +const myCustomComponent = document.querySelector("custom-component"); +const childComponentQrReaderElement = myCustomComponent.shadowRoot.querySelector("div.qr-reader"); +const html5QrCodeScanner = new Html5QrcodeScanner(childComponentQrReaderElement, { + fps: 10, + qrbox: 250, +}); +html5QrCodeScanner.render(onScanSuccess); +``` + +### Contributors +| Name | Profile| +| ----- | ------ | +| Bilal EL CHAMI | [@bilal-elchami](https://github.com/bilal-elchami) | \ No newline at end of file diff --git a/examples/html5-shadow-dom/index.html b/examples/html5-shadow-dom/index.html new file mode 100644 index 0000000..9ce0964 --- /dev/null +++ b/examples/html5-shadow-dom/index.html @@ -0,0 +1,67 @@ + + + + Html-Qrcode Demo + + +

This is a Shadow Dom example

+ + + + + + + diff --git a/src/html5-qrcode-scanner.ts b/src/html5-qrcode-scanner.ts index 028262f..0c9b5f5 100644 --- a/src/html5-qrcode-scanner.ts +++ b/src/html5-qrcode-scanner.ts @@ -51,7 +51,7 @@ import { } from "./ui"; import { - CameraPermissions + CameraPermissions } from "./camera/permissions"; import { Html5QrcodeScannerState } from "./state-manager"; @@ -179,7 +179,7 @@ function toHtml5QrcodeFullConfig( export class Html5QrcodeScanner { //#region private fields - private elementId: string; + private container: HTMLElement; private config: Html5QrcodeScannerConfig; private verbose: boolean; private currentScanType: Html5QrcodeScanType; @@ -188,7 +188,7 @@ export class Html5QrcodeScanner { private scanTypeSelector: ScanTypeSelector; private logger: Logger; - // Initally null fields. + // Initially null fields. private html5Qrcode: Html5Qrcode | undefined; private qrCodeSuccessCallback: QrcodeSuccessCallback | undefined; private qrCodeErrorCallback: QrcodeErrorCallback | undefined; @@ -196,6 +196,13 @@ export class Html5QrcodeScanner { private cameraScanImage: HTMLImageElement | null = null; private fileScanImage: HTMLImageElement | null = null; private fileSelectionUi: FileSelectionUi | null = null; + // DOM Elements + private scpCameraScanRegion: HTMLDivElement | null = null; + private dashboardSection: HTMLDivElement | null = null; + private permissionButton: HTMLButtonElement | null = null; + private headerMessageContainer: HTMLDivElement | null = null; + private qrCodeScanRegion: HTMLDivElement | null = null; + private switchScanTypeLink: HTMLAnchorElement | null = null; //#endregion /** @@ -208,15 +215,39 @@ export class Html5QrcodeScanner { public constructor( elementId: string, config: Html5QrcodeScannerConfig | undefined, + verbose: boolean | undefined); + + /** + * Creates instance of this class. + * + * @param element The HTML DOM element. + * @param config Extra configurations to tune the code scanner. + * @param verbose - If true, all logs would be printed to console. + */ + public constructor( + element: HTMLElement, + config: Html5QrcodeScannerConfig | undefined, + verbose: boolean | undefined); + + /** + * Creates instance of this class. + * + * @param elementOrId The HTML DOM element or its Id. + * @param config Extra configurations to tune the code scanner. + * @param verbose - If true, all logs would be printed to console. + */ + public constructor( + elementOrId: HTMLElement | string, + config: Html5QrcodeScannerConfig | undefined, verbose: boolean | undefined) { - this.elementId = elementId; + if (typeof elementOrId === "string") { + this.container = this.validateInputAsStringId(elementOrId); + } else { + this.container = this.validateInputAsHTMLElement(elementOrId); + } this.config = this.createConfig(config); this.verbose = verbose === true; - if (!document.getElementById(elementId)) { - throw `HTML Element with id=${elementId} not found`; - } - this.scanTypeSelector = new ScanTypeSelector( this.config.supportedScanTypes); this.currentScanType = this.scanTypeSelector.getDefaultScanType(); @@ -246,36 +277,35 @@ export class Html5QrcodeScanner { // Add wrapper to success callback. this.qrCodeSuccessCallback = (decodedText: string, result: Html5QrcodeResult) => { - if (qrCodeSuccessCallback) { - qrCodeSuccessCallback(decodedText, result); - } else { - if (this.lastMatchFound === decodedText) { - return; - } + if (qrCodeSuccessCallback) { + qrCodeSuccessCallback(decodedText, result); + } else { + if (this.lastMatchFound === decodedText) { + return; + } - this.lastMatchFound = decodedText; - this.setHeaderMessage( - Html5QrcodeScannerStrings.lastMatch(decodedText), - Html5QrcodeScannerStatus.STATUS_SUCCESS); - } - }; + this.lastMatchFound = decodedText; + this.setHeaderMessage( + Html5QrcodeScannerStrings.lastMatch(decodedText), + Html5QrcodeScannerStatus.STATUS_SUCCESS); + } + }; // Add wrapper to failure callback this.qrCodeErrorCallback = (errorMessage: string, error: Html5QrcodeError) => { - if (qrCodeErrorCallback) { - qrCodeErrorCallback(errorMessage, error); - } - }; + if (qrCodeErrorCallback) { + qrCodeErrorCallback(errorMessage, error); + } + }; - const container = document.getElementById(this.elementId); - if (!container) { - throw `HTML Element with id=${this.elementId} not found`; + if (!this.container) { + throw "HTML Element not found"; } - container.innerHTML = ""; - this.createBasicLayout(container!); + this.container.innerHTML = ""; + this.createBasicLayout(this.container!); this.html5Qrcode = new Html5Qrcode( - this.getScanRegionId(), + this.qrCodeScanRegion!, toHtml5QrcodeFullConfig(this.config, this.verbose)); } @@ -298,7 +328,7 @@ export class Html5QrcodeScanner { this.getHtml5QrcodeOrFail().pause(shouldPauseVideo); } - + /** * Resumes the paused scan. * @@ -323,7 +353,7 @@ export class Html5QrcodeScanner { * @returns state of type {@link Html5QrcodeScannerState}. */ public getState(): Html5QrcodeScannerState { - return this.getHtml5QrcodeOrFail().getState(); + return this.getHtml5QrcodeOrFail().getState(); } /** @@ -334,10 +364,9 @@ export class Html5QrcodeScanner { */ public clear(): Promise { const emptyHtmlContainer = () => { - const mainContainer = document.getElementById(this.elementId); - if (mainContainer) { - mainContainer.innerHTML = ""; - this.resetBasicLayout(mainContainer); + if (this.container) { + this.container.innerHTML = ""; + this.resetBasicLayout(this.container); } } @@ -425,13 +454,39 @@ export class Html5QrcodeScanner { * fails otherwise. * @throws error if the scanning is not in running state. */ - public applyVideoConstraints(videoConstaints: MediaTrackConstraints) + public applyVideoConstraints(videoConstraints: MediaTrackConstraints) : Promise { - return this.getHtml5QrcodeOrFail().applyVideoConstraints(videoConstaints); + return this.getHtml5QrcodeOrFail().applyVideoConstraints(videoConstraints); } //#endregion //#region Private methods + + /** + * Verifies if the element id is valid and returns the corresponding HTML Element. + * @param elementId Id of the HTML element. + * @returns a valid HTML Element. + */ + private validateInputAsStringId(elementId: string): HTMLElement { + const element = document.getElementById(elementId) as HTMLElement; + if (!element) { + throw `HTML Element with id=${elementId} not found`; + } + return element; + } + + /** + * Verifies if the parameter is a valid HTML Element and returns it. + * @param element The HTML DOM Element. + * @returns a valid HTML Element. + */ + private validateInputAsHTMLElement(element: HTMLElement): HTMLElement { + if (!element || !(element instanceof HTMLElement)) { + throw "HTML Element is not valid"; + } + return element; + } + private getHtml5QrcodeOrFail() { if (!this.html5Qrcode) { throw "Code scanner not initialized."; @@ -475,13 +530,13 @@ export class Html5QrcodeScanner { parent.style.border = "1px solid silver"; this.createHeader(parent); - const qrCodeScanRegion = document.createElement("div"); + this.qrCodeScanRegion = document.createElement("div"); const scanRegionId = this.getScanRegionId(); - qrCodeScanRegion.id = scanRegionId; - qrCodeScanRegion.style.width = "100%"; - qrCodeScanRegion.style.minHeight = "100px"; - qrCodeScanRegion.style.textAlign = "center"; - parent.appendChild(qrCodeScanRegion); + this.qrCodeScanRegion.id = scanRegionId; + this.qrCodeScanRegion.style.width = "100%"; + this.qrCodeScanRegion.style.minHeight = "100px"; + this.qrCodeScanRegion.style.textAlign = "center"; + parent.appendChild(this.qrCodeScanRegion); if (ScanTypeSelector.isCameraScanType(this.currentScanType)) { this.insertCameraScanImageToScanRegion(); } else { @@ -518,24 +573,24 @@ export class Html5QrcodeScanner { let libraryInfo = new LibraryInfoContainer(); libraryInfo.renderInto(header); - const headerMessageContainer = document.createElement("div"); - headerMessageContainer.id = this.getHeaderMessageContainerId(); - headerMessageContainer.style.display = "none"; - headerMessageContainer.style.textAlign = "center"; - headerMessageContainer.style.fontSize = "14px"; - headerMessageContainer.style.padding = "2px 10px"; - headerMessageContainer.style.margin = "4px"; - headerMessageContainer.style.borderTop = "1px solid #f6f6f6"; - header.appendChild(headerMessageContainer); + this.headerMessageContainer = document.createElement("div"); + this.headerMessageContainer.id = this.getHeaderMessageContainerId(); + this.headerMessageContainer.style.display = "none"; + this.headerMessageContainer.style.textAlign = "center"; + this.headerMessageContainer.style.fontSize = "14px"; + this.headerMessageContainer.style.padding = "2px 10px"; + this.headerMessageContainer.style.margin = "4px"; + this.headerMessageContainer.style.borderTop = "1px solid #f6f6f6"; + header.appendChild(this.headerMessageContainer); } private createSection(dashboard: HTMLElement) { - const section = document.createElement("div"); - section.id = this.getDashboardSectionId(); - section.style.width = "100%"; - section.style.padding = "10px 0px 10px 0px"; - section.style.textAlign = "left"; - dashboard.appendChild(section); + this.dashboardSection = document.createElement("div"); + this.dashboardSection.id = this.getDashboardSectionId(); + this.dashboardSection.style.width = "100%"; + this.dashboardSection.style.padding = "10px 0px 10px 0px"; + this.dashboardSection.style.textAlign = "left"; + dashboard.appendChild(this.dashboardSection); } private createCameraListUi( @@ -572,7 +627,7 @@ export class Html5QrcodeScanner { }).catch((error) => { $this.persistedDataManager.setHasPermission( /* hasPermission */ false); - + if (requestPermissionButton) { requestPermissionButton.disabled = false; } else { @@ -621,21 +676,21 @@ export class Html5QrcodeScanner { && this.persistedDataManager.hasCameraPermissions()) { CameraPermissions.hasPermissions().then( (hasPermissions: boolean) => { - if (hasPermissions) { - $this.createCameraListUi( - scpCameraScanRegion, requestPermissionContainer); - } else { - $this.persistedDataManager.setHasPermission( + if (hasPermissions) { + $this.createCameraListUi( + scpCameraScanRegion, requestPermissionContainer); + } else { + $this.persistedDataManager.setHasPermission( /* hasPermission */ false); + $this.createPermissionButton( + scpCameraScanRegion, requestPermissionContainer); + } + }).catch((_: any) => { + $this.persistedDataManager.setHasPermission( + /* hasPermission */ false); $this.createPermissionButton( scpCameraScanRegion, requestPermissionContainer); - } - }).catch((_: any) => { - $this.persistedDataManager.setHasPermission( - /* hasPermission */ false); - $this.createPermissionButton( - scpCameraScanRegion, requestPermissionContainer); - }); + }); return; } @@ -644,15 +699,14 @@ export class Html5QrcodeScanner { } private createSectionControlPanel() { - const section = document.getElementById(this.getDashboardSectionId())!; const sectionControlPanel = document.createElement("div"); - section.appendChild(sectionControlPanel); - const scpCameraScanRegion = document.createElement("div"); - scpCameraScanRegion.id = this.getDashboardSectionCameraScanRegionId(); - scpCameraScanRegion.style.display + this.dashboardSection!.appendChild(sectionControlPanel); + this.scpCameraScanRegion = document.createElement("div"); + this.scpCameraScanRegion.id = this.getDashboardSectionCameraScanRegionId(); + this.scpCameraScanRegion.style.display = ScanTypeSelector.isCameraScanType(this.currentScanType) - ? "block" : "none"; - sectionControlPanel.appendChild(scpCameraScanRegion); + ? "block" : "none"; + sectionControlPanel.appendChild(this.scpCameraScanRegion); // Web browsers require the users to grant explicit permissions before // giving camera access. We need to render a button to request user @@ -660,7 +714,7 @@ export class Html5QrcodeScanner { // Assuming when the object is created permission is needed. const requestPermissionContainer = document.createElement("div"); requestPermissionContainer.style.textAlign = "center"; - scpCameraScanRegion.appendChild(requestPermissionContainer); + this.scpCameraScanRegion.appendChild(requestPermissionContainer); // TODO(minhazav): If default scan type is file, the permission or // camera access shouldn't start unless user explicitly switches to @@ -668,7 +722,7 @@ export class Html5QrcodeScanner { if (this.scanTypeSelector.isCameraScanRequired()) { this.createPermissionsUi( - scpCameraScanRegion, requestPermissionContainer); + this.scpCameraScanRegion, requestPermissionContainer); } this.renderFileScanUi(sectionControlPanel); @@ -709,41 +763,39 @@ export class Html5QrcodeScanner { private renderCameraSelection(cameras: Array) { const $this = this; - const scpCameraScanRegion = document.getElementById( - this.getDashboardSectionCameraScanRegionId())!; - scpCameraScanRegion.style.textAlign = "center"; + this.scpCameraScanRegion!.style.textAlign = "center"; // Hide by default. let cameraZoomUi: CameraZoomUi = CameraZoomUi.create( - scpCameraScanRegion, /* renderOnCreate= */ false); + this.scpCameraScanRegion as HTMLElement, /* renderOnCreate= */ false); const renderCameraZoomUiIfSupported = (cameraCapabilities: CameraCapabilities) => { - let zoomCapability = cameraCapabilities.zoomFeature(); - if (!zoomCapability.isSupported()) { - return; - } + let zoomCapability = cameraCapabilities.zoomFeature(); + if (!zoomCapability.isSupported()) { + return; + } - // Supported. - cameraZoomUi.setOnCameraZoomValueChangeCallback((zoomValue) => { - zoomCapability.apply(zoomValue); - }); - let defaultZoom = 1; - if (this.config.defaultZoomValueIfSupported) { - defaultZoom = this.config.defaultZoomValueIfSupported; - } - defaultZoom = clip( - defaultZoom, zoomCapability.min(), zoomCapability.max()); - cameraZoomUi.setValues( - zoomCapability.min(), - zoomCapability.max(), - defaultZoom, - zoomCapability.step(), - ); - cameraZoomUi.show(); - }; + // Supported. + cameraZoomUi.setOnCameraZoomValueChangeCallback((zoomValue) => { + zoomCapability.apply(zoomValue); + }); + let defaultZoom = 1; + if (this.config.defaultZoomValueIfSupported) { + defaultZoom = this.config.defaultZoomValueIfSupported; + } + defaultZoom = clip( + defaultZoom, zoomCapability.min(), zoomCapability.max()); + cameraZoomUi.setValues( + zoomCapability.min(), + zoomCapability.max(), + defaultZoom, + zoomCapability.step(), + ); + cameraZoomUi.show(); + }; let cameraSelectUi: CameraSelectionUi = CameraSelectionUi.create( - scpCameraScanRegion, cameras); + this.scpCameraScanRegion as HTMLElement, cameras); // Camera Action Buttons. const cameraActionContainer = document.createElement("span"); @@ -767,34 +819,34 @@ export class Html5QrcodeScanner { let torchButton: TorchButton; const createAndShowTorchButtonIfSupported = (cameraCapabilities: CameraCapabilities) => { - if (!cameraCapabilities.torchFeature().isSupported()) { - // Torch not supported, ignore. - if (torchButton) { - torchButton.hide(); + if (!cameraCapabilities.torchFeature().isSupported()) { + // Torch not supported, ignore. + if (torchButton) { + torchButton.hide(); + } + return; } - return; - } - if (!torchButton) { - torchButton = TorchButton.create( - cameraActionContainer, - cameraCapabilities.torchFeature(), - { display: "none", marginLeft: "5px" }, - // Callback in case of torch action failure. - (errorMessage) => { - $this.setHeaderMessage( - errorMessage, - Html5QrcodeScannerStatus.STATUS_WARNING); - } - ); - } else { - torchButton.updateTorchCapability( - cameraCapabilities.torchFeature()); - } - torchButton.show(); - }; + if (!torchButton) { + torchButton = TorchButton.create( + cameraActionContainer, + cameraCapabilities.torchFeature(), + { display: "none", marginLeft: "5px" }, + // Callback in case of torch action failure. + (errorMessage) => { + $this.setHeaderMessage( + errorMessage, + Html5QrcodeScannerStatus.STATUS_WARNING); + } + ); + } else { + torchButton.updateTorchCapability( + cameraCapabilities.torchFeature()); + } + torchButton.show(); + }; - scpCameraScanRegion.appendChild(cameraActionContainer); + this.scpCameraScanRegion!.appendChild(cameraActionContainer); const resetCameraActionStartButton = (shouldShow: boolean) => { if (!shouldShow) { @@ -821,7 +873,7 @@ export class Html5QrcodeScanner { if (this.scanTypeSelector.hasMoreThanOneScanType()) { $this.showHideScanTypeSwapLink(false); } - $this.resetHeaderMessage(); + $this.resetHeaderMessage(); // Attempt starting the camera. const cameraId = cameraSelectUi.getValue(); @@ -872,10 +924,10 @@ export class Html5QrcodeScanner { .then((_) => { // Swap link is required if more than one scan types are // required. - if(this.scanTypeSelector.hasMoreThanOneScanType()) { + if (this.scanTypeSelector.hasMoreThanOneScanType()) { $this.showHideScanTypeSwapLink(true); } - + cameraSelectUi.enable(); cameraActionStartButton.disabled = false; cameraActionStopButton.style.display = "none"; @@ -913,19 +965,17 @@ export class Html5QrcodeScanner { const TEXT_IF_FILE_SCAN_SELECTED = Html5QrcodeScannerStrings.textIfFileScanSelected(); - // TODO(minhaz): Export this as an UI element. - const section = document.getElementById(this.getDashboardSectionId())!; const switchContainer = document.createElement("div"); switchContainer.style.textAlign = "center"; - const switchScanTypeLink + this.switchScanTypeLink = BaseUiElementFactory.createElement( "span", this.getDashboardSectionSwapLinkId()); - switchScanTypeLink.style.textDecoration = "underline"; - switchScanTypeLink.style.cursor = "pointer"; - switchScanTypeLink.innerText + this.switchScanTypeLink.style.textDecoration = "underline"; + this.switchScanTypeLink.style.cursor = "pointer"; + this.switchScanTypeLink.innerText = ScanTypeSelector.isCameraScanType(this.currentScanType) - ? TEXT_IF_CAMERA_SCAN_SELECTED : TEXT_IF_FILE_SCAN_SELECTED; - switchScanTypeLink.addEventListener("click", function () { + ? TEXT_IF_CAMERA_SCAN_SELECTED : TEXT_IF_FILE_SCAN_SELECTED; + this.switchScanTypeLink.addEventListener("click", function () { // TODO(minhazav): Abstract this to a different library. if (!$this.sectionSwapAllowed) { if ($this.verbose) { @@ -939,21 +989,21 @@ export class Html5QrcodeScanner { $this.resetHeaderMessage(); $this.fileSelectionUi!.resetValue(); $this.sectionSwapAllowed = false; - + if (ScanTypeSelector.isCameraScanType($this.currentScanType)) { // Swap to file based scanning. $this.clearScanRegion(); - $this.getCameraScanRegion().style.display = "none"; + $this.scpCameraScanRegion!.style.display = "none"; $this.fileSelectionUi!.show(); - switchScanTypeLink.innerText = TEXT_IF_FILE_SCAN_SELECTED; + $this.switchScanTypeLink!.innerText = TEXT_IF_FILE_SCAN_SELECTED; $this.currentScanType = Html5QrcodeScanType.SCAN_TYPE_FILE; $this.insertFileScanImageToScanRegion(); } else { // Swap to camera based scanning. $this.clearScanRegion(); - $this.getCameraScanRegion().style.display = "block"; + $this.scpCameraScanRegion!.style.display = "block"; $this.fileSelectionUi!.hide(); - switchScanTypeLink.innerText = TEXT_IF_CAMERA_SCAN_SELECTED; + $this.switchScanTypeLink!.innerText = TEXT_IF_CAMERA_SCAN_SELECTED; $this.currentScanType = Html5QrcodeScanType.SCAN_TYPE_CAMERA; $this.insertCameraScanImageToScanRegion(); @@ -962,8 +1012,8 @@ export class Html5QrcodeScanner { $this.sectionSwapAllowed = true; }); - switchContainer.appendChild(switchScanTypeLink); - section.appendChild(switchContainer); + switchContainer.appendChild(this.switchScanTypeLink); + this.dashboardSection!.appendChild(switchContainer); } // Start camera scanning automatically when swapping to camera based scan @@ -973,33 +1023,29 @@ export class Html5QrcodeScanner { if (this.persistedDataManager.hasCameraPermissions()) { CameraPermissions.hasPermissions().then( (hasPermissions: boolean) => { - if (hasPermissions) { - // Start feed. - // Assuming at this point the permission button exists. - let permissionButton = document.getElementById( - $this.getCameraPermissionButtonId()); - if (!permissionButton) { - this.logger.logError( - "Permission button not found, fail;"); - throw "Permission button not found"; + if (hasPermissions) { + // Start feed. + // Assuming at this point the permission button exists. + if (!this.permissionButton) { + this.logger.logError( + "Permission button not found, fail;"); + throw "Permission button not found"; + } + this.permissionButton.click(); + } else { + $this.persistedDataManager.setHasPermission( + /* hasPermission */ false); } - permissionButton.click(); - } else { + }).catch((_: any) => { $this.persistedDataManager.setHasPermission( - /* hasPermission */ false); - } - }).catch((_: any) => { - $this.persistedDataManager.setHasPermission( /* hasPermission */ false); - }); + }); return; } } private resetHeaderMessage() { - const messageDiv = document.getElementById( - this.getHeaderMessageContainerId())!; - messageDiv.style.display = "none"; + this.headerMessageContainer!.style.display = "none"; } private setHeaderMessage( @@ -1008,23 +1054,22 @@ export class Html5QrcodeScanner { scannerStatus = Html5QrcodeScannerStatus.STATUS_DEFAULT; } - const messageDiv = this.getHeaderMessageDiv(); - messageDiv.innerText = messageText; - messageDiv.style.display = "block"; + this.headerMessageContainer!.innerText = messageText; + this.headerMessageContainer!.style.display = "block"; switch (scannerStatus) { case Html5QrcodeScannerStatus.STATUS_SUCCESS: - messageDiv.style.background = "rgba(106, 175, 80, 0.26)"; - messageDiv.style.color = "#477735"; + this.headerMessageContainer!.style.background = "rgba(106, 175, 80, 0.26)"; + this.headerMessageContainer!.style.color = "#477735"; break; case Html5QrcodeScannerStatus.STATUS_WARNING: - messageDiv.style.background = "rgba(203, 36, 49, 0.14)"; - messageDiv.style.color = "#cb2431"; + this.headerMessageContainer!.style.background = "rgba(203, 36, 49, 0.14)"; + this.headerMessageContainer!.style.color = "#cb2431"; break; case Html5QrcodeScannerStatus.STATUS_DEFAULT: default: - messageDiv.style.background = "rgba(0, 0, 0, 0)"; - messageDiv.style.color = "rgb(17, 17, 17)"; + this.headerMessageContainer!.style.background = "rgba(0, 0, 0, 0)"; + this.headerMessageContainer!.style.color = "rgb(17, 17, 17)"; break; } } @@ -1036,26 +1081,24 @@ export class Html5QrcodeScanner { } this.sectionSwapAllowed = shouldDisplay; - this.getDashboardSectionSwapLink().style.display + this.switchScanTypeLink!.style.display = shouldDisplay ? "inline-block" : "none"; } } private insertCameraScanImageToScanRegion() { const $this = this; - const qrCodeScanRegion = document.getElementById( - this.getScanRegionId())!; if (this.cameraScanImage) { - qrCodeScanRegion.innerHTML = "
"; - qrCodeScanRegion.appendChild(this.cameraScanImage); + this.qrCodeScanRegion!.innerHTML = "
"; + this.qrCodeScanRegion!.appendChild(this.cameraScanImage); return; } this.cameraScanImage = new Image; this.cameraScanImage.onload = (_) => { - qrCodeScanRegion.innerHTML = "
"; - qrCodeScanRegion.appendChild($this.cameraScanImage!); + this.qrCodeScanRegion!.innerHTML = "
"; + this.qrCodeScanRegion!.appendChild($this.cameraScanImage!); } this.cameraScanImage.width = 64; this.cameraScanImage.style.opacity = "0.8"; @@ -1065,19 +1108,16 @@ export class Html5QrcodeScanner { private insertFileScanImageToScanRegion() { const $this = this; - const qrCodeScanRegion = document.getElementById( - this.getScanRegionId())!; - if (this.fileScanImage) { - qrCodeScanRegion.innerHTML = "
"; - qrCodeScanRegion.appendChild(this.fileScanImage); + this.qrCodeScanRegion!.innerHTML = "
"; + this.qrCodeScanRegion!.appendChild(this.fileScanImage); return; } this.fileScanImage = new Image; this.fileScanImage.onload = (_) => { - qrCodeScanRegion.innerHTML = "
"; - qrCodeScanRegion.appendChild($this.fileScanImage!); + this.qrCodeScanRegion!.innerHTML = "
"; + this.qrCodeScanRegion!.appendChild($this.fileScanImage!); } this.fileScanImage.width = 64; this.fileScanImage.style.opacity = "0.8"; @@ -1086,18 +1126,16 @@ export class Html5QrcodeScanner { } private clearScanRegion() { - const qrCodeScanRegion = document.getElementById( - this.getScanRegionId())!; - qrCodeScanRegion.innerHTML = ""; + this.qrCodeScanRegion!.innerHTML = ""; } //#region state getters private getDashboardSectionId(): string { - return `${this.elementId}__dashboard_section`; + return "scanner__dashboard_section"; } private getDashboardSectionCameraScanRegionId(): string { - return `${this.elementId}__dashboard_section_csr`; + return "scanner__dashboard_section_csr"; } private getDashboardSectionSwapLinkId(): string { @@ -1105,33 +1143,20 @@ export class Html5QrcodeScanner { } private getScanRegionId(): string { - return `${this.elementId}__scan_region`; + return "scanner__scan_region"; } private getDashboardId(): string { - return `${this.elementId}__dashboard`; + return "scanner__dashboard"; } private getHeaderMessageContainerId(): string { - return `${this.elementId}__header_message`; + return "scanner__header_message"; } private getCameraPermissionButtonId(): string { return PublicUiElementIdAndClasses.CAMERA_PERMISSION_BUTTON_ID; } - - private getCameraScanRegion(): HTMLElement { - return document.getElementById( - this.getDashboardSectionCameraScanRegionId())!; - } - - private getDashboardSectionSwapLink(): HTMLElement { - return document.getElementById(this.getDashboardSectionSwapLinkId())!; - } - - private getHeaderMessageDiv(): HTMLElement { - return document.getElementById(this.getHeaderMessageContainerId())!; - } //#endregion //#endregion } diff --git a/src/html5-qrcode.ts b/src/html5-qrcode.ts index b3fcbda..7b3162e 100644 --- a/src/html5-qrcode.ts +++ b/src/html5-qrcode.ts @@ -263,7 +263,6 @@ export class Html5Qrcode { //#region Private fields. private readonly logger: Logger; - private readonly elementId: string; private readonly verbose: boolean; private readonly qrcode: RobustQrcodeDecoderAsync; @@ -273,13 +272,14 @@ export class Html5Qrcode { // TODO(mebjas): Reduce the state-fulness of this mammoth class, by splitting // into independent classes for better separation of concerns and reducing // error prone nature of a large stateful class. - private element: HTMLElement | null = null; private canvasElement: HTMLCanvasElement | null = null; private scannerPausedUiElement: HTMLDivElement | null = null; private hasBorderShaders: boolean | null = null; private borderShaders: Array | null = null; private qrMatch: boolean | null = null; private renderedCamera: RenderedCamera | null = null; + private element: HTMLElement | null = null; + private shadingElement: HTMLDivElement | null = null; private foreverScanTimeout: any; private qrRegion: QrcodeRegionBounds | null = null; @@ -296,7 +296,7 @@ export class Html5Qrcode { /** * Initialize the code scanner. * - * @param elementId Id of the HTML element. + * @param element the HTML element. * @param configOrVerbosityFlag optional, config object of type {@link * Html5QrcodeFullConfig} or a boolean verbosity flag (to maintain backward * compatibility). If nothing is passed, default values would be used. @@ -308,16 +308,16 @@ export class Html5Qrcode { * * TODO(mebjas): Deprecate the verbosity boolean flag completely. */ - public constructor(elementId: string, + public constructor(element: HTMLElement, configOrVerbosityFlag?: boolean | Html5QrcodeFullConfig | undefined) { - if (!document.getElementById(elementId)) { - throw `HTML Element with id=${elementId} not found`; + if (!element || !(element instanceof Element)) { + throw "HTML Element is not valid"; } - this.elementId = elementId; + this.element = element; this.verbose = false; - - let experimentalFeatureConfig : ExperimentalFeaturesConfig | undefined; + + let experimentalFeatureConfig: ExperimentalFeaturesConfig | undefined; let configObject: Html5QrcodeFullConfig | undefined; if (typeof configOrVerbosityFlag == "boolean") { this.verbose = configOrVerbosityFlag === true; @@ -326,7 +326,7 @@ export class Html5Qrcode { this.verbose = configObject.verbose === true; experimentalFeatureConfig = configObject.experimentalFeatures; } - + this.logger = new BaseLoggger(this.verbose); this.qrcode = new Html5QrcodeShim( this.getSupportedFormats(configOrVerbosityFlag), @@ -377,7 +377,7 @@ export class Html5Qrcode { qrCodeErrorCallbackInternal = qrCodeErrorCallback; } else { qrCodeErrorCallbackInternal - = this.verbose ? this.logger.log : () => {}; + = this.verbose ? this.logger.log : () => { }; } const internalConfig = InternalHtml5QrcodeConfig.create( @@ -390,7 +390,7 @@ export class Html5Qrcode { if (!internalConfig.isMediaStreamConstraintsValid()) { this.logger.logError( "'videoConstraints' is not valid 'MediaStreamConstraints, " - + "it will be ignored.'", + + "it will be ignored.'", /* experimental= */ true); } else { videoConstraintsAvailableAndValid = true; @@ -399,13 +399,9 @@ export class Html5Qrcode { const areVideoConstraintsEnabled = videoConstraintsAvailableAndValid; // qr shaded box - const element = document.getElementById(this.elementId)!; - const rootElementWidth = element.clientWidth - ? element.clientWidth : Constants.DEFAULT_WIDTH; - element.style.position = "relative"; + this.element!.style.position = "relative"; this.shouldScan = true; - this.element = element; const $this = this; const toScanningStateChangeTransaction: StateManagerTransaction @@ -413,8 +409,8 @@ export class Html5Qrcode { Html5QrcodeScannerState.SCANNING); return new Promise((resolve, reject) => { const videoConstraints = areVideoConstraintsEnabled - ? internalConfig.videoConstraints - : $this.createVideoConstraints(cameraIdOrConfig); + ? internalConfig.videoConstraints + : $this.createVideoConstraints(cameraIdOrConfig); if (!videoConstraints) { toScanningStateChangeTransaction.cancel(); reject("videoConstraints should be defined"); @@ -562,11 +558,10 @@ export class Html5Qrcode { if (!this.element) { return; } - let childElement = document.getElementById(Constants.SHADED_REGION_ELEMENT_ID); - if (childElement) { - this.element.removeChild(childElement); + if (this.shadingElement) { + this.element.removeChild(this.shadingElement); } - }; + }; let $this = this; return this.renderedCamera!.close().then(() => { @@ -637,7 +632,7 @@ export class Html5Qrcode { : Promise { if (!imageFile || !(imageFile instanceof File)) { throw "imageFile argument is mandatory and should be instance " - + "of File. Use 'event.target.files[0]'."; + + "of File. Use 'event.target.files[0]'."; } if (isNullOrUndefined(showImage)) { @@ -657,12 +652,11 @@ export class Html5Qrcode { inputImage.onload = () => { const imageWidth = inputImage.width; const imageHeight = inputImage.height; - const element = document.getElementById(this.elementId)!; - const containerWidth = element.clientWidth - ? element.clientWidth : Constants.DEFAULT_WIDTH; + const containerWidth = this.element!.clientWidth + ? this.element!.clientWidth : Constants.DEFAULT_WIDTH; // No default height anymore. - const containerHeight = Math.max( - element.clientHeight ? element.clientHeight : imageHeight, + const containerHeight = Math.max( + this.element!.clientHeight ? this.element!.clientHeight : imageHeight, Constants.FILE_SCAN_MIN_HEIGHT); const config = this.computeCanvasDrawConfig( @@ -671,7 +665,7 @@ export class Html5Qrcode { const visibleCanvas = this.createCanvasElement( containerWidth, containerHeight, "qr-canvas-visible"); visibleCanvas.style.display = "inline-block"; - element.appendChild(visibleCanvas); + this.element!.appendChild(visibleCanvas); const context = visibleCanvas.getContext("2d"); if (!context) { throw "Unable to get 2d context from canvas"; @@ -707,7 +701,7 @@ export class Html5Qrcode { // color inversion. const hiddenCanvas = this.createCanvasElement( hiddenCanvasWidth, hiddenCanvasHeight); - element.appendChild(hiddenCanvas); + this.element!.appendChild(hiddenCanvas); const context = hiddenCanvas.getContext("2d"); if (!context) { throw "Unable to get 2d context from canvas"; @@ -841,7 +835,7 @@ export class Html5Qrcode { private getRenderedCameraOrFail() { if (this.renderedCamera == null) { throw "Scanning is not in running state, call this API only when" - + " QR code scanning using camera is in running state."; + + " QR code scanning using camera is in running state."; } return this.renderedCamera!; } @@ -882,7 +876,7 @@ export class Html5Qrcode { Html5QrcodeSupportedFormats.UPC_EAN_EXTENSION, ]; - if (!configOrVerbosityFlag + if (!configOrVerbosityFlag || typeof configOrVerbosityFlag == "boolean") { return allFormats; } @@ -893,7 +887,7 @@ export class Html5Qrcode { if (!Array.isArray(configOrVerbosityFlag.formatsToSupport)) { throw "configOrVerbosityFlag.formatsToSupport should be undefined " - + "or an array."; + + "or an array."; } if (configOrVerbosityFlag.formatsToSupport.length === 0) { @@ -923,7 +917,7 @@ export class Html5Qrcode { */ /*eslint complexity: ["error", 10]*/ private getUseBarCodeDetectorIfSupported( - config: Html5QrcodeConfigs | undefined) : boolean { + config: Html5QrcodeConfigs | undefined): boolean { // Default value is true. if (isNullOrUndefined(config)) { return true; @@ -962,7 +956,7 @@ export class Html5Qrcode { const validateMinSize = (size: number) => { if (size < Constants.MIN_QR_BOX_SIZE) { throw "minimum size of 'config.qrbox' dimension value is" - + ` ${Constants.MIN_QR_BOX_SIZE}px.`; + + ` ${Constants.MIN_QR_BOX_SIZE}px.`; } }; @@ -1013,7 +1007,7 @@ export class Html5Qrcode { // Alternatively, the config is expected to be of type QrDimensions. if (qrboxSize.width === undefined || qrboxSize.height === undefined) { throw "Invalid instance of QrDimensions passed for " - + "'config.qrbox'. Both 'width' and 'height' should be set."; + + "'config.qrbox'. Both 'width' and 'height' should be set."; } } @@ -1026,7 +1020,7 @@ export class Html5Qrcode { viewfinderHeight: number, qrboxSize: number | QrDimensions | QrDimensionFunction): QrDimensions { if (typeof qrboxSize === "number") { - return { width: qrboxSize, height: qrboxSize}; + return { width: qrboxSize, height: qrboxSize }; } else if (typeof qrboxSize === "function") { try { return qrboxSize(viewfinderWidth, viewfinderHeight); @@ -1058,8 +1052,8 @@ export class Html5Qrcode { // If `qrbox` size is not set, it will default to the dimensions of the // viewfinder. - const qrboxSize = isNullOrUndefined(internalConfig.qrbox) ? - {width: viewfinderWidth, height: viewfinderHeight}: internalConfig.qrbox!; + const qrboxSize = isNullOrUndefined(internalConfig.qrbox) ? + { width: viewfinderWidth, height: viewfinderHeight } : internalConfig.qrbox!; this.validateQrboxConfig(qrboxSize); let qrDimensions = this.toQrdimensions(viewfinderWidth, viewfinderHeight, qrboxSize); @@ -1068,10 +1062,10 @@ export class Html5Qrcode { + "greater than the height of the video stream. Shading will be" + " ignored"); } - + const shouldShadingBeApplied = internalConfig.isShadedBoxEnabled() - && qrDimensions.height <= viewfinderHeight; + && qrDimensions.height <= viewfinderHeight; const defaultQrRegion: QrcodeRegionBounds = { x: 0, y: 0, @@ -1082,7 +1076,7 @@ export class Html5Qrcode { const qrRegion = shouldShadingBeApplied ? this.getShadedRegionBounds(viewfinderWidth, viewfinderHeight, qrDimensions) : defaultQrRegion; - + const canvasElement = this.createCanvasElement( qrRegion.width, qrRegion.height); // Tell user agent that this canvas will be read frequently. @@ -1104,7 +1098,7 @@ export class Html5Qrcode { } this.createScannerPausedUiElement(this.element!); - + // Update local states this.qrRegion = qrRegion; this.context = context; @@ -1126,38 +1120,38 @@ export class Html5Qrcode { rootElement.appendChild(scannerPausedUiElement); this.scannerPausedUiElement = scannerPausedUiElement; } - - /** - * Scans current context using the qrcode library. - * - *

This method call would result in callback being triggered by the - * qrcode library. This method also handles the border coloring. - * - * @returns true if scan match is found, false otherwise. - */ + + /** + * Scans current context using the qrcode library. + * + *

This method call would result in callback being triggered by the + * qrcode library. This method also handles the border coloring. + * + * @returns true if scan match is found, false otherwise. + */ private scanContext( - qrCodeSuccessCallback: QrcodeSuccessCallback, - qrCodeErrorCallback: QrcodeErrorCallback - ): Promise { + qrCodeSuccessCallback: QrcodeSuccessCallback, + qrCodeErrorCallback: QrcodeErrorCallback + ): Promise { if (this.stateManagerProxy.isPaused()) { return Promise.resolve(false); } return this.qrcode.decodeAsync(this.canvasElement!) - .then((result) => { - qrCodeSuccessCallback( - result.text, - Html5QrcodeResultFactory.createFromQrcodeResult( - result)); - this.possiblyUpdateShaders(/* qrMatch= */ true); - return true; - }).catch((error) => { - this.possiblyUpdateShaders(/* qrMatch= */ false); - let errorMessage = Html5QrcodeStrings.codeParseError(error); - qrCodeErrorCallback( - errorMessage, Html5QrcodeErrorFactory.createFrom(errorMessage)); - return false; - }); + .then((result) => { + qrCodeSuccessCallback( + result.text, + Html5QrcodeResultFactory.createFromQrcodeResult( + result)); + this.possiblyUpdateShaders(/* qrMatch= */ true); + return true; + }).catch((error) => { + this.possiblyUpdateShaders(/* qrMatch= */ false); + let errorMessage = Html5QrcodeStrings.codeParseError(error); + qrCodeErrorCallback( + errorMessage, Html5QrcodeErrorFactory.createFrom(errorMessage)); + return false; + }); } /** @@ -1237,7 +1231,7 @@ export class Html5Qrcode { private createVideoConstraints( cameraIdOrConfig: string | MediaTrackConstraints) - : MediaTrackConstraints | undefined { + : MediaTrackConstraints | undefined { if (typeof cameraIdOrConfig == "string") { // If it's a string it should be camera device Id. return { deviceId: { exact: cameraIdOrConfig } }; @@ -1245,7 +1239,7 @@ export class Html5Qrcode { const facingModeKey = "facingMode"; const deviceIdKey = "deviceId"; const allowedFacingModeValues - = { "user" : true, "environment" : true}; + = { "user": true, "environment": true }; const exactKey = "exact"; const isValidFacingModeValue = (value: string) => { if (value in allowedFacingModeValues) { @@ -1254,20 +1248,20 @@ export class Html5Qrcode { } else { // Invalid config throw "config has invalid 'facingMode' value = " - + `'${value}'`; + + `'${value}'`; } }; const keys = Object.keys(cameraIdOrConfig); if (keys.length !== 1) { throw "'cameraIdOrConfig' object should have exactly 1 key," - + ` if passed as an object, found ${keys.length} keys`; + + ` if passed as an object, found ${keys.length} keys`; } - const key:string = Object.keys(cameraIdOrConfig)[0]; + const key: string = Object.keys(cameraIdOrConfig)[0]; if (key !== facingModeKey && key !== deviceIdKey) { throw `Only '${facingModeKey}' and '${deviceIdKey}' ` - + " are supported for 'cameraIdOrConfig'"; + + " are supported for 'cameraIdOrConfig'"; } if (key === facingModeKey) { @@ -1286,15 +1280,15 @@ export class Html5Qrcode { } else if (typeof facingMode == "object") { if (exactKey in facingMode) { if (isValidFacingModeValue(facingMode[`${exactKey}`])) { - return { - facingMode: { - exact: facingMode[`${exactKey}`] - } - }; + return { + facingMode: { + exact: facingMode[`${exactKey}`] + } + }; } } else { throw "'facingMode' should be string or object with" - + ` ${exactKey} as key.`; + + ` ${exactKey} as key.`; } } else { const type = (typeof facingMode); @@ -1312,11 +1306,11 @@ export class Html5Qrcode { } else if (typeof deviceId == "object") { if (exactKey in deviceId) { return { - deviceId : { exact: deviceId[`${exactKey}`] } + deviceId: { exact: deviceId[`${exactKey}`] } }; } else { throw "'deviceId' should be string or object with" - + ` ${exactKey} as key.`; + + ` ${exactKey} as key.`; } } else { const type = (typeof deviceId); @@ -1378,9 +1372,8 @@ export class Html5Qrcode { if (this.stateManagerProxy.isScanning()) { throw "Cannot clear while scan is ongoing, close it first."; } - const element = document.getElementById(this.elementId); - if (element) { - element.innerHTML = ""; + if (this.element) { + this.element.innerHTML = ""; } } @@ -1443,47 +1436,47 @@ export class Html5Qrcode { height: number, qrboxSize: QrDimensions) { if ((width - qrboxSize.width) < 1 || (height - qrboxSize.height) < 1) { - return; + return; } - const shadingElement = document.createElement("div"); - shadingElement.style.position = "absolute"; + this.shadingElement = document.createElement("div"); + this.shadingElement.style.position = "absolute"; const rightLeftBorderSize = (width - qrboxSize.width) / 2; const topBottomBorderSize = (height - qrboxSize.height) / 2; - shadingElement.style.borderLeft + this.shadingElement.style.borderLeft = `${rightLeftBorderSize}px solid rgba(0, 0, 0, 0.48)`; - shadingElement.style.borderRight + this.shadingElement.style.borderRight = `${rightLeftBorderSize}px solid rgba(0, 0, 0, 0.48)`; - shadingElement.style.borderTop + this.shadingElement.style.borderTop = `${topBottomBorderSize}px solid rgba(0, 0, 0, 0.48)`; - shadingElement.style.borderBottom + this.shadingElement.style.borderBottom = `${topBottomBorderSize}px solid rgba(0, 0, 0, 0.48)`; - shadingElement.style.boxSizing = "border-box"; - shadingElement.style.top = "0px"; - shadingElement.style.bottom = "0px"; - shadingElement.style.left = "0px"; - shadingElement.style.right = "0px"; - shadingElement.id = `${Constants.SHADED_REGION_ELEMENT_ID}`; - + this.shadingElement.style.boxSizing = "border-box"; + this.shadingElement.style.top = "0px"; + this.shadingElement.style.bottom = "0px"; + this.shadingElement.style.left = "0px"; + this.shadingElement.style.right = "0px"; + this.shadingElement.id = `${Constants.SHADED_REGION_ELEMENT_ID}`; + // Check if div is too small for shadows. As there are two 5px width // borders the needs to have a size above 10px. - if ((width - qrboxSize.width) < 11 + if ((width - qrboxSize.width) < 11 || (height - qrboxSize.height) < 11) { - this.hasBorderShaders = false; + this.hasBorderShaders = false; } else { const smallSize = 5; const largeSize = 40; this.insertShaderBorders( - shadingElement, - /* width= */ largeSize, + this.shadingElement, + /* width= */ largeSize, /* height= */ smallSize, /* top= */ -smallSize, /* bottom= */ null, /* side= */ 0, /* isLeft= */ true); this.insertShaderBorders( - shadingElement, + this.shadingElement, /* width= */ largeSize, /* height= */ smallSize, /* top= */ -smallSize, @@ -1491,7 +1484,7 @@ export class Html5Qrcode { /* side= */ 0, /* isLeft= */ false); this.insertShaderBorders( - shadingElement, + this.shadingElement, /* width= */ largeSize, /* height= */ smallSize, /* top= */ null, @@ -1499,7 +1492,7 @@ export class Html5Qrcode { /* side= */ 0, /* isLeft= */ true); this.insertShaderBorders( - shadingElement, + this.shadingElement, /* width= */ largeSize, /* height= */ smallSize, /* top= */ null, @@ -1507,7 +1500,7 @@ export class Html5Qrcode { /* side= */ 0, /* isLeft= */ false); this.insertShaderBorders( - shadingElement, + this.shadingElement, /* width= */ smallSize, /* height= */ largeSize + smallSize, /* top= */ -smallSize, @@ -1515,7 +1508,7 @@ export class Html5Qrcode { /* side= */ -smallSize, /* isLeft= */ true); this.insertShaderBorders( - shadingElement, + this.shadingElement, /* width= */ smallSize, /* height= */ largeSize + smallSize, /* top= */ null, @@ -1523,7 +1516,7 @@ export class Html5Qrcode { /* side= */ -smallSize, /* isLeft= */ true); this.insertShaderBorders( - shadingElement, + this.shadingElement, /* width= */ smallSize, /* height= */ largeSize + smallSize, /* top= */ -smallSize, @@ -1531,7 +1524,7 @@ export class Html5Qrcode { /* side= */ -smallSize, /* isLeft= */ false); this.insertShaderBorders( - shadingElement, + this.shadingElement, /* width= */ smallSize, /* height= */ largeSize + smallSize, /* top= */ null, @@ -1540,7 +1533,7 @@ export class Html5Qrcode { /* isLeft= */ false); this.hasBorderShaders = true; } - element.append(shadingElement); + element.append(this.shadingElement); } private insertShaderBorders( @@ -1563,12 +1556,12 @@ export class Html5Qrcode { elem.style.bottom = `${bottom}px`; } if (isLeft) { - elem.style.left = `${side}px`; + elem.style.left = `${side}px`; } else { - elem.style.right = `${side}px`; + elem.style.right = `${side}px`; } if (!this.borderShaders) { - this.borderShaders = []; + this.borderShaders = []; } this.borderShaders.push(elem); shaderElem.appendChild(elem);