I converted a page that contains HTML, CSS and JavaScript to a React app. The original website is working fine. But, when I load the React app, I get this error:
Uncaught TypeError: Cannot read properties of null (reading 'classList') at window. Onload
It seems like the element I'm trying to get is not in the DOM when the script runs.
So, I tried 5 following solutions without any positive result:
Putting the defer attribute on the external scripts.
I was thinking of moving the script in script.js, but I can't because I'm using an external script.
Using the type attribute with the module value, but I can't because I'm using an external script.
Deferring with event handling, but it's not working.
Tried the Event delegation, but is not working neither.
Here is my code:
App.js with some changes to adapt the HTML to JSX:
/* eslint-disable no-unused-vars */
/* eslint-disable no-script-url */
/* eslint-disable jsx-a11y/anchor-is-valid */
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div>
{/* <!-- Start preloader --> */}
<div id="preloader">
<div id="ctn-preloader" className="ctn-preloader">
<div className="animation-preloader">
<div className="spinner"></div>
<div className="txt-loading">
<span data-text-preloader="C" className="letters-loading">
{' '}
C{' '}
</span>
<span data-text-preloader="H" className="letters-loading">
{' '}
H{' '}
</span>
<span data-text-preloader="A" className="letters-loading">
{' '}
A{' '}
</span>
<span data-text-preloader="R" className="letters-loading">
{' '}
R{' '}
</span>
<span data-text-preloader="G" className="letters-loading">
{' '}
G{' '}
</span>
<span data-text-preloader="E" className="letters-loading">
{' '}
E{' '}
</span>
<span data-text-preloader="M" className="letters-loading">
{' '}
M{' '}
</span>
<span data-text-preloader="E" className="letters-loading">
{' '}
E{' '}
</span>
<span data-text-preloader="N" className="letters-loading">
{' '}
N{' '}
</span>
<span data-text-preloader="T" className="letters-loading">
{' '}
T{' '}
</span>
</div>
</div>
<div className="loader-section section-left"></div>
<div className="loader-section section-right"></div>
</div>
</div>
{/* <!-- End preloader --> */}
</div>
);
}
export default App;
script.js without any change:
'use strict';
// Preloader
const preLoader = function () {
let preloaderWrapper = document.getElementById('preloader');
window.onload = () => {
preloaderWrapper.classList.add('loaded');
};
};
preLoader();
// getSiblings
var getSiblings = function (elem) {
const siblings = [];
let sibling = elem.parentNode.firstChild;
while (sibling) {
if (sibling.nodeType === 1 && sibling !== elem) {
siblings.push(sibling);
}
sibling = sibling.nextSibling;
}
return siblings;
};
/* Slide Up */
var slideUp = (target, time) => {
const duration = time ? time : 500;
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = duration + 'ms';
target.style.boxSizing = 'border-box';
target.style.height = target.offsetHeight + 'px';
target.offsetHeight;
target.style.overflow = 'hidden';
target.style.height = 0;
window.setTimeout(() => {
target.style.display = 'none';
target.style.removeProperty('height');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
}, duration);
};
/* Slide Down */
var slideDown = (target, time) => {
const duration = time ? time : 500;
target.style.removeProperty('display');
let display = window.getComputedStyle(target).display;
if (display === 'none') display = 'block';
target.style.display = display;
const height = target.offsetHeight;
target.style.overflow = 'hidden';
target.style.height = 0;
target.offsetHeight;
target.style.boxSizing = 'border-box';
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = duration + 'ms';
target.style.height = height + 'px';
window.setTimeout(() => {
target.style.removeProperty('height');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
}, duration);
};
// Get window top offset
function TopOffset(el) {
let rect = el.getBoundingClientRect(),
scrollTop = window.pageYOffset || document.documentElement.scrollTop;
return { top: rect.top + scrollTop };
}
// Header sticky activation
const headerStickyWrapper = document.querySelector('header');
const headerStickyTarget = document.querySelector('.header__sticky');
if (headerStickyTarget) {
let headerHeight = headerStickyWrapper.clientHeight;
window.addEventListener('scroll', function () {
let StickyTargetElement = TopOffset(headerStickyWrapper);
let TargetElementTopOffset = StickyTargetElement.top;
if (window.scrollY > TargetElementTopOffset) {
headerStickyTarget.classList.add('sticky');
} else {
headerStickyTarget.classList.remove('sticky');
}
});
}
// Back to top
const scrollTop = document.getElementById('scroll__top');
if (scrollTop) {
scrollTop.addEventListener('click', function () {
window.scroll({ top: 0, left: 0, behavior: 'smooth' });
});
window.addEventListener('scroll', function () {
if (window.scrollY > 300) {
scrollTop.classList.add('active');
} else {
scrollTop.classList.remove('active');
}
});
}
// blog swiper column3 activation
var swiper = new Swiper('.blog__swiper--activation', {
slidesPerView: 3,
loop: true,
clickable: true,
spaceBetween: 30,
breakpoints: {
1200: {
slidesPerView: 3
},
992: {
slidesPerView: 3
},
768: {
slidesPerView: 2,
spaceBetween: 30
},
480: {
slidesPerView: 2,
spaceBetween: 20
},
0: {
slidesPerView: 1
}
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev'
}
});
// testimonial swiper activation
var swiper = new Swiper('.testimonial__swiper--activation', {
slidesPerView: 2,
loop: true,
clickable: true,
spaceBetween: 30,
breakpoints: {
768: {
slidesPerView: 2,
spaceBetween: 30
},
576: {
slidesPerView: 2,
spaceBetween: 20
},
0: {
slidesPerView: 1
}
},
pagination: {
clickable: true,
el: '.swiper-pagination'
}
});
// testimonial swiper column1 activation
var swiper = new Swiper('.testimonial__swiper--column1', {
slidesPerView: 1,
loop: true,
clickable: true,
spaceBetween: 30,
pagination: {
clickable: true,
el: '.swiper-pagination'
}
});
// project swiper activation
var swiper = new Swiper('.project__swiper--activation', {
slidesPerView: 4,
loop: true,
spaceBetween: 30,
clickable: true,
roundLengths: true,
centeredSlides: true,
breakpoints: {
1200: {
slidesPerView: 4,
spaceBetween: 30
},
992: {
slidesPerView: 3,
spaceBetween: 25
},
768: {
slidesPerView: 3,
spaceBetween: 25
},
400: {
slidesPerView: 2,
spaceBetween: 15
},
300: {
slidesPerView: 1,
spaceBetween: 15
},
0: {
slidesPerView: 1
}
},
pagination: {
clickable: true,
el: '.swiper-pagination'
}
});
// brand swiper activation
var swiper = new Swiper('.brand__swiper--activation', {
slidesPerView: 5,
loop: true,
spaceBetween: 50,
clickable: true,
breakpoints: {
1366: {
slidesPerView: 5,
spaceBetween: 50
},
1200: {
slidesPerView: 5,
spaceBetween: 40
},
992: {
slidesPerView: 5,
spaceBetween: 30
},
768: {
slidesPerView: 4,
spaceBetween: 30
},
400: {
slidesPerView: 3,
spaceBetween: 20
},
200: {
slidesPerView: 2,
spaceBetween: 20
},
0: {
slidesPerView: 1
}
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev'
}
});
// related project swiper activation
var swiper = new Swiper('.related__project--swiper', {
slidesPerView: 4,
loop: true,
clickable: true,
spaceBetween: 30,
breakpoints: {
992: {
slidesPerView: 4
},
768: {
slidesPerView: 3,
spaceBetween: 30
},
576: {
slidesPerView: 3,
spaceBetween: 20
},
480: {
slidesPerView: 2,
spaceBetween: 20
},
300: {
slidesPerView: 2,
spaceBetween: 15
},
0: {
slidesPerView: 1
}
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev'
}
});
// tab activation
const tab = function (wrapper) {
let tabContainer = document.querySelector(wrapper);
if (tabContainer) {
tabContainer.addEventListener('click', function (evt) {
let listItem = evt.target;
if (listItem.hasAttribute('data-toggle')) {
let targetId = listItem.dataset.target,
targetItem = document.querySelector(targetId);
listItem.parentElement
.querySelectorAll('[data-toggle="tab"]')
.forEach(function (list) {
list.classList.remove('active');
});
listItem.classList.add('active');
targetItem.classList.add('active');
setTimeout(function () {
targetItem.classList.add('show');
}, 150);
getSiblings(targetItem).forEach(function (pane) {
pane.classList.remove('show');
setTimeout(function () {
pane.classList.remove('active');
}, 150);
});
}
});
}
};
// Homepage 1 product tab
tab('.project__tab--one');
tab('.product__tab--one');
// countdown activation
document.querySelectorAll('[data-countdown]').forEach(function (elem) {
const countDownItem = function (value, label) {
return `<div class="countdown__item" ${label}"><span class="countdown__number">${value}</span><p class="countdown__text">${label}</p></div>`;
};
const date = new Date(elem.getAttribute('data-countdown')).getTime(),
second = 1000,
minute = second * 60,
hour = minute * 60,
day = hour * 24;
const countDownInterval = setInterval(function () {
let currentTime = new Date().getTime(),
timeDistance = date - currentTime,
daysValue = Math.floor(timeDistance / day),
hoursValue = Math.floor((timeDistance % day) / hour),
minutesValue = Math.floor((timeDistance % hour) / minute),
secondsValue = Math.floor((timeDistance % minute) / second);
elem.innerHTML =
countDownItem(daysValue, 'days') +
countDownItem(hoursValue, 'hrs') +
countDownItem(minutesValue, 'mins') +
countDownItem(secondsValue, 'secs');
if (timeDistance < 0) clearInterval(countDownInterval);
}, 1000);
});
// adding & removing class function
const activeClassAction = function (toggle, target) {
const to = document.querySelector(toggle),
ta = document.querySelector(target);
if (to && ta) {
to.addEventListener('click', function (e) {
e.preventDefault();
let triggerItem = e.target;
if (triggerItem.classList.contains('active')) {
triggerItem.classList.remove('active');
ta.classList.remove('active');
} else {
triggerItem.classList.add('active');
ta.classList.add('active');
}
});
document.addEventListener('click', function (event) {
if (
!event.target.closest(toggle) &&
!event.target.classList.contains(toggle.replace(/\./, ''))
) {
if (
!event.target.closest(target) &&
!event.target.classList.contains(target.replace(/\./, ''))
) {
to.classList.remove('active');
ta.classList.remove('active');
}
}
});
}
};
activeClassAction('.account__currency--link', '.dropdown__currency');
activeClassAction('.language__switcher', '.dropdown__language');
activeClassAction(
'.offcanvas__language--switcher',
'.offcanvas__dropdown--language'
);
activeClassAction(
'.offcanvas__account--currency__menu',
'.offcanvas__account--currency__submenu'
);
activeClassAction('.footer__language--link', '.footer__dropdown--language');
activeClassAction('.footer__currency--link', '.footer__dropdown--currency');
// OffCanvas Sidebar Activation
function offcanvsSidebar(openTrigger, closeTrigger, wrapper) {
let OpenTriggerprimary__btn = document.querySelectorAll(openTrigger);
let closeTriggerprimary__btn = document.querySelector(closeTrigger);
let WrapperSidebar = document.querySelector(wrapper);
let wrapperOverlay = wrapper.replace('.', '');
function handleBodyClass(evt) {
let eventTarget = evt.target;
if (!eventTarget.closest(wrapper) && !eventTarget.closest(openTrigger)) {
WrapperSidebar.classList.remove('active');
document
.querySelector('body')
.classList.remove(`${wrapperOverlay}_active`);
}
}
if (OpenTriggerprimary__btn && WrapperSidebar) {
OpenTriggerprimary__btn.forEach(function (singleItem) {
singleItem.addEventListener('click', function (e) {
if (e.target.dataset.offcanvas != undefined) {
WrapperSidebar.classList.add('active');
document
.querySelector('body')
.classList.add(`${wrapperOverlay}_active`);
document.body.addEventListener('click', handleBodyClass.bind(this));
}
});
});
}
if (closeTriggerprimary__btn && WrapperSidebar) {
closeTriggerprimary__btn.addEventListener('click', function (e) {
if (e.target.dataset.offcanvas != undefined) {
WrapperSidebar.classList.remove('active');
document
.querySelector('body')
.classList.remove(`${wrapperOverlay}_active`);
document.body.removeEventListener('click', handleBodyClass.bind(this));
}
});
}
}
// Mini Cart
offcanvsSidebar(
'.minicart__open--btn',
'.minicart__close--btn',
'.offCanvas__minicart'
);
// Search Bar
offcanvsSidebar(
'.search__open--btn',
'.predictive__search--close__btn',
'.predictive__search--box'
);
// Offcanvas filter sidebar
offcanvsSidebar(
'.widget__filter--btn',
'.offcanvas__filter--close',
'.offcanvas__filter--sidebar'
);
/* Offcanvas Mobile Menu Function */
const offcanvasHeader = function () {
const offcanvasOpen = document.querySelector(
'.offcanvas__header--menu__open--btn'
),
offcanvasClose = document.querySelector('.offcanvas__close--btn'),
offcanvasHeader = document.querySelector('.offcanvas-header'),
offcanvasMenu = document.querySelector('.offcanvas__menu'),
body = document.querySelector('body');
/* Offcanvas SubMenu Toggle */
if (offcanvasMenu) {
offcanvasMenu
.querySelectorAll('.offcanvas__sub_menu')
.forEach(function (ul) {
const subMenuToggle = document.createElement('button');
subMenuToggle.classList.add('offcanvas__sub_menu_toggle');
ul.parentNode.appendChild(subMenuToggle);
});
}
/* Open/Close Menu On Click Toggle Button */
if (offcanvasOpen) {
offcanvasOpen.addEventListener('click', function (e) {
e.preventDefault();
if (e.target.dataset.offcanvas != undefined) {
offcanvasHeader.classList.add('open');
body.classList.add('mobile_menu_open');
}
});
}
if (offcanvasClose) {
offcanvasClose.addEventListener('click', function (e) {
e.preventDefault();
if (e.target.dataset.offcanvas != undefined) {
offcanvasHeader.classList.remove('open');
body.classList.remove('mobile_menu_open');
}
});
}
/* Mobile submenu slideToggle Activation */
let mobileMenuWrapper = document.querySelector('.offcanvas__menu_ul');
if (mobileMenuWrapper) {
mobileMenuWrapper.addEventListener('click', function (e) {
let targetElement = e.target;
if (targetElement.classList.contains('offcanvas__sub_menu_toggle')) {
const parent = targetElement.parentElement;
if (parent.classList.contains('active')) {
targetElement.classList.remove('active');
parent.classList.remove('active');
parent
.querySelectorAll('.offcanvas__sub_menu')
.forEach(function (subMenu) {
subMenu.parentElement.classList.remove('active');
subMenu.nextElementSibling.classList.remove('active');
slideUp(subMenu);
});
} else {
targetElement.classList.add('active');
parent.classList.add('active');
slideDown(targetElement.previousElementSibling);
getSiblings(parent).forEach(function (item) {
item.classList.remove('active');
item
.querySelectorAll('.offcanvas__sub_menu')
.forEach(function (subMenu) {
subMenu.parentElement.classList.remove('active');
subMenu.nextElementSibling.classList.remove('active');
slideUp(subMenu);
});
});
}
}
});
}
if (offcanvasHeader) {
document.addEventListener('click', function (event) {
if (
!event.target.closest('.offcanvas__header--menu__open--btn') &&
!event.target.classList.contains(
'.offcanvas__header--menu__open--btn'.replace(/\./, '')
)
) {
if (
!event.target.closest('.offcanvas-header') &&
!event.target.classList.contains(
'.offcanvas-header'.replace(/\./, '')
)
) {
offcanvasHeader.classList.remove('open');
body.classList.remove('mobile_menu_open');
}
}
});
}
/* Remove Mobile Menu Open Class & Hide Mobile Menu When Window Width in More Than 991 */
if (offcanvasHeader) {
window.addEventListener('resize', function () {
if (window.outerWidth >= 992) {
offcanvasHeader.classList.remove('open');
body.classList.remove('mobile_menu_open');
}
});
}
};
/* Mobile Menu Active */
offcanvasHeader();
// Increment & Decrement Qunatity Button
const quantityWrapper = document.querySelectorAll('.quantity__box');
if (quantityWrapper) {
quantityWrapper.forEach(function (singleItem) {
let increaseButton = singleItem.querySelector('.increase');
let decreaseButton = singleItem.querySelector('.decrease');
increaseButton.addEventListener('click', function (e) {
let input = e.target.previousElementSibling.children[0];
if (input.dataset.counter != undefined) {
let value = parseInt(input.value, 10);
value = isNaN(value) ? 0 : value;
value++;
input.value = value;
}
});
decreaseButton.addEventListener('click', function (e) {
let input = e.target.nextElementSibling.children[0];
if (input.dataset.counter != undefined) {
let value = parseInt(input.value, 10);
value = isNaN(value) ? 0 : value;
value < 1 ? (value = 1) : '';
value--;
input.value = value;
}
});
});
}
// Modal JS
const openEls = document.querySelectorAll('[data-open]');
const closeEls = document.querySelectorAll('[data-close]');
const isVisible = 'is-visible';
for (const el of openEls) {
el.addEventListener('click', function () {
const modalId = this.dataset.open;
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener('click', function () {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener('click', e => {
if (e.target == document.querySelector('.modal.is-visible')) {
document.querySelector('.modal.is-visible').classList.remove(isVisible);
}
});
document.addEventListener('keyup', e => {
if (e.key == 'Escape' && document.querySelector('.modal.is-visible')) {
document.querySelector('.modal.is-visible').classList.remove(isVisible);
}
});
// Accordion
function customAccordion(accordionWrapper, singleItem, accordionBody) {
let accoridonButtons = document.querySelectorAll(accordionWrapper);
accoridonButtons.forEach(function (item) {
item.addEventListener('click', function (evt) {
let itemTarget = evt.target;
if (
itemTarget.classList.contains('accordion__items--button') ||
itemTarget.classList.contains('widget__categories--menu__label')
) {
let singleAccordionWrapper = itemTarget.closest(singleItem),
singleAccordionBody =
singleAccordionWrapper.querySelector(accordionBody);
if (singleAccordionWrapper.classList.contains('active')) {
singleAccordionWrapper.classList.remove('active');
slideUp(singleAccordionBody);
} else {
singleAccordionWrapper.classList.add('active');
slideDown(singleAccordionBody);
getSiblings(singleAccordionWrapper).forEach(function (item) {
let sibllingSingleAccordionBody = item.querySelector(accordionBody);
item.classList.remove('active');
slideUp(sibllingSingleAccordionBody);
});
}
}
});
});
}
customAccordion(
'.accordion__container',
'.accordion__items',
'.accordion__items--body'
);
customAccordion(
'.widget__categories--menu',
'.widget__categories--menu__list',
'.widget__categories--sub__menu'
);
// footer widget js
let accordion = true;
const footerWidgetAccordion = function () {
accordion = false;
let footerWidgetContainer = document.querySelector('.main__footer');
footerWidgetContainer.addEventListener('click', function (evt) {
let singleItemTarget = evt.target;
if (singleItemTarget.classList.contains('footer__widget--button')) {
const footerWidget = singleItemTarget.closest('.footer__widget'),
footerWidgetInner = footerWidget.querySelector(
'.footer__widget--inner'
);
if (footerWidget.classList.contains('active')) {
footerWidget.classList.remove('active');
slideUp(footerWidgetInner);
} else {
footerWidget.classList.add('active');
slideDown(footerWidgetInner);
getSiblings(footerWidget).forEach(function (item) {
const footerWidgetInner = item.querySelector(
'.footer__widget--inner'
);
item.classList.remove('active');
slideUp(footerWidgetInner);
});
}
}
});
};
window.addEventListener('load', function () {
if (accordion) {
footerWidgetAccordion();
}
});
window.addEventListener('resize', function () {
document.querySelectorAll('.footer__widget').forEach(function (item) {
if (window.outerWidth >= 768) {
item.classList.remove('active');
item.querySelector('.footer__widget--inner').style.display = '';
}
});
if (accordion) {
footerWidgetAccordion();
}
});
// lightbox Activation
const customLightboxHTML = `<div id="glightbox-body" class="glightbox-container">
<div class="gloader visible"></div>
<div class="goverlay"></div>
<div class="gcontainer">
<div id="glightbox-slider" class="gslider"></div>
<button class="gnext gbtn" tabindex="0" aria-label="Next" data-customattribute="example">{nextSVG}</button>
<button class="gprev gbtn" tabindex="1" aria-label="Previous">{prevSVG}</button>
<button class="gclose gbtn" tabindex="2" aria-label="Close">{closeSVG}</button>
</div>
</div>`;
const lightbox = GLightbox({
touchNavigation: true,
lightboxHTML: customLightboxHTML,
loop: true
});
// CounterUp Activation
const wrapper = document.getElementById('funfactId');
if (wrapper) {
const counters = wrapper.querySelectorAll('.js-counter');
const duration = 400;
let isCounted = false;
document.addEventListener('scroll', function () {
const wrapperPos = wrapper.offsetTop - window.innerHeight;
if (!isCounted && window.scrollY > wrapperPos) {
counters.forEach(counter => {
const countTo = counter.dataset.count;
const countPerMs = countTo / duration;
let currentCount = 0;
const countInterval = setInterval(function () {
if (currentCount >= countTo) {
clearInterval(countInterval);
}
counter.textContent = Math.round(currentCount);
currentCount = currentCount + countPerMs;
}, 1);
});
isCounted = true;
}
});
}
Anyone has the correct solution?
i try to use scanner.js but the same error keeps on showing
i have a problem with websocket connection when i try using scanner.js from Asprise.
i installed scanner.js using npm i scanner.js and imported it in my react code. i even added the following script script in my html page and it does notwork.
import React from "react";
import scanner from 'scanner-js';
let scanRequest = {
"use_asprise_dialog": true, // Whether to use Asprise Scanning Dialog
"show_scanner_ui": true, // Whether scanner UI should be shown
"twain_cap_setting" : {
"ICAP_PIXELTYPE" : "TWPT_RGB", // Color
"ICAP_XRESOLUTION" : "100", // DPI: 100
"ICAP_YRESOLUTION" : "100",
"ICAP_SUPPORTEDSIZES" : "TWSS_USLETTER" // Paper size: TWSS_USLETTER, TWSS_A4, ...
},
"output_settings": [{
"type": "return-base64",
"format": "pdf",
"thumbnail_height": 200,
}]
};
/** Triggers the scan */
const scan = () => {
scanner.scan(displayImagesOnPage, scanRequest);
}
/** Processes the scan result */
const displayImagesOnPage = (successful, mesg, response) => {
if (!successful) { // On error
console.error('Failed: ' + mesg);
return;
}
if (successful && mesg != null && mesg.toLowerCase().indexOf('user cancel') >= 0) { // User cancelled.
console.info('User cancelled');
return;
}
let scannedImages = scanner.getScannedImages(response, true, false); // returns an array of ScannedImage
for (let i = 0;
(scannedImages instanceof Array) && i < scannedImages.length; i++) {
let scannedImage = scannedImages[i];
let elementImg = scanner.createDomElementFromModel({
'name': 'img',
'attributes': {
'class': 'scanned',
'src': scannedImage.src
}
});
(document.getElementById('images') ? document.getElementById('images') : document.body).appendChild(elementImg);
}
}
export const Scanner = () => {
return (
<div>
<h2>Scanner.js TEST</h2>
<button type="button" onClick={()=>scan()}>
Scan
</button>
<div id="images"></div>
</div>
);
};
export default Scanner;
I want to create a PWA which acts as a bar- and QR-code scanner. For detecting QR-codes I use jsQR (see: https://github.com/cozmo/jsQR), for barcodes, I want to use QuaggaJS (see: https://serratus.github.io/quaggaJS/). To select the type of code that should be detected, I have some radio buttons on my site, which call the function "triggerScannerInitialisation()" (see below). Scanning QR-codes is working already, but scanning barcodes causes some problems. The camera image is not loaded properly. If I run the same code on testing site that only uses QuaggaJS, scanning barcodes works as it should do. I assume that opening two camera streams from the same camera may cause a problem. Can anyone give me a hint on how to use both with the same camera stream?
// variables for stopping scanner types on next run
let stopJsQrOnNextRun = true;
function triggerScannerInitialisation() {
// get the selected code type
let codeTypeSelector = document.querySelector('input[name="code_type_selector"]:checked').value;
switch (codeTypeSelector) {
case 'barcode':
stopJsQrOnNextRun = true;
startQuaggaJs();
break;
case 'qr':
stopQuaggaJs();
stopJsQrOnNextRun = false;
startJsQr();
break;
default:
return false;
}
}
function startQuaggaJs() {
document.getElementById("barcode_camera_div").hidden = false;
Quagga.init({
inputStream: {
name: "Live",
type: "LiveStream",
target: document.querySelector('#barcode_camera_div'),
constraints: {
width: 480,
height: 320,
facingMode: "environment"
},
},
decoder: {
readers: [
"code_128_reader",
"ean_reader",
"ean_8_reader",
"code_39_reader",
"code_39_vin_reader",
"codabar_reader",
"upc_reader",
"upc_e_reader",
"i2of5_reader"
],
debug: {
showCanvas: true,
showPatches: true,
showFoundPatches: true,
showSkeleton: true,
showLabels: true,
showPatchLabels: true,
showRemainingPatchLabels: true,
boxFromPatches: {
showTransformed: true,
showTransformedBox: true,
showBB: true
}
}
},
}, function (err) {
if (err) {
console.log(err);
return
}
console.log("Initialization finished. Ready to start");
Quagga.start();
});
Quagga.onProcessed(function (result) {
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box) {
return box !== result.box;
}).forEach(function (box) {
Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, { color: "green", lineWidth: 2 });
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, { color: "#00F", lineWidth: 2 });
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, { color: 'red', lineWidth: 3 });
}
}
});
Quagga.onDetected(function (result) {
console.log("Barcode detected and processed : [" + result.codeResult.code + "]", result);
});
}
function stopQuaggaJs() {
// stop quagga JS
Quagga.stop();
document.getElementById("barcode_camera_div").hidden = true;
}
function startJsQr() {
let video = document.createElement("video");
let canvasElement = document.getElementById("canvas");
let canvas = canvasElement.getContext("2d");
let loadingMessage = document.getElementById("loadingMessage");
function drawLine(begin, end, color) {
canvas.beginPath();
canvas.moveTo(begin.x, begin.y);
canvas.lineTo(end.x, end.y);
canvas.lineWidth = 4;
canvas.strokeStyle = color;
canvas.stroke();
}
// Use facingMode: environment to attemt to get the front camera on phones
navigator.mediaDevices.getUserMedia({video: {facingMode: "environment"}}).then(function (stream) {
video.srcObject = stream;
video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
video.play();
console.log("JSQR triggered");
requestAnimationFrame(tickQRcode);
});
function tickQRcode() {
loadingMessage.innerText = "⌛ Video laden...";
if (video.readyState === video.HAVE_ENOUGH_DATA) {
loadingMessage.hidden = true;
canvasElement.hidden = false;
canvasElement.height = video.videoHeight;
canvasElement.width = video.videoWidth;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
let imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
let code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: "dontInvert",
});
if (code) {
drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58");
drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58");
drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58");
drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58");
codeFound(code.data, 'qr');
}
}
if (!stopJsQrOnNextRun) {
requestAnimationFrame(tickQRcode);
} else {
stopJsQr();
}
}
function stopJsQr() {
// stop the stream
video.srcObject.getTracks().forEach(function (track) {
if (track.readyState === 'live') {
track.stop();
}
});
// remove HTML element properties
let canvasElement = document.getElementById('canvas');
canvasElement.setAttribute('hidden', 1);
canvasElement.removeAttribute('height');
canvasElement.removeAttribute('width');
}
}
Thank you for your help!
On iPhone Safari version 9.3.2 or on chrome, when I inspect it to mobile the iframe flashes visible for a moment when I scroll the slider or even when I navigate between different browser tabs, despite the cover image being "on top".
It seems that the problem is coming from Buffering state, because when I switch tabs and comeback to page it update the value to 3 (buffering) showing the video for a millisecond and then showing back image cover.
Is there a way to avoid it?
React code:
const VideoState = {
UNSTARTED: -1,
ENDED: 0,
PLAYING: 1,
PAUSED: 2,
BUFFERING: 3,
CUED: 5
};
this.state = {
player: null,
showCover: true,
videoInitialised: false
};
onStateChange(event) {
const { showCover } = this.state;
if (this.coverTimeout && event.data === VideoState.BUFFERING) {
window.clearTimeout(this.coverTimeout);
}
if ((event.data === VideoState.PLAYING || event.data === VideoState.BUFFERING) && showCover) {
this.setState({
showCover: false
});
} else if ((event.data === VideoState.PAUSED || event.data === VideoState.ENDED || event.data === VideoState.UNSTARTED) && !showCover) {
this.coverTimeout = window.setTimeout(() => {
this.setState({
showCover: true
});
}, 250);
}
}
I am trying to put together a custom Quill theme for my application that's based on the default Snow theme.
My project is built in ES6, and so I have had to adapt the Snow theme accordingly (mostly replacing instances of this with a variable name (let tooltip = this).
The area I'm having trouble in is displaying the link tooltip. I am super close in getting it to work (I can add links) but I am now stuck in displaying the preview for a link when the user puts the cursor on a hyperlink.
I have managed to narrow it down to the following line:
let [link, offset] = tooltip.quill.scroll.descendant(LinkBlot, range.index);
Range.index is the correct index of the user's cursor. But link is always null and offset is always -1, no matter if the user's cursor position is on or off a hyperlink.
Why won't the scroll.descendant function correctly retrieve the link that the user is on?
Complete theme code:
import extend from 'extend';
import Emitter from 'quill/core/emitter';
import BaseTheme, { BaseTooltip } from 'quill/themes/base';
import LinkBlot from 'quill/formats/link';
import { Range } from 'quill/core/selection';
const TOOLBAR_CONFIG = [
[{ header: ['1', '2', '3', false] }],
['bold', 'italic', 'underline', 'link'],
[{ list: 'ordered' }, { list: 'bullet' }],
['clean']
];
class ZSSnowTooltip extends BaseTooltip {
constructor(quill, bounds) {
super(quill, bounds);
this.preview = this.root.querySelector('a.ql-preview');
}
listen() {
super.listen();
let tooltip = this;
// on action add
tooltip.root.querySelector('a.ql-action').addEventListener('click', function(event) {
if (tooltip.root.classList.contains('ql-editing')) {
tooltip.save();
} else {
tooltip.edit('link', tooltip.preview.textContent);
}
event.preventDefault();
});
// on action remove
tooltip.root.querySelector('a.ql-remove').addEventListener('click', function(event) {
if (tooltip.linkRange !== null) {
tooltip.restoreFocus();
tooltip.quill.formatText(tooltip.linkRange, 'link', false, Emitter.sources.USER);
delete tooltip.linkRange;
}
event.preventDefault();
tooltip.hide();
});
// on selection change
tooltip.quill.on(Emitter.events.SELECTION_CHANGE, function(range) {
// if no range is selected
if (range === null) {
return;
}
// if range length is 0, try to get a link, if there is a link, show preview box
if (range.length === 0) {
let [link, offset] = tooltip.quill.scroll.descendant(LinkBlot, range.index); // link IS ALWAYS NULL
if (link !== null) {
tooltip.linkRange = new Range(range.index - offset, link.length());
let preview = LinkBlot.formats(link.domNode);
tooltip.preview.textContent = preview;
tooltip.preview.setAttribute('href', preview);
tooltip.show();
tooltip.position(tooltip.quill.getBounds(tooltip.linkRange));
return;
}
} else {
delete tooltip.linkRange;
}
tooltip.hide();
});
}
show() {
super.show();
this.root.removeAttribute('data-mode');
}
}
class ZSSnowTheme extends BaseTheme {
constructor(quill, options) {
if (options.modules.toolbar !== null && options.modules.toolbar.container === null) {
options.modules.toolbar.container = TOOLBAR_CONFIG;
}
super(quill, options);
}
extendToolbar(toolbar) {
this.tooltip = new ZSSnowTooltip(this.quill, this.options.bounds); //eslint-disable-line
if (toolbar.container.querySelector('.ql-link')) {
this.quill.keyboard.addBinding({ key: 'K', shortKey: true }, function(range, context) { //eslint-disable-line
toolbar.handlers['link'].call(toolbar, !context.format.link); //eslint-disable-line
});
}
}
}
ZSSnowTheme.DEFAULTS = extend(true, {}, BaseTheme.DEFAULTS, {
modules: {
toolbar: {
handlers: {
link: function(value) { // eslint-disable-line
if (value) {
let range = this.quill.getSelection(),
tooltip,
preview;
if (range === null || range.length === 0) {
return;
}
preview = this.quill.getText(range);
if (/^\S+#\S+\.\S+$/.test(preview) && preview.indexOf('mailto:') !== 0) {
preview = `mailto:${preview}`;
}
tooltip = this.quill.theme.tooltip;
tooltip.edit('link', preview);
} else {
this.quill.format('link', false);
}
}
}
}
}
});
ZSSnowTooltip.TEMPLATE =
`<span class="title">
Bezoek link
</span>
<a class="ql-preview" target="_blank" href="about:blank"></a>
<input type="text" />
<span> - </span>
<a class="ql-action">
<i class="mdi"></i>
<span class="ql-action-label"></span>
</a>
<a class="ql-remove">
<i class="mdi mdi-delete"></i>
Verwijder
</a>`;
export default ZSSnowTheme;