上書きされていくテキスト

モーション種別
  • フワッと系
コンポーネント
  • テキスト
トリガー
  • クリック
技術
  • JavaScript

概要

文章が切り替わる際に、次の文章が上からディゾルプして表示されるアニメーションです。

複数の文章を重ね合わせ、 文章にマスクをつけて、そのマスクの大きさを変動させることで上から文章が徐々に表示される動きを表現しています。文章は背景色を指定することで、背面にある文章は徐々に消えていくようにしました。文章の重ね順はクラス名によって、z-indexを操作して調整しています。

JavaScriptで現在表示されているものをis-current、前回表示されていたものをis-prevというクラス名を操作しています。

テキストだけでなく、ページ自体をディゾルプで切り替えたりもできるアニメーションです。

コードサンプル

HTML

<div class="screen-text">
  <p class="screen-text__item is-current">TEXT</p>
  <p class="screen-text__item">SCREEN</p>
  <p class="screen-text__item">SAMPLE</p>
  <button class="screen-btnarea__btn screen-btnarea__btn--prev" aria-label="前へ" type="button">
    <svg viewBox="0 0 24 24" width="50%" height="50%" aria-hidden="true">
      <path d="M15 18l-6-6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
    </svg>
  </button>
  <button class="screen-btnarea__btn screen-btnarea__btn--next" aria-label="次へ" type="button">
    <svg viewBox="0 0 24 24" width="50%" height="50%" aria-hidden="true">
      <path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
    </svg>
  </button>
</div>

CSS

.screen-text {
  /* アニメーションの時間 */
  --duration: 2.5s;
  /* 境界のぼかし範囲 */
  --blur: 10%;
  /* 色 */
  --bg-color: #f4f5f9;
  --font-color: #333333;
  --btn-color: #08254f;
  --btn-hv-color: #7d8797;

  position: relative;
  display: grid;
  width: 100%;
  grid-template: "text" auto / 1fr;
  place-items: center;
  background-color: var(--bg-color);
}

.screen-text__item {
  grid-area: text;
  width: 100%;
  color: var(--font-color);
  background-color: inherit;
  text-align: center;

  /* マスク(黒=表示/透明=非表示) */
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 calc(100% - var(--blur)), rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 calc(100% - var(--blur)), rgba(0, 0, 0, 0) 100%);
  -webkit-mask-size: 100% 0%; /* 縦0% → 100%に広げて露出 */
  mask-size: 100% 100%;

  will-change: mask-size;
}

/* 現在表示中のもの(静止時の見た目) */
.screen-text__item.is-current {
  animation: screen-reveal var(--duration) cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
  z-index: 2;
}

@keyframes screen-reveal {
  from {
    mask-size: 100% 0%;
  }
  to {
    mask-size: 100% 100%;
  }
}
/* 前回表示したもの */
.screen-text__item.is-prev {
  z-index: 1;
}
/* --- ボタンのCSS --- */
.screen-btnarea__btn {
  display: grid;
  place-items: center;
  font-size: inherit;
  color: #F4F5F9;
  width: 0.8em;
  height: 0.8em;
  background: var(--btn-color);
  border: 1px solid;
  border-radius: 50%;
  transition: background-color 0.3s;
  z-index: 5;
}
.screen-btnarea__btn:focus-visible {
  background-color: var(--btn-hv-color);
  color: #F4F5F9;
}
@media (any-hover) {
  .screen-btnarea__btn:hover {
    background-color: var(--btn-hv-color);
    color: #F4F5F9;
  }
}
.screen-btnarea__btn--prev {
  position: absolute;
  inset-inline-start: 1em;
  inset-block: 0;
  margin-block: auto;
}
.screen-btnarea__btn--next {
  position: absolute;
  inset-inline-end: 1em;
  inset-block: 0;
  margin-block: auto;
}

JavaScript

document.addEventListener("DOMContentLoaded", () => {
  const wrap = document.querySelector(".screen-text");
  if (!wrap) return;

  const items = Array.from(wrap.querySelectorAll(".screen-text__item"));
  const prevBtn = document.querySelector(".screen-btnarea__btn--prev");
  const nextBtn = document.querySelector(".screen-btnarea__btn--next");

  // 初期の current を決定
  let current = items.findIndex(el => el.classList.contains("is-current"));
  if (current  {
      if (el.classList.contains("is-prev")) {
        el.classList.remove("is-prev");
      }
    });

    // 現在の要素を is-prev に
    items[current]?.classList.remove("is-current");
    items[current]?.classList.add("is-prev");

    // 新しい要素を is-current に
    items[newIndex]?.classList.add("is-current");

    current = newIndex;
  }

  function next() {
    setCurrent((current + 1) % items.length);
  }
  function prev() {
    setCurrent((current - 1 + items.length) % items.length);
  }

  nextBtn?.addEventListener("click", next);
  prevBtn?.addEventListener("click", prev);

  // キーボード操作(左右)
  document.addEventListener("keydown", e => {
    if (e.key === "ArrowRight") next();
    if (e.key === "ArrowLeft") prev();
  });
});

お問い合わせ Contact

制作のご依頼やその他ご相談は、お問い合わせフォームにて受け付けております。

お問い合わせフォームへ