JSFiddle - React, Tailwind, and code Playground

Intersection Observer test with animated clip-path

by Travis Almand

HTML

<input type="range" id="horizontal" min="-350" max="350" value="0">

<div id="intersectionRect" class="hide"></div>

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

<div id="panel">
  <div id="intersecting">isIntersecting: <span></span></div>
  <div id="ratio">intersection ratio: <span></span></div>
  <div class="buttons">
    <button id="clip">clip-path</button>
    <button id="animate">animate</button>
  </div>
</div>

SCSS

body {
  align-items: center;
  background-color: #E0E0E0;
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
  justify-content: center;
}

#intersectionRect {
  background-color: #307B21;
  position: absolute;
  z-index: 10;
  
  &.hide {
    display: none !important;
  }
}

#container {
  align-items: center;
  background-color: #fff;
  border: 3px solid rebeccapurple;
  display: flex;
  height: 400px;
  justify-content: center;
  overflow: hidden;
  width: 400px;
}
#contained {
  background-color: rebeccapurple;
  height: 300px;
  width: 300px;
  
  &.clip-path {
    clip-path: circle(25%);
  }
  &.animate {
    animation: 2s grow alternate infinite;
  }
}
#panel {
  margin: 10px;
  width: 400px;
  
  .buttons {
    margin: 10px 0;
  }
}
#horizontal {
  width: 400px;
}

@keyframes grow {
  0% {
    clip-path: circle(100%);
  }
  100% {
    clip-path: circle(25%);
  }
}

JavaScript

console.clear();

let sliderH = document.querySelector('#horizontal');
let container = document.querySelector('#container');
let contained = document.querySelector('#contained');
let isIntersecting = document.querySelector('#intersecting span');
let intersectionRatio = document.querySelector('#ratio span');
let clip = document.querySelector('#clip');
let animate = document.querySelector('#animate');
let intersectionRect = document.querySelector('#intersectionRect');

let threshold = [...Array(100).keys()].map(x => x / 100);
let options = {
	root: container,
  rootMargin: '0px',
  threshold: threshold
}

let observer = new IntersectionObserver(callback, options);
observer.observe(contained);

sliderH.addEventListener('input', function (e) {
	contained.style.transform = `translate3d(${e.target.value}px, 0, 0`;
});

clip.addEventListener('click', function () {
	contained.classList.remove('animate');
	contained.classList.toggle('clip-path');
});
animate.addEventListener('click', function () {
	contained.classList.remove('clip-path');
	contained.classList.toggle('animate');
});

function callback (entries, observer) {
	entries.forEach(entry => {
  	isIntersecting.innerText = entry.isIntersecting;
    intersectionRatio.innerText = entry.intersectionRatio;
    
    intersectionRect.style.display = entry.intersectionRatio > 0.99 ? 'none': 'block';
    intersectionRect.style.height = entry.intersectionRect.height + 'px';
    intersectionRect.style.width = entry.intersectionRect.width + 'px';
    intersectionRect.style.top = entry.intersectionRect.top + 'px';
    intersectionRect.style.left = entry.intersectionRect.left + 'px';
  });
}