<div class="video-block">
  <svg class="video-block__svg">
    <defs>
      <clipPath id="video-block__clip">
        <circle class="video-block__clip-circle" cx="60%" cy="30%" r="45%"/>
      </clipPath>
      <linearGradient id="video-block__grad" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stop-color="red"/>
        <stop offset="100%" stop-color="blue"/>
      </linearGradient>
    </defs>
    
    <circle class="video-block__circle" cx="60%" cy="30%" r="48%" fill="none" stroke="url(#video-block__grad)"/>
  </svg>
  <video class="video-block__video" autoplay playsinline muted loop preload poster="https://thenewcode.com/assets/images/vid-glacier.jpg" playsinline>
    <source src="https://thenewcode.com/assets/videos/glacier.mp4" type="video/mp4">
    <source src="https://thenewcode.com/assets/videos/glacier.webm" type="video/webm">
  </video>
</div>
body {
  display: flex;
  flex-direction: column;
  margin: 0;
  background:
    linear-gradient(to right, #0001 1px, transparent 1px) 0 0 / 8px 8px,
    linear-gradient(to bottom, #0001 1px, transparent 1px) 0 0 / 8px 8px;
  font-family: sans-serif;
  min-height: 100vh;
  height: 1px;
}

.video-block {
  position: relative;
  outline: 1px solid red;
  width: 90%;
  margin: 20px auto 0;
}

.video-block__svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.video-block__circle {
  stroke-width: 2px;
  stroke-dasharray: 10px;
}

.video-block__video {
  display: block;
  width: 100%;
  clip-path: url('#video-block__clip');
}
import { value, spring } from "https://cdn.skypack.dev/popmotion@8";
const section = document.querySelector('.video-block');
const clip = section.querySelector('.video-block__clip-circle');
const circle = section.querySelector('.video-block__circle');

const clipXY = value(
  { x: 60, y: 30 },
  ({ x, y }) => {
    clip.setAttribute('cx', x + '%');
    clip.setAttribute('cy', y + '%');
  }
);

const circleXY = value(
  { x: 60, y: 30 },
  ({ x, y }) => {
    circle.setAttribute('cx', x + '%');
    circle.setAttribute('cy', y + '%');
  }
);

section.addEventListener('mousemove', ({ clientX, clientY }) => {
  const rect = section.getBoundingClientRect();
  const x = 100 * (event.clientX - rect.left) / rect.width;
  const y = 100 * (event.clientY - rect.top) / rect.height;
  
  spring({
    from: clipXY.get(),
    to: { x, y },
    velocity: clipXY.getVelocity(),
    stiffness: 200,
    damping: 50,
    mass: 3,
  }).start(clipXY);
  
  spring({
    from: circleXY.get(),
    to: { x, y },
    velocity: circleXY.getVelocity(),
    stiffness: 200,
    damping: 50,
    mass: 2.5,
  }).start(circleXY);
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.