<svg class="svg-text" viewBox="0 0 800 900" preserveAspectRatio="xMidYMin meet" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <path id="path" d="M60,20c0,105.5-31.5,423,130,423,137.5,0,161-214,315-214,57.5,0,147.5,46.5,147.5,180.5,0,226-283,162-283,357,0,137,223,114.5,348.5,114.5" />
  </defs>

  <text>
    <textPath class="svg-text__text" href="#path">
      <tspan>Lorem</tspan>
      <tspan>ipsum</tspan>
      <tspan>dolor</tspan>
      <tspan>sit</tspan>
      <tspan>amet</tspan>
      <tspan>consectetur</tspan>
      <tspan>adipisicing</tspan>
      <tspan>elit.</tspan>
      <tspan>Deserunt</tspan>
      <tspan>porro</tspan>
      <tspan>excepturi</tspan>
      <tspan>eius</tspan>
      <tspan>facere</tspan>
      <tspan>quo</tspan>
      <tspan>recusandae,</tspan>
      <tspan>voluptates</tspan>
      <tspan>pariatur</tspan>
      <tspan>eaque</tspan>
      <tspan>debitis</tspan>
      <tspan>expedita</tspan>
      <tspan>in</tspan>
      <tspan>fuga</tspan>
      <tspan>assumenda</tspan>
      <tspan>suscipit</tspan>
      <tspan>laudantium</tspan>
      <tspan>distinctio</tspan>
      <tspan>nihil</tspan>
      <tspan>autem</tspan>
      <tspan>aliquid</tspan>
    </textPath>
  </text>

</svg>
body {
  position: relative;
  margin: 0;
}

.svg-text {
  position: absolute;
  width: 100%;
  top: 0;
  left: 0;
  font-family: sans-serif;
  font-size: 2rem;
}

.svg-text__text tspan {
  opacity: 0;
}
import anime from "https://cdn.skypack.dev/animejs@3.2.1";
const svg = document.querySelector('.svg-text');
const tspans = svg.querySelectorAll('.svg-text__text tspan');
const tspansSeenMap = [...tspans].reduce((m, el) => m.set(el, false), new Map());

function onScroll() {
  const animtionList = [];
  for (const el of tspans) {
    const hasSeen = tspansSeenMap.get(el);
    if (hasSeen) continue;
    
    const elRect = el.getBoundingClientRect();
    const elX = elRect.x + elRect.width / 2;
    const elY = elRect.y + elRect.height / 2;
    const inView = elX > 0 && elX < window.innerWidth && elY > 0 && elY < window.innerHeight;
    if (!inView) continue;

    tspansSeenMap.set(el, true);
    animtionList.push(el);
  }
  
  if (animtionList.length > 0) {
    anime({
      targets: animtionList,
      duration: 500,
      easing: "easeOutQuad",
      delay: anime.stagger(200),
      opacity: 1,
    });
  }
}

onScroll();
window.addEventListener('scroll', onScroll);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.