分割テキスト アニメーションの作成

分割された文字と単語のアニメーションを作成する方法の基本的な概要。

この記事では、ウェブ用の分割テキスト アニメーションとインタラクションを最小限に抑え、アクセシビリティを確保し、ブラウザ間で動作させる方法について説明します。デモをお試しください。

デモ

動画でご覧になりたい場合は、こちらの YouTube 版をご覧ください。

概要

テキスト アニメーションを分割すると、素晴らしい効果が得られます。この記事では、アニメーションの可能性のほんの一部しか紹介しませんが、アニメーションを構築するための基盤を提供します。目標は、プログレッシブにアニメーション化することです。テキストはデフォルトで読み取り可能で、その上にアニメーションが構築されている必要があります。テキスト分割のモーション効果は過剰になり、混乱を招く可能性があるため、ユーザーがモーションを許可している場合にのみ、HTML を操作するか、モーション スタイルを適用します。

ワークフローと結果の概要は次のとおりです。

  1. CSS と JS のモーション軽減条件変数を準備します。
  2. JavaScript で分割テキスト ユーティリティを準備します。
  3. ページの読み込み時に条件とユーティリティをオーケストレートします。
  4. 文字や単語の CSS トランジションとアニメーションを記述します(楽しい部分です)。

条件付きの結果のプレビューを次に示します。

Chrome デベロッパー ツールのスクリーンショット。[要素] パネルが開いており、モーションの低減が [低減] に設定されている。h1 は分割されていない。
ユーザーがモーションの削減を希望している: テキストは読みやすく、分割されていない

ユーザーがモーションの低減を希望している場合は、HTML ドキュメントをそのままにして、アニメーションは行いません。動きが問題なければ、分割します。JavaScript でテキストを文字ごとに分割した後の HTML のプレビューは次のとおりです。

[要素] パネルが開いており、モーションの低減が [低減] に設定されている Chrome デベロッパー ツールのスクリーンショット。h1 は分割されていない状態で表示されています。
ユーザーはモーションを許可しており、テキストが複数の <span> 要素に分割されている

モーション条件の準備

このプロジェクトでは、CSS と JavaScript から 利用可能@media (prefers-reduced-motion: reduce) メディアクエリが使用されます。このメディアクエリは、テキストを分割するかどうかを決定するための主な条件です。CSS メディアクエリはトランジションとアニメーションを保留するために使用され、JavaScript メディアクエリは HTML 操作を保留するために使用されます。

CSS 条件の準備

PostCSS を使用して Media Queries Level 5 の構文を有効にしました。これにより、メディアクエリのブール値を変数に保存できます。

@custom-media --motionOK (prefers-reduced-motion: no-preference);

JS 条件の準備

JavaScript では、ブラウザにメディアクエリを確認する方法が用意されています。分割代入を使用して、メディアクエリのチェックからブール値の結果を抽出して名前を変更しました。

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

次に motionOK をテストし、ユーザーがモーションの低減をリクエストしていない場合にのみドキュメントを変更できます。

if (motionOK) {
  // document split manipulations
}

PostCSS を使用して Nesting Draft 1@nest 構文を有効にすると、同じ値を確認できます。これにより、親と子の両方のアニメーションに関するロジックとスタイル要件を 1 か所に保存できます。

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

PostCSS カスタム プロパティと JavaScript ブール値を使用して、エフェクトを条件付きでアップグレードする準備が整いました。次のセクションでは、文字列を要素に変換する JavaScript について説明します。

テキストの分割

CSS や JS を使用して、テキストの文字、単語、行などを個別にアニメーション化することはできません。この効果を実現するには、ボックスが必要です。各文字をアニメーション化する場合は、各文字を要素にする必要があります。各単語をアニメーション化する場合は、各単語を要素にする必要があります。

  1. 文字列を要素に分割するための JavaScript ユーティリティ関数を作成する
  2. これらのユーティリティの使用をオーケストレートする

文字分割ユーティリティ関数

文字列を受け取り、各文字を配列で返す関数から始めるとよいでしょう。

export const byLetter = text =>
  [...text].map(span)

ES6 のスプレッド構文は、このタスクを迅速に実行するのに非常に役立ちました。

単語分割ユーティリティ関数

文字の分割と同様に、この関数は文字列を受け取り、各単語を配列で返します。

export const byWord = text =>
  text.split(' ').map(span)

JavaScript 文字列の split() メソッドを使用すると、どの文字でスライスするかを指定できます。単語間の分割を示す空白スペースを渡しました。

ボックスのユーティリティ関数を作成する

この効果では、各文字のボックスが必要になります。これらの関数では、map()span() 関数で呼び出されています。span() 関数は次のとおりです。

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

--index というカスタム プロパティが配列の位置で設定されていることに注意してください。文字アニメーション用のボックスがあるのは素晴らしいことですが、CSS で使用するインデックスがあるのは、一見すると小さな追加ですが、大きな影響があります。この大きな影響で最も注目すべきは、スタッガリングです。--index を使用して、アニメーションをオフセットし、ずらして表示できるようになります。

ユーティリティのまとめ

完成した splitting.js モジュール:

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

export const byLetter = text =>
  [...text].map(span)

export const byWord = text =>
  text.split(' ').map(span)

次は、これらの byLetter() 関数と byWord() 関数をインポートして使用します。

分割オーケストレーション

分割ユーティリティを使用する準備が整ったら、すべてをまとめるには次の手順を行います。

  1. 分割する要素を見つける
  2. テキストを分割して HTML に置き換える

その後、CSS が引き継ぎ、要素 / ボックスをアニメーション化します。

要素の検索

属性と値を使用して、目的のアニメーションとテキストの分割方法に関する情報を保存することにしました。これらの宣言型オプションを HTML に配置するのが気に入りました。属性 split-by は、JavaScript から要素を見つけて、文字または単語のいずれかのボックスを作成するために使用されます。属性 letter-animation または word-animation は、CSS から要素の子をターゲットにして変換とアニメーションを適用するために使用されます。

2 つの属性を示す HTML の例を次に示します。

<h1 split-by="letter" letter-animation="breath">animated letters</h1>
<h1 split-by="word" word-animation="trampoline">hover the words</h1>

JavaScript から要素を見つける

属性の存在に関する CSS セレクタ構文を使用して、テキストを分割する要素のリストを取得しました。

const splitTargets = document.querySelectorAll('[split-by]')

CSS から要素を見つける

また、CSS で属性存在セレクタを使用して、すべての文字アニメーションに同じベーススタイルを適用しました。後で、属性値を使用して、より具体的なスタイルを追加して効果を実現します。

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

テキストをその場で分割する

JavaScript で見つかった分割対象ごとに、属性の値に基づいてテキストを分割し、各文字列を <span> にマッピングします。次に、要素のテキストを、作成したボックスに置き換えます。

splitTargets.forEach(node => {
  const type = node.getAttribute('split-by')
  let nodes = null

  if (type === 'letter') {
    nodes = byLetter(node.innerText)
  }
  else if (type === 'word') {
    nodes = byWord(node.innerText)
  }

  if (nodes) {
    node.firstChild.replaceWith(...nodes)
  }
})

オーケストレーションのまとめ

index.js の完了:

import {byLetter, byWord} from './splitting.js'

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

if (motionOK) {
  const splitTargets = document.querySelectorAll('[split-by]')

  splitTargets.forEach(node => {
    const type = node.getAttribute('split-by')
    let nodes = null

    if (type === 'letter')
      nodes = byLetter(node.innerText)
    else if (type === 'word')
      nodes = byWord(node.innerText)

    if (nodes)
      node.firstChild.replaceWith(...nodes)
  })
}

この JavaScript は次のように解釈できます。

  1. いくつかのヘルパー ユーティリティ関数をインポートします。
  2. このユーザーでモーションが許可されているかどうかを確認し、許可されていない場合は何も行いません。
  3. 分割する各要素。
    1. 分割方法に基づいて分割します。
    2. テキストを要素に置き換えます。

アニメーションとトランジションを分割する

上記のドキュメント分割操作により、CSS または JavaScript を使用した多数のアニメーションと効果が実現可能になりました。この記事の末尾に、分割の可能性を広げるためのリンクをいくつかご紹介します。

この機能を使ってできることをご紹介します。CSS ドリブン アニメーションとトランジションを 4 つご紹介します。🤓

文字の分割

文字分割効果の基礎として、次の CSS が役立ちました。すべてのトランジションとアニメーションを motion メディアクエリの背後に配置し、新しい子文字 span に display プロパティと、空白の処理方法のスタイルを指定します。

[letter-animation] > span {
  display: inline-block;
  white-space: break-spaces;
}

空白文字のスタイルは、空白文字のみのスパンがレイアウト エンジンによって折りたたまれないようにするために重要です。次に、ステートフルな楽しい部分に進みます。

トランジション スプリット文字の例

この例では、CSS トランジションを使用してテキスト分割効果を実現しています。トランジションでは、エンジンがアニメーション化する状態が必要になります。ここでは、ホバーなし、文中のホバー、文字のホバーの 3 つの状態を選択しました。

ユーザーが文(コンテナ)にカーソルを合わせると、ユーザーが子要素をさらに遠ざけたかのように、すべての子要素が縮小されます。ユーザーが文字にカーソルを合わせると、その文字が前面に表示されます。

@media (--motionOK) {
  [letter-animation="hover"] {
    &:hover > span {
      transform: scale(.75);
    }

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:hover {
        transform: scale(1.25);
      }
    }
  }
}

分割された文字のアニメーションの例

この例では、事前定義された @keyframe アニメーションを使用して各文字を無限にアニメーション化し、インライン カスタム プロパティ インデックスを活用してスタガー効果を作成しています。

@media (--motionOK) {
  [letter-animation="breath"] > span {
    animation:
      breath 1200ms ease
      calc(var(--index) * 100 * 1ms)
      infinite alternate;
  }
}

@keyframes breath {
  from {
    animation-timing-function: ease-out;
  }
  to {
    transform: translateY(-5px) scale(1.25);
    text-shadow: 0 0 25px var(--glow-color);
    animation-timing-function: ease-in-out;
  }
}

単語の分割

これらの例では、Flexbox がコンテナ タイプとして機能し、ch 単位を適切なギャップ長としてうまく活用しています。

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
単語間のギャップを示す Flexbox デベロッパー ツール

トランジションで単語を分割する例

このトランジションの例でも、ホバーを使用しています。この効果は、ホバーするまでコンテンツを非表示にするため、デバイスにホバー機能がある場合にのみインタラクションとスタイルが適用されるようにしました。

@media (hover) {
  [word-animation="hover"] {
    overflow: hidden;
    overflow: clip;

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:not(:hover) {
        transform: translateY(50%);
      }
    }
  }
}

単語の分割をアニメーション化する例

このアニメーションの例では、CSS の @keyframes を再び使用して、通常のテキスト段落に無限に繰り返されるアニメーションを作成しています。

[word-animation="trampoline"] > span {
  display: inline-block;
  transform: translateY(100%);
  animation:
    trampoline 3s ease
    calc(var(--index) * 150 * 1ms)
    infinite alternate;
}

@keyframes trampoline {
  0% {
    transform: translateY(100%);
    animation-timing-function: ease-out;
  }
  50% {
    transform: translateY(0);
    animation-timing-function: ease-in;
  }
}

まとめ

私がどのようにして達成したかをご理解いただけたかと思います。では、あなたならどうしますか?🙂

アプローチを多様化し、ウェブで構築するさまざまな方法を学びましょう。Codepen を作成するか、独自のデモをホストして、そのデモをツイートしてください。下のコミュニティ リミックス セクションに追加します。

ソース

その他のデモとアイデア

コミュニティ リミックス