This content originally appeared on Modern Web Development with Chrome and was authored by Paul Kinlan
<p>I'm a big fan of QRCodes, they are very simple and neat way to exchange data between the real world and the digital world. For a few years now I've had a little side project called <a href="https://qrsnapper.com">QRSnapper</a> — well it's had a few names, but this is the one I've settled on — that uses the <code>getUserMedia</code> API to take live data from the user's camera so that it can scan for QR Codes in near real time.</p>
<p>The goal of the app was to maintain 60fps in the UI and near instant detection of the QR Code, this meant that I had to put the detection code in to a Web Worker (pretty standard stuff). In this post I just wanted to quickly share how I used <a href="https://github.com/GoogleChromeLabs/comlink">comlink</a> to massively simplify the logic in the Worker.</p>
<h4 id="qrclientjs">qrclient.js</h4>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#66d9ef">import</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">as</span> <span style="color:#a6e22e">Comlink</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">'./comlink.js'</span>;
<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">proxy</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Comlink</span>.<span style="color:#a6e22e">proxy</span>(<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Worker</span>(<span style="color:#e6db74">'/scripts/qrworker.js'</span>));
<span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">decode</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">async</span> <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">context</span>) {
<span style="color:#66d9ef">try</span> {
<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">canvas</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">canvas</span>;
<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">width</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">canvas</span>.<span style="color:#a6e22e">width</span>;
<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">height</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">canvas</span>.<span style="color:#a6e22e">height</span>;
<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">imageData</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">getImageData</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">width</span>, <span style="color:#a6e22e">height</span>);
<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">await</span> <span style="color:#a6e22e">proxy</span>.<span style="color:#a6e22e">detectUrl</span>(<span style="color:#a6e22e">width</span>, <span style="color:#a6e22e">height</span>, <span style="color:#a6e22e">imageData</span>);
} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">err</span>) {
<span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">err</span>);
}
};
</code></pre></div><h4 id="qrworkerjs-web-worker">qrworker.js (web worker)</h4>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#66d9ef">import</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">as</span> <span style="color:#a6e22e">Comlink</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">'./comlink.js'</span>;
<span style="color:#66d9ef">import</span> {<span style="color:#a6e22e">qrcode</span>} <span style="color:#a6e22e">from</span> <span style="color:#e6db74">'./qrcode.js'</span>;
<span style="color:#75715e">// Use the native API's
</span><span style="color:#75715e"></span><span style="color:#66d9ef">let</span> <span style="color:#a6e22e">nativeDetector</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">async</span> (<span style="color:#a6e22e">width</span>, <span style="color:#a6e22e">height</span>, <span style="color:#a6e22e">imageData</span>) => {
<span style="color:#66d9ef">try</span> {
<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">barcodeDetector</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">BarcodeDetector</span>();
<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">barcodes</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">await</span> <span style="color:#a6e22e">barcodeDetector</span>.<span style="color:#a6e22e">detect</span>(<span style="color:#a6e22e">imageData</span>);
<span style="color:#75715e">// return the first barcode.
</span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">barcodes</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">></span> <span style="color:#ae81ff">0</span>) {
<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">barcodes</span>[<span style="color:#ae81ff">0</span>].<span style="color:#a6e22e">rawValue</span>;
}
} <span style="color:#66d9ef">catch</span>(<span style="color:#a6e22e">err</span>) {
<span style="color:#a6e22e">detector</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">workerDetector</span>;
}
};
<span style="color:#75715e">// Use the polyfil
</span><span style="color:#75715e"></span><span style="color:#66d9ef">let</span> <span style="color:#a6e22e">workerDetector</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">async</span> (<span style="color:#a6e22e">width</span>, <span style="color:#a6e22e">height</span>, <span style="color:#a6e22e">imageData</span>) => {
<span style="color:#66d9ef">try</span> {
<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">qrcode</span>.<span style="color:#a6e22e">decode</span>(<span style="color:#a6e22e">width</span>, <span style="color:#a6e22e">height</span>, <span style="color:#a6e22e">imageData</span>);
} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">err</span>) {
<span style="color:#75715e">// the library throws an excpetion when there are no qrcodes.
</span><span style="color:#75715e"></span> <span style="color:#66d9ef">return</span>;
}
}
<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">detectUrl</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">async</span> (<span style="color:#a6e22e">width</span>, <span style="color:#a6e22e">height</span>, <span style="color:#a6e22e">imageData</span>) => {
<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">detector</span>(<span style="color:#a6e22e">width</span>, <span style="color:#a6e22e">height</span>, <span style="color:#a6e22e">imageData</span>);
};
<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">detector</span> <span style="color:#f92672">=</span> (<span style="color:#e6db74">'BarcodeDetector'</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">self</span>) <span style="color:#f92672">?</span> <span style="color:#a6e22e">nativeDetector</span> <span style="color:#f92672">:</span> <span style="color:#a6e22e">workerDetector</span>;
<span style="color:#75715e">// Expose the API to the client pages.
</span><span style="color:#75715e"></span><span style="color:#a6e22e">Comlink</span>.<span style="color:#a6e22e">expose</span>({<span style="color:#a6e22e">detectUrl</span>}, <span style="color:#a6e22e">self</span>);
</code></pre></div><p>I really love Comlink, I think it is a game changer of a library especially when it comes to creating idiomatic JavaScript that works across threads. Finally a neat thing here, is that the native Barcode detection API can be run inside a worker so all the logic is encapsulated away from the UI.</p>
<p><a href="https://github.com/PaulKinlan/qrcode/blob/production/app/scripts/qrworker.js">Read full post</a>.</p>
This content originally appeared on Modern Web Development with Chrome and was authored by Paul Kinlan