<div class="block">
  <svg class="block__svg svg" viewBox="0 0 1600 900" width="100%" height="100%">
    <defs>
      <mask id="text-mask" x="0" y="0" width="100%" height="100%">
        <text class="svg__mask-line" x="50%" y="96%" text-anchor="middle" fill="#fff">150</text>
      </mask>
    </defs>

    <image href="https://images.unsplash.com/photo-1560114928-40f1f1eb26a0?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=870&q=80" preserveAspectRatio="xMidYMid slice" width="100%" height="100%" mask="url(#text-mask)" />

    <g class="svg__first-line">
      <text class="svg__line-dark" x="1%" y="65%" text-anchor="start" fill="#000">lorem</text>
      <g mask="url(#text-mask)">
        <text class="svg__line-light" x="1%" y="65%" text-anchor="start" fill="#fff" >lorem</text>
      </g>
    </g>

    <g class="svg__second-line" stroke="#fff">
      <text class="svg__line-dark" x="50%" y="100%" text-anchor="middle" fill="#000">ipsumm</text>
      <g mask="url(#text-mask)">
        <text class="svg__line-light" x="50%" y="100%" text-anchor="middle" fill="#fff" >ipsumm</text>
      </g>
    </g>
  </svg>
</div>
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@700;900&display=swap');

body {
  font-family: Roboto, sans-serif;
  margin: 0;
  height: 400vh;
}

.block {
  margin-top: 50vh;
}

.block__svg {
  display: block;
}

.svg__mask-line {
  font-weight: 900;
  font-size: 800px;
}

.svg__first-line {
  font-weight: 700;
  font-size: 80px;
  text-transform: uppercase;
}

.svg__second-line {
  font-weight: 700;
  font-size: 360px;
  text-transform: uppercase;
}
const svg = document.querySelector('.svg');

const textMaskLine = document.querySelector('.svg__mask-line');

const textFirstLineLight = document.querySelector('.svg__first-line .svg__line-light');
const textFirstLineDark = document.querySelector('.svg__first-line .svg__line-dark');

const textSecondLineLight = document.querySelector('.svg__second-line .svg__line-light');
const textSecondLineDark = document.querySelector('.svg__second-line .svg__line-dark');

const svgRect = { x: 0, y: 0, w: 0, h: 0 };

function updateSvgRect() {
  const rect = svg.getBoundingClientRect();
  svgRect.x = rect.x + window.scrollX;
  svgRect.y = rect.y + window.scrollY;
  svgRect.w = rect.width;
  svgRect.h = rect.height;
}

updateSvgRect();
window.addEventListener('resize', updateSvgRect);

let scrollProgressPrev = clamp((svgRect.y - window.scrollY) / (window.innerHeight - svgRect.h), 0, 1);

function loop(now) {
  const y = svgRect.y - window.scrollY;
  const scrollProgress = clamp(y / (window.innerHeight - svgRect.h), 0, 1);
  scrollProgressPrev = lerp(scrollProgressPrev, scrollProgress, 0.05);
  
  const t = 1 - scrollProgressPrev;
  textMaskLine.setAttribute('transform', `translate(0 ${lerp(0, -200, t)})`);
  
  textFirstLineDark.setAttribute('transform', `translate(0 ${lerp(0, -400, t)})`);
  textFirstLineLight.setAttribute('transform', `translate(0 ${lerp(0, -400, t)})`);
  
  textSecondLineDark.setAttribute('transform', `translate(0 ${lerp(0, -300, t)})`);
  textSecondLineLight.setAttribute('transform', `translate(0 ${lerp(0, -300, t)})`);
  
  requestAnimationFrame(loop);
}

requestAnimationFrame(loop);
  
function clamp(val, min, max) {
  return Math.min(Math.max(val, min), max);
}

function lerp(v0, v1, t) {
  return v0 * (1 - t) + v1 * t;
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.