JSFiddle - React, Tailwind, and code Playground

by Csaba Hellinger

HTML

<video src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" autoplay muted controls></video>

<div id="container">
</div>

CSS

/* only use border radius if we can apply it during the transition too */
@supports(view-transition-group: contain) {
  :root {
    --radius: 1.5rem;
  }
}

::view-transition-group(container) {
  border-radius: var(--radius);
  overflow: clip;
}

::view-transition-group(*) {
  animation-duration: 400ms;
  transition-timing-function: ease-in-out;
  overflow: clip;
}

body {
  background: #222;
}

video {
  width: 500px;

  __opacity: 0;
  __position: absolute;
  __pointer-events: none;
}

#container {
  --columns: 2;
  display: inline-grid;
  grid-template-columns: repeat(var(--columns), 0fr);
  gap: 0.2rem;
  border-radius: var(--radius);
  overflow: clip;
  view-transition-name: container;
  view-transition-group: contain;
}

canvas {
  view-transition-name: match-element;
}

JavaScript

const SPLIT_WIDTH = 4;
const SPLIT_HEIGHT = 3;

const video = document.querySelector('video');

const container = document.querySelector('#container');
container.style = `--columns: ${SPLIT_WIDTH}`;

const blocks = [];

for (let y = 0; y < SPLIT_HEIGHT; y += 1) {
  for (let x = 0; x < SPLIT_WIDTH; x += 1) {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext("2d");
    canvas.width = 100;
    canvas.height = 100;
    container.appendChild(canvas);
    blocks.push({ canvas, context, x, y });
  }
}

function onFrame() {
  const blockWidth = video.videoWidth / SPLIT_WIDTH;
  const blockHeight = video.videoHeight / SPLIT_HEIGHT;

  const canvasWidth = video.clientWidth / SPLIT_WIDTH;
  const canvasHeight = video.clientHeight / SPLIT_HEIGHT;

  for ({canvas, context, x, y} of blocks) {
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
    context.drawImage(
      video, 
      blockWidth * x, blockHeight * y, blockWidth, blockHeight,
      0, 0, canvasWidth, canvasHeight
    );
  }

  if (!video.paused && !video.ended) {
    video.requestVideoFrameCallback(onFrame);
  }
}

const timer = setInterval(() => {
  document.startViewTransition(() => {
    for ({ canvas } of blocks) {
      canvas.style.order = Math.round(Math.random() * 1000);
    }
  });
}, 1000);


video.onloadeddata = onFrame;
video.onplay = onFrame;

// test init
video.currentTime = 15;
window.stop = () => {
  video.pause();
  clearInterval(timer);
}