<div class="container">
  <div class="grid">
    <div class="item" data-id="1">1</div>
    <div class="item" data-id="2">2</div>
    <div class="item" data-id="3">3</div>
    <div class="item" data-id="4">4</div>
    <div class="item" data-id="5">5</div>
    <div class="item" data-id="6">6</div>
    <div class="item" data-id="7">7</div>
    <div class="item" data-id="8">8</div>
    <div class="item" data-id="9">9</div>
  </div>

  <div class="grid grid--shadow-map">
    <div class="item" data-id="1"></div>
    <div class="item" data-id="2"></div>
    <div class="item" data-id="3"></div>
    <div class="item" data-id="4"></div>
    <div class="item" data-id="5"></div>
    <div class="item" data-id="6"></div>
    <div class="item" data-id="7"></div>
    <div class="item" data-id="8"></div>
    <div class="item" data-id="9"></div>
  </div>

</div>
body {
  margin: 0;
  font-family: sans-serif;
  min-height: 100vh;
}

.container {
  position: relative;
}

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(auto-fit, minmax(50px, 1fr));
  min-height: 100vh;
}

.grid--shadow-map {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.item {
  display: grid;
  place-content: center;
  font-size: 2rem;
  color: #eea7;
  background-color: #161616;
}

.grid--shadow-map .item {
  background-color: transparent;
}
const lerp = (a, b, t) => (1 - t) * a + t * b;
const shadowColor = 'rgba(103, 58, 183, 0.5)';
let activeEl = null;

const groups = [
  new Set([1, 3, 9]),
  new Set([2, 5, 8]),
  new Set([7, 4, 6]),
];

const grid = document.querySelector('.grid');
const gridShadow = document.querySelector('.grid--shadow-map');
const itemsShadow = Array.from(
  gridShadow.querySelectorAll('.item'),
  el => ({ el, currH: 0, prevH: 0 })
);

grid.addEventListener('mouseover', (e) => {
  activeEl = e.target.closest('.item');
});

grid.addEventListener('mouseleave', (e) => {
  activeEl = null;
});

function update() {
  const activeId = activeEl?.dataset.id;
  const activeGroup = activeId === null ? null : groups.find(g => g.has(+activeId));
  
  itemsShadow.forEach(it => {
    // const targetH = it.el.dataset.id === activeEl?.dataset.id ? 80 : 0;
    const targetH = activeGroup && activeGroup.has(+it.el.dataset.id) ? 80 : 0;
    it.currH = lerp(it.prevH, targetH, 0.05);
    it.currH = Math.abs(it.currH - targetH) < 1 ? targetH : it.currH;
    it.prevH = it.currH;
    const blurRadius = it.currH;
    const spreadRadius = it.currH / 4;
    it.el.style.boxShadow = `0 0 ${blurRadius}px ${spreadRadius}px ${shadowColor}`;
  });
  
  requestAnimationFrame(update);
}

requestAnimationFrame(update);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.