<a href="https://youtu.be/PDSWq6WaJMk" target="_blank" data-keyframers-credit style="color: #000"></a>
<script src="https://codepen.io/shshaw/pen/QmZYMG.js"></script>

<main id="app">
  <div class="product">
    <figure class="photo">
      <img src="https://source.unsplash.com/RECZjSWMPVI/1200x900" alt="">
    </figure>
    <div class="title">
      <h2>
        <div><span>BEEF</span></div>
        <div><span>WELLINGTON</span></div>
        <div><span>CHAIR</span></div>
      </h2>
    </div>
  </div>
  
  <div class="product">
    <figure class="photo">
      <img src="https://source.unsplash.com/3IX-Tz_0ZN0/1200x900" alt="">
    </figure>
    <div class="title">
      <h2>
        <div><span>SO MANY</span></div>
        <div><span>CHAIR</span></div>
        <div><span>TABLES</span></div>
      </h2>
    </div>
  </div>
  
  <div class="product">
    <figure class="photo">
      <img src="https://source.unsplash.com/hTPUYIIvZBY/1200x900" alt="">
    </figure>
    <div class="title">
      <h2>
        <div><span>I HAVE</span></div>
        <div><span>THIS IKEA</span></div>
        <div><span>CHAIR</span></div>
      </h2>
    </div>
  </div>
  
  <div class="product">
    <figure class="photo">
      <img src="https://source.unsplash.com/m1JPKkCHhkc/1200x900" alt="">
    </figure>
    <div class="title">
      <h2>
        <div><span>CLOSE-UP</span></div>
        <div><span>OF A</span></div>
        <div><span>CHAIR</span></div>
      </h2>
    </div>
  </div>
  
  <div class="mouse-tracker">
  </div>
</main>
@import url("https://fonts.googleapis.com/css?family=Roboto");

*,
*:before,
*:after {
  box-sizing: border-box;
  position: relative; // fight me
}

body,
html {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: Roboto, Helvetica, sans-serif;
  font-size: 300;
  background: #aaa;
}

// _+_+_+_+_+_+_+_+_+-=-=3-=-3=-=3-=3-3=-3=-

#app {
  height: 100%;
  width: 100%;
  
  &:before, &:after {
    position: absolute;
    font-size: 3vmin;
    letter-spacing: -.1vmin;
    z-index: 100000;
  }
  
  &:before {
    content: 'KEY FRAMERS';
    position: absolute;
    top: 2vh;
    left: 2vw;
  }
  
  &:after {
    content: attr(data-product) ' / ' attr(data-product-count);
    position: absolute;
    bottom: 2vh;
    left: 2vw;
    font-size: 6vmin;
  }
}

.product {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;

  &:after {
    content: "";
  }

  > .photo {
    grid-column: 1 / -1;
    grid-row: 1 / -1;
    margin: 0;
    background: #fff;

    img {
      opacity: 0.8;
      display: block;
      object-fit: cover;
      width: 100%;
      height: 100%;
    }
  }

  > .title {
    grid-column: 2;
    grid-row: 2;
    font-size: 8vmin;
    line-height: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding: 1rem;

    h2 {
      margin: 0;
      font-weight: 300;
      letter-spacing: -0.5vmin;
      line-height: 0.9;
    }

    span {
      display: block;
      white-space: nowrap;
      overflow: hidden;
    }
  }
}

.title div {
  overflow: hidden;

  &:nth-child(2) {
    text-align: center;
  }
  &:nth-child(3) {
    text-align: right;
  }
}

/* ---------------------------------- */



:root {
  --duration: 1s;
  --easing: cubic-bezier(.2, 0, .3, 1);
}

.product {

  > .photo {
    
    mask-image: radial-gradient(#000 70%, transparent 70.1%);
    mask-repeat: no-repeat;
    
    mask-position: center center;
    // Thanks to JoseG
    // https://cdpn.io/pen/qwxOeM
    mask-position: 
      calc(var(--mouse-x) - 50vw + 50%)
      calc(var(--mouse-y) - 50vh + 50%);
    
    mask-size: 0vmax 0vmax;
    
    transition: mask-size var(--duration) var(--easing);
    transition-delay: 0.5s;
  }

  .title span {
    transform: translateY(-100%);
    transition: transform var(--duration) var(--easing);
  }
  
  .title div:nth-child(2) span {
    transform: translateY(100%);
  }
}

.product[data-active] {

  z-index: 10;

  > .photo {
    mask-size: 300vmax 300vmax;
    transition-delay: 0s;
  }

  .title span {
    transform: none !important;
  }

  + .product {
    .title span {
      visibility: hidden;
      transform: translateY(100%);
    }
    
    .title div:nth-child(2) span {
      transform: translateY(-100%);
    }
  }
}

/* ---------------------------------- */

:root {
  --mouse-x: calc(50vw + 1px);
  --mouse-y: calc(50vh + 1px);
}

.mouse-tracker {
  z-index: 999;
  position: fixed;
  top: 0;
  left: 0;
  width: 10vmin;
  height: 10vmin;
  background: rgba(255,255,255,.2);
  border-radius: 50%;
  border: solid 2px #000;
  opacity: 0.8;
  transform: 
    translate(-50%, -50%)
    translate(
      var(--mouse-x),
      var(--mouse-y)
      // calc(var(--mouse-x) * 1px),
      // calc(var(--mouse-y) * 1px)
    );
  pointer-events: none;
}
View Compiled
console.clear();

const elApp = document.querySelector("#app");
const elProducts = document.querySelectorAll(".product");

let product = 0;
elProducts[product].dataset.active = true;
elApp.dataset.product = product + 1;
elApp.dataset.productCount = elProducts.length;

app.addEventListener("click", () => {
  delete elProducts[product].dataset.active;
  product = (product + 1) % elProducts.length;
  elApp.dataset.product = product + 1;
  elProducts[product].dataset.active = true;
});

/* ---------------------------------- */

document.addEventListener("mousemove", e => {
  document.documentElement.style.setProperty("--mouse-x", e.clientX +'px');
  document.documentElement.style.setProperty("--mouse-y", e.clientY +'px');
});


External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.