Javascript Camera Progressive Web App (That Works Offline)

The Stone Age of the Internet is long over, and if you are wondering if we can build a camera web app using pure Javascript – Yes, we can. This is a small experiment of mine to not just build a “take photo with webcam”, but also make it entirely offline. That’s right, this is an installable Javascript Camera PWA that works even when offline. Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes How It Works Useful Bits & Links
The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES & REQUIREMENTS

  • This is not a “newbie-friendly” open-source project cum example – It involves the use of service workers, cache storage, and webcam access.
  • Too much of a hassle to create an “online example”, just download the source code or grab it from Github.
  • “Grade A” browser required, alongside an https:// server. http://localhost is fine for testing too.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

LICENSE & DOWNLOAD

This project is released under the MIT License. You are free to use it for your own personal and commercial projects, modify it as you see fit. On the condition that there the software is provided “as-is”. There are no warranties provided and “no strings attached”. Code Boxx and the authors are not liable for any claims, damages, or liabilities.

Download | GitHub

 

 

HOW IT WORKS

Not going to explain everything line-by-line (will take forever), but here’s a quick walkthrough of the Javascript Camera PWA.

 

PART 1) SINGLE PAGE APP

1A) MAIN LANDING PAGE

js-cam.html
<!-- (A) MAIN CONTENTS -->
<div id="cb-main"></div>
 
<!-- (B) INFO BOX -->
<div id="cb-info">
  <div id="cb-info-ico" class="mi"></div>
  <div id="cb-info-txt"></div>
</div>

This is the landing page of the one-page app.

  • <div id="cb-main"> We will use AJAX to load everything into this wrapper.
  • <div id="cb-info"> This is a simple notification box to show messages like “image deleted”. Dismisses itself after 2 seconds.

 

1B) SINGLE-PAGE APP JAVASCRIPT ENGINE

assets/cb.js
var cb = {
  // (A) REGISTER PAGES HERE
  pages : {
    home : {file:"home.inc", load:cam.start},
    gallery : {file:"gallery.inc", load:gallery.list}
  },
 
// (B) LOAD PAGE
load : () => {
    MAP WINDOW.LOCATION.HASH TO CB.PAGES
    THEN USE AJAX TO FETCH THE PAGE, PLACE INTO #CB-MAIN
}  

Not going to explain this line-by-line, but basically listens to window.location.hash and AJAX load the page accordingly:

  • Map window.location.hash to cb.pages.
  • For example, #gallery will map to cb.pages["gallery"].
  • This loads gallery.inc into #cb-main and runs cam.start() when ready.

Of course, the “master troll code ninjas” are going to sing their “React/Angular/Vue is better” tune. Not that they are bad, it’s just that I didn’t want to add more bloat to a project this small.

 

 

PART 2) THE PAGES

2A) CAMERA PAGE

home.inc
<div id="cam-wrap">
  <!-- (A) VIDEO LIVE FEED -->
  <video id="cam-feed" autoplay></video>
 
  <!-- (B) CAMERA CONTROLS -->
  <div id="cam-btn">
    <button id="cam-pics" class="mi" onclick="window.location.hash='gallery'" disabled>
      collections
    </button>
    <button id="cam-snap" class="mi" onclick="cam.snap()" disabled>
      photo_camera
    </button>
  </div>
</div>
 
<!-- (C) SNAPSHOT FEEDBACK - JUST A WHITE FLASH -->
<div id="cam-flash"></div>

  1. <video id="cam-feed"> Where we put the live video feed into.
  2. <div id="cam-button"> The “gallery” and “take photo” buttons.
  3. <div id="cam-flash"> The “one bright flash” feedback when the user takes a photo.

 

2B) GALLERY PAGE

gallery.inc
<!-- (A) HEADER -->
<header class="cb-head">
  <!-- (A1) TITLE -->
  <h1 class="cb-head-title">My Pictures</h1>
 
  <!-- (A2) BUTTONS -->
  <div class="cb-head-btn">
    <a class="btn-ico mi" href="#home">reply</a>
  </div>
</header> 
 
<!-- (B) GALLERY PICTURES -->
<div id="gallery-pics"></div>
<template id="gallery-template"><div class="pic">
  <img class="img"/>
  <div class="btn">
    <button class="mi del">delete</button>
    <button class="mi get">file_download</button>
  </div>
</div></template>

  1. The page header and “back button”.
  2. The gallery itself.
    • <div id="gallery-pics"> Empty container to generate the pictures.
    • <template id="gallery-template"> Captain Obvious, template of the images.

 

 

PART 3) APP INIT

assets/js-cam.js
// (A) HELPER FUNCTION TO GENERATE ERROR MESSAGE
err : (msg) => {
  let row = document.createElement("div");
  row.className = "error";
  row.innerHTML = msg;
  document.getElementById("cb-main").appendChild(row);
},
 
// (B) INIT APP
cPics : "MyPics", // cache to store pictures
ready : 0, // number of components that are ready
init : (ready) => {
  // (B1) ALL CHECKS & COMPONENTS GOOD TO GO?
  if (ready==1) {
    cam.ready++;
    if (cam.ready==3) { cb.load(); }
  }
 
  // (B2) REQUIREMENT CHECKS & SETUP
  { ALL THE CHECKS & SETUP }
}
 
window.addEventListener("load", cam.init);

Right, the app initialize is super long-winded. To keep the long story short:

  • (A) cam.err() A helper function to show a nice big red error message on the screen.
  • (B) cam.init() This will run on page load, starting with B2, doing all the checks, and setting up. Thereafter, B1 will launch the app only when everything is good.

 

PART 4) APP ENGINE

CAMERA

The rest of assets/js-cam.js is the “camera engine” itself:

  • (C) cam.start() Initializes the camera, sets up the HTML.
  • (D) cam.snap() Takes a photo, saves it into cache storage.

GALLERY

  • (A) gallery.list() Initializes the gallery, draws the HTML list of images (in the cache storage).
  • (B) gallery.del() Delete a selected image.
  • (C) gallery.get() Download selected image.

 

 

PART 5) SERVICE WORKER

js-cam-sw.js
// (A) FILES TO CACHE
const cName = "JSCam",
cFiles = [ LIST OF FILES ];
 
// (B) CREATE/INSTALL CACHE
self.addEventListener("install", (evt) => {
  self.skipWaiting();
  evt.waitUntil(
    caches.open(cName)
    .then((cache) => { return cache.addAll(cFiles); })
    .catch((err) => { console.error(err) })
  );
});
 
// (C) LOAD FROM CACHE, FALLBACK TO NETWORK IF NOT FOUND
self.addEventListener("fetch", (evt) => {
  evt.respondWith(
    caches.match(evt.request)
    .then((res) => { return res || fetch(evt.request); })
  );
});

The service worker is a simple one – It caches the entire app itself so that it can run even when offline.

 

PART 6) INSTALLABLE PWA

js-cam.html
<!-- WEB APP MANIFEST -->
<!-- https://web.dev/add-manifest/ -->
<link rel="manifest" href="js-cam-manifest.json">

With the service worker and cached storage, the app itself should already work offline. This is just one last bit – Add a manifest to make it installable.

 

 

USEFUL BITS & LINKS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

COMPATIBILITY CHECKS

Most of the required features are already well-supported on modern “Grade A” browsers.

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

Your email address will not be published. Required fields are marked *