Real-time Loading Indicator

by Julien Etienne

HTML

<div class="bar"><div class="progress" id="progress"></div></div>

<div id="status">&nbsp;</div>
<h1 id="progress">&nbsp;</h1>
<img id="img" />

CSS

.bar{
  width: 100%;
  height: 1rem; 
   background: #999;
  }
  
  .progress {
    background: lime;
    width: 0;
    height: 100%; 
  }

JavaScript

const loadingIndicator = async (url, indicator, before, after) => {
  if (typeof before === 'function') before();

  if (typeof indicator !== 'function') return; // validation error

  const response = await fetch(url);
  const contentLength = response.headers.get('content-length');
  const total = parseInt(contentLength, 10);
  let loaded = 0;
  const reader = response.body.getReader();
  const res = new Response(new ReadableStream({
    async start(controller) {

      while (true) {
        const {
          done,
          value
        } = await reader.read();
        if (done) {
          if (typeof after === 'function') after(res);
          break;
        }
        loaded += value.byteLength;
        const completionValue = loaded / total * 100;
        indicator(completionValue);
        controller.enqueue(value);
      }
      controller.close();
    },
    pull(controller) {
      console.log('pull: controller', controller)
    },
    cancel(reason) {

    }
  }));
  console.log('res', res)
  setTimeout(() => {
    reader.cancel();
  }, 300)
}



const elStatus = document.getElementById('status');
const progress = document.querySelector('#progress');
const status = (text) => elStatus.innerHTML = text;
const url = 'https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg';


const indicator = (amount) => {
  progress.style.width = amount + '%';
}

const before = () => status('downloading with fetch()...');
const after = async (res) => {
  status('download completed');
  const blob = await res.blob();
  document.getElementById('img').src = URL.createObjectURL(blob);
}


loadingIndicator(url, indicator, before, after);