css Audio - Active file-generic CSS - Active Generic - Active HTML - Active JS - Active SVG - Active Text - Active file-generic Video - Active header Love html icon-new-collection icon-person icon-team numbered-list123 pop-out spinner split-screen star tv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <script id="workerScript" type="text/javascript">
  // This script is written in such a way that it can be used
  // inline or converted to a url blob and used as a web worker.

  const msPerFrame = 1000 / 60;
  const squareSize = 50;
  const margin = 1;

  let canvas;
  let ctx;
  let shouldSlow = false;
  
  function animate() {
    const totalSize = squareSize + (margin) * 2;

    const frameNo = Math.floor(performance.now() / msPerFrame);
    const moveBy = frameNo % totalSize;

    const across = Math.ceil(canvas.width / squareSize);
    const down = Math.ceil(canvas.height / squareSize);

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    for(let a = -3; a <= across; a++) {
      for(let d = -3; d <= down; d++) {
        const x = a * totalSize + moveBy;
        const y = d * totalSize;

        ctx.fillStyle = d % 2 === 0 ? '#fa8520' : '#2fbee9';
        ctx.fillRect(x, y, squareSize, squareSize);
      }
    }

    // Do a big loop to slow down the thread
    if(shouldSlow) {
      for(let i=0; i < 500000000; i++) {
        // slow ...
      }
    }

    self.canvasRafId = self.requestAnimationFrame(animate);
  }

  function slow(slow) {
    shouldSlow = slow;
  }

  self.onmessage = function(ev) {
    if(ev.data.msg === 'init') {
      canvas = ev.data.canvas || window.canvas;
      ctx = canvas.getContext('2d');
      animate();
    } else if(ev.data.msg === 'slow') {
      slow(ev.data.shouldSlow);
    }
  }
</script>

<div id="content">
  <div id="unsupported-warning" style="display: none">Your browser does not support Offscreen Canvas. Please try this demo in one that does, such as Chrome 69+.</div>
  <div id="explanation">
    <h1>Main Thread Canvas vs Web Worker (Offscreen) Canvas</h1>
    <p>This simple example shows how running a canvas on a web worker (that is, an <em>offscreen canvas</em>) can prevent jank and other performance issues.</p>
    <p>First, run the simple animation by pressing the <strong>Start Animation</strong> button. This runs on the main thread. If the animation takes a long time, then the whole page will become unresponsive. Note the timestamp, next to the buttons, which updates with every frame.</p>
    <p>Now, press the <strong>Slow Animation</strong> button. This includes a very large loop in each animation frame, causing it to take a long time. The animation will become jumpy, compared to the smooth motion before. Notice that the timestamp is also slow to update, compared to previously. This is because the whole page cannot update until the animation has completed, as it is blocking the single thread. Try typing in the box below, and you will see that it feels unresponsive.</p>
    <p><input type="text" placeholder="Try typing here when slowed..." /></p>
    <p>Press the <strong>Transfer Canvas to Worker</strong> button. This uses the new <em>canvas.transferControlToOffscreen</em> function to take a canvas which is in the DOM and give control of it to a web worker. Notice that the animation is smooth, as it resets to normal speed. Now, press the <strong>Slow Animation</strong> button. The animation slows down, but the timer continues at a normal speed. Additionally, you can type like normal in the text box.</p>
  </div>
   
  <div id="controls">
    <button id="toggle-animation" onclick="toggleAnimation();"><span class="action">Start</span> Animation</button>
    <button id="toggle-slow" onclick="toggleSlow();"><span class="action">Slow</span> Animation</button>
    <button id="toggle-thread" onclick="toggleThread();" class="if-">Transfer Canvas to <span class="target">Worker</span></button>
    <span id="time"></span>
  </div>
</div>

<div id="canvas-container"></div>
<div id="canvas-info">
  <span class="slow"></span>
  <span class="thread"></span>
</div>
            
          
!
            
              * {
  box-sizing: border-box;
}

html, body {
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0;
  padding: 0;
  display: flex;
  background-color: #fafafa;
  font-family: Roboto, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Open Sans", sans-serif
}

h1 {
  margin: 0;
  font-size: 150%;
}

#content {
  display: flex;
  flex-direction: column;
  flex: 1;
}

#explanation {
  flex: 1;
  padding: 10px;
  overflow-y: auto;
}

#unsupported-warning {
  background-color: red;
  text-align: center;
  color: #fff;
  padding: 10px;
}

#controls {
  padding: 10px;
  text-align: center;
  border-top: 1px solid #ccc;
}

#canvas-container {
  width: 35%;
  height: 100%;
  background-color: #fff;
}

#canvas {
  width: 100%;
  height: 100%;
}

#canvas-info {
  position: fixed;
  right: 0;
  bottom: 0;
  background-color: #555;
  color: #fff;
  padding: 5px 10px;
  font-size: 80%;
}

button, input {
  padding: 10px;
}

button {
  display: inline-block;
}

input {
  width: 100%;
}

@media (max-width: 700px) {
  body {
    flex-direction: column;
  }
  
  #content {
    max-height: 75%;
  }
  
  #canvas-container {
    width: 100%;
    height: 35%;
  }
}
            
          
!
            
              let currentThread = 'main';
let isAnimating = false; // start with animation stopped
let isSlow = false;
let worker;

// Animate the canvas using the worker, and stop the main thread if it is running there
function animateOnWorker() {
  init();

  // Prepare this canvas to be transferred to the worker
  const offscreenCanvas = canvas.transferControlToOffscreen();

  // Get the worker script from this page, and create an object url from it
  // so it can be loaded without being a separate file.
  const workerScript = document.getElementById('workerScript').textContent;
  const blob = new Blob([workerScript], {type: 'text/javascript'});
  const url = URL.createObjectURL(blob);
  worker = new Worker(url);

  // Pass our init message to the worker, along with the transferred canvas
  worker.postMessage({msg: 'init', canvas: offscreenCanvas}, [offscreenCanvas]);

  // We're done with the object url, get rid of it to save memory
  URL.revokeObjectURL(url);
}

// Animate the canvas using the main thread
function animateOnMain() {
  init();
  window.postMessage({msg: 'init'}, '*');
}

/***** 
 *
 * Below here are the things that make the demo run better, but
 * the important stuff is above here!
 *
 *****/

function init() {
  stopAnimation();
  
  // We need to make a new canvas every time we switch between the main thread
  // and the worker. This is because a canvas cannot be transferred once it has
  // a rendering context.
  document.getElementById('canvas-container').innerHTML = '<canvas id="canvas"></canvas>';
  canvas = document.getElementById('canvas');
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;

  // when running on the main thread, we can't pass the canvas in a postMessage,
  // so we just set it globally
  window.canvas = canvas;
  
  tickTimer();
}

function setThread(thread) {
  currentThread = thread;
  
  if(currentThread === 'main') {
    animateOnMain();
  } else if(currentThread === 'worker') {
    animateOnWorker();
  }
  
  // Getting slow to turn on when we switch requires listening 
  // for the worker to be ready, which is a bit complicated for now.
  // So, we just turn off slow.
  toggleSlow(false);
  
  document.querySelector('#toggle-thread .target').innerText = currentThread === 'main' ? 'Worker' : 'Main';
  document.querySelector('#canvas-info .thread').innerText = currentThread;
}

function toggleThread() {
  if(typeof currentThread !== 'undefined' && currentThread === 'main') {
    setThread('worker');
  } else {
    setThread('main');
  }
}

function toggleAnimation() {
  isAnimating = !isAnimating;
  
  if(isAnimating) {
    setThread(currentThread);
  } else {
    stopAnimation();
  }
  
  document.querySelector('#toggle-animation .action').innerText = isAnimating ? 'Stop' : 'Start';
}

function stopAnimation() {
  if(worker) {
    worker.terminate();
    worker = null;
  }
  
  [window.canvasRafId, window.timerRafId].map(rafId => {
    if(rafId) {
      window.cancelAnimationFrame(rafId);
    }
  });
}

function toggleSlow(shouldSlow) {
  isSlow = typeof shouldSlow === 'boolean' ? shouldSlow : !isSlow;
  
  if(worker) {
    worker.postMessage({msg: 'slow', shouldSlow: isSlow});
  } else {
    slow(isSlow);
  }
  
  document.querySelector('#toggle-slow .action').innerText = isSlow ? 'Unslow' : 'Slow';
  document.querySelector('#canvas-info .slow').innerText = isSlow ? '(slow)' : '';
}

const timeEl = document.getElementById('time');
function tickTimer() {
  timeEl.innerText = Date.now();
  window.timerRafId = window.requestAnimationFrame(tickTimer);
}

if(typeof window.OffscreenCanvas !== 'function') {
  document.getElementById('unsupported-warning').style.display = '';
  document.getElementById('toggle-thread').setAttribute('disabled', 'disabled');
}
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.
Loading ..................

Console