<div class="wrapper">
  <div class="container">
    <p class="text" data-char-span data-text-animation="fadeIn">
      吾輩は猫である。
    </p>
    <p class="text" data-char-span data-text-animation="fadeIn">
      名前はまだない。
    </p>
  </div>
  <p class="note">再読み込みするにはReturnを押してください↓↓↓</p>
</div>
.wrapper {
  --_base-height: 95svh;
  --_note-height: 1lh;
  min-height: var(--_base-height);
}

.container {
  min-height: calc(var(--_base-height) - var(--_note-height));
  padding-top: var(--_note-height);
  display: grid;
  place-content: center;
}

.text {
  font-size: max(20px, 5vw);
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.1em;
  text-align: center;
}

.note {
  text-align: right;
}

.alternative {
  position: fixed !important;
  inset: 0 !important;
  display: block !important;
  inline-size: 4px !important;
  block-size: 4px !important;
  contain: strict !important;
  pointer-events: none !important;
  opacity: 0 !important;
}

/* アニメーション前 */
[data-text-animation="fadeIn"] span {
  opacity: 0;
  transition: opacity 1s;
}

/* アニメーション後 */
[data-text-animation="fadeIn"] span.is-active {
  opacity: 1;
}
// 1文字づつspanタグで囲う関数
const wrapCharactersWithSpan = (element, className = "") => {
  const text = element.textContent.trim();
  const characters = text.split("");
  let wrappedContent = "";

  characters.forEach((char, index) => {
    if (char === " ") {
      wrappedContent += '<span aria-hidden="true">&nbsp;</span>';
    } else {
      wrappedContent += `<span class="${className}" aria-hidden="true" translate="no" style="--index: ${index};">${char}</span>`;
    }
  });

  wrappedContent += `<span class="alternative">${text}</span>`;
  element.innerHTML = wrappedContent;
};

const chars = document.querySelectorAll("[data-char-span]");

// 1文字づつspanタグで囲う関数を実行
chars.forEach((char) => {
  wrapCharactersWithSpan(char);
});

// テキストアニメーションの一連の処理
const fadeInChars = document.querySelectorAll(
  '[data-text-animation="fadeIn"] span'
); // アニメーション対象の要素をNodeListとして取得
const spansArray = Array.from(fadeInChars); // NodeListを配列に変換

// ランダムに要素を選択してアクティブ化する関数
const addActiveClassRandomly = () => {
  if (spansArray.length === 0) return; // 配列が空の場合は処理を終了

  const randomIndex = Math.floor(Math.random() * spansArray.length); // 0から配列の長さ-1までのランダムな整数を生成
  const randomSpan = spansArray[randomIndex]; // ランダムに選択された要素を取得

  randomSpan.classList.add("is-active"); // 選択された要素にis-activeを追加
  spansArray.splice(randomIndex, 1); // 処理済みの要素を配列から削除

  if (spansArray.length > 0) {
    // 配列の要素数が0になるまで
    setTimeout(addActiveClassRandomly, 100); // 100ミリ秒後に再帰的に関数を呼び出し
  }
};

addActiveClassRandomly(); // アニメーション処理の開始

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.