Google's Thanos Effect

by wales

HTML

<script src="https://unpkg.com/[email protected]/dist/html2canvas.min.js"></script>
<div id="banner-message">
  <p>Hello World</p>
  <button type="button" class="js-alt">Change color</button>
</div>

<div id="effect"></div>

<div class="action">
  <button type="button" class="js-play">
    開始
  </button>
  <button type="button" class="js-reset">
    重置
  </button>
  <hr style="margin: 20px 0;" />
  <div class="line">
    <label for="layer-border">
      <input type="checkbox" id="layer-border" class="js-border" /> 顯示分層邊框
    </label>
  </div>
  <div class="line">
    <label for="layer-count">
      分層數
      <select id="layer-count" class="js-layer-count">
        <option>2</option>
        <option>8</option>
        <option>16</option>
        <option>32</option>
        <option>64</option> 
        <option>128</option>
        <option>256</option>
        <option>512</option>
        <option>1024</option>
      </select>
    </label>
  </div>
</div>

CSS

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica, sans-serif;
  overflow-x: hidden;
}

#banner-message {
  background: #fff;
  padding: 20px;
  font-size: 25px;
  text-align: center;
  transition: all 0.2s;
  margin: 0 auto;
  width: 300px;
}

button {
  background: #0084ff;
  border: none;
  border-radius: 5px;
  padding: 8px 14px;
  font-size: 15px;
  color: #fff;
}

#banner-message.alt {
  background: #0084ff;
  color: #fff;
  margin-top: 40px;
  width: 200px;
}

#banner-message.alt button {
  background: #fff;
  color: #000;
}

.action {
  margin-top: 1rem;
  text-align: center;
  color: #fff;
}

.action .line {
  margin: 15px 0;
}


#effect {
  position: absolute;
  text-align: center;
  pointer-events: none;
}

#effect canvas {
  position: absolute;
  left: 0;
  top: 0;
  transition: transform 1.5s ease-out, opacity 1.5s ease-out;
}

#effect.border canvas {
  box-shadow: 0 0 0 1px red;
}

JavaScript

$(() => {  
  const LAYER_COUNT = 32;
  const TRANSITION_DURATION = 1.5;
  const TRANSITION_DELAY = 1.35;
  
  let currentLayerCount = LAYER_COUNT;
  
  const $target = $('#banner-message');
  const target = $target[0];
  
  const $effect = $('#effect');
  
  const hideTarget = () => {
  	$target.css({
    	'transition': `opacity ${TRANSITION_DURATION} ease`,
      opacity: 0
    });
    delay(1e3 * TRANSITION_DURATION).then(() => {
    	$target.css('visibility', 'hidden');
    });
  };
  
  const showTarget = () => {
  	$target.css({
    	opacity: 1,
      visibility: 'visible'
    });
  };
  
  // bind events
  $('.js-play').on('click', function () {
  	play(currentLayerCount);
  });
  $('.js-reset').on('click', function () {
  	showTarget();
  });
  $('.js-alt').on('click', function($evt) {
  	$target.toggleClass('alt', $evt.target.checked);
  });
  $('.js-border').on('change', function($evt) {
  	$effect.toggleClass('border', $evt.target.checked);
  });
  $('.js-layer-count').on('change', function($evt) {
  	currentLayerCount = Number($evt.target.value);
  });
  $('.js-layer-count').val(currentLayerCount);
  
  function play(layerCount) {
  	showTarget();
    
    const bRect = target.getBoundingClientRect();
    $effect.css({
      left: bRect.left,
      top: bRect.top,
      width: bRect.width,
      height: bRect.height
    });
    
    html2canvas(target, {
      backgroundColor: null,
    })
    .then(canvas => {
      const context = canvas.getContext('2d');
      const { width, height } = canvas;

      // get element imageData
      const imgData = context.getImageData(0, 0, width, height);

      // init empty imageData
      const effectImgDatas = [];
      for (let i = 0; i < layerCount; i++) {
        effectImgDatas.push(context.createImageData(width, height));
      }
      sampler(effectImgDatas, imgData, width, height, layerCount);

      // create cloned canvases
      for (let i = 0; i < layerCount; i++) {
        const canvasClone =...