HTMLとCSSでつくる! 1文字ずつ変化するテキストのアニメーション

前回、以下の記事でリンクテキストをホバーした際のアニメーション実装例をご紹介しました。

さて、今回は少しだけ複雑になりますが、HTMLとCSSのみでより凝った表現をする方法を紹介します。

リンクテキストといえば、ヘッダーなどのメニューとして小さめのフォントサイズでデザインされていることも多いでしょう。

そんな小さいサイズのテキストでもホバー時に目を引くような、1文字ずつ区切ったテキストアニメーションのアイデアを紹介します。よりサイトの雰囲気に合う演出をしたい、またアイデアの引き出しを増やしたいデザイナーやエンジニアの参考になれば嬉しいです。

▼ 今回紹介する実装例一覧はこちらです。

記事前半では、実装方法について基本となるアニメーションの仕組みと実装時に気をつけたいことを解説し、後半はバリエーションの実装例を掲載していきます。 それでは、まずは基本となる実装例を紹介します。

基本のアニメーション:1文字ずつ下に回転

▼ ホバーしてお試しください

▼リンクテキスト部分のHTML

<a href="#">
  <span aria-hidden="true" translate="no" class="text-wrap">
    <span class="letter" style="--index: 0">S</span>
    <span class="letter" style="--index: 1">E</span>
    <span class="letter" style="--index: 2">R</span>
    <span class="letter" style="--index: 3">V</span>
    <span class="letter" style="--index: 4">I</span>
    <span class="letter" style="--index: 5">C</span>
    <span class="letter" style="--index: 6">E</span>
  </span>
  <span class="sr-only">SERVICE</span>
</a>

▼リンクテキスト部分のCSS

.text-wrap {
  display: flex;
  gap: 8px;
}

.text-wrap:hover .letter {
  text-shadow: 0 0 0 #000, 0 1.5em 0 #000;

}

.letter {
  overflow: hidden;
  color: transparent;
  text-shadow: 0 -1.5em 0 #000, 0 0 0 #000;
  transition: text-shadow 0.2s;
  transition-delay: calc(var(--index) * 0.05s);
}

アニメーションの仕組み

時間差で1文字ずつ変化しています。これを実現しているのが、HTMLでstyle属性に指定したCSS変数(カスタムプロパティ)の--indexと、CSSで設定したtransition-delay: calc(var(--index) * 0.05s)の組み合わせです。calc()関数の計算式に振った番号が入るので、最初の文字(0番目)は0秒後、2文字目(1番目)は0.05秒後、3文字目(2番目)は0.1秒後…といった具合でアニメーションの開始をずらすことがきます。

また、テキストが回転する動きについてはtext-shadowプロパティを活用しているのですが、以下記事の「2. テキストが上下に回転するアニメーション」にて解説していますので詳しくはこちらを閲覧ください。

気をつけたいこと①:意図しないスクリーンリーダーでの読み上げを避ける

文字同士を囲う親要素のspanタグにつけている、aria-hidden="true"の指定について説明します。

<span aria-hidden="true" translate="no" class="text-wrap">

今回spanタグで単語を1文字ずつ区切っているため、そのままでは支援技術のスクリーンリーダーなどで、1文字ずつ区切られて読み上げられてしまいます。たとえば、macOSに標準搭載されているVoiceOverではaria-hidden="true"を指定しない場合、「エス」「イー」「アール」…といったように1文字ずつ読み上げます。「SERVICE」のように短い英単語であれば1文字ずつ読まれても元の単語が分かるかもしれません。しかし漢字を含む日本語の場合は、1文字ずつ読まれると意味が分かりにくくなることがあります。これでは見た目の演出のために支援技術での使い勝手が悪くなってしまいます。

そこで、aria-hidden="true"を設定することで、支援技術からコンテンツを隠しています。

さらに、以下の部分で支援技術向けのテキストを置くことで「サービス」として読み上げ可能にしています。

<span class="sr-only">SERVICE</span>

上記のテキストは表示をさせたくないので、たとえば以下のようにCSSで見た目に影響が出ないように調整をします。

.sr-only {
  position: absolute;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border-width: 0;
  white-space: nowrap;
}

気をつけたいこと②:意図しないGoogle翻訳をされないために

さきほどaria-hidden="true"を指定していたspanタグでは、ほかにtranslate="no"を設定しています。この設定で、Google翻訳のような自動翻訳ツールでは要素の翻訳をしないようにできます。

<span aria-hidden="true" translate="no" class="text-wrap">

この設定がない場合、ページに対してGoogle翻訳をかけると以下のような表示になります。「S そして R で 私 C そして」という全く意味の通じない言葉になってしまいました。

一方で、翻訳対象にしたい場合もあるかと思います。その場合は対策として、アニメーションは無効になりますがlang属性を利用して、日本語ではない場合(lang="ja"以外の場合)に表示させるテキストを用意してCSSで表示を切り替える方法が考えられます。また、アニメーションさせるテキストとは別に、翻訳可能なテキストを添えたデザインにするという手もあるかと思います。

1文字ずつ区切る際に気をつけたいポイントを2つ紹介しました。ここからは、基本の実装を元にCSSを変化させたバリエーションを見ていきます。HTMLは上述したものと共通なので割愛します。

バリエーション:交互に変化

▼ ホバーしてお試しください

▼CSS一部抜粋

.text-wrap {
  display: flex;
  gap: 8px;
}

.letter {
  overflow: hidden;
  color: transparent;
  text-shadow: 0 -1.5em 0 #000, 0 0 0 #000;
  transition: text-shadow 0.2s;
  transition-delay: calc(var(--index) * 0.04s);
}

.text-wrap:hover .letter {
  text-shadow: 0 0 0 orangered, -1.5em 0 0 #000;
}

.text-wrap:hover .letter:nth-child(odd) {
  text-shadow: 0 0 0 blue, 0 1.5em 0 #000;
}

:nth-child()セレクターでoddを指定することで、奇数番目の文字スタイルを上書きしています。これにより、変化する色と回転方向が交互に切り替えることができます。

バリエーション:背景色の変化

▼ ホバーしてお試しください

▼CSS一部抜粋

.text-wrap {
  display: flex;
}

.text-wrap:hover .letter {
  background-color: hsl(calc((var(--index) + 200) * 10deg) 80% 70%);
  color: #fff;
}

.letter {
  width: 20px;
  height: 20px;
  display: grid;
  place-content: center;
  overflow: hidden;
  transition: 0.2s background-color;
  transition-delay: calc(var(--index) * 0.04s);
  color: #fff;
  background-color: darkblue;
}

transition-delay--indexを使用している点はこれまで通りですが、追加でhsl()関数にも使用しています。hsl()関数を使用すると、色を色相・彩度・輝度で指定することができます。今回は色相に当てはめることで1文字ずつ色味が変化していくようにしています。また、+ 200を計算式に入れておくことで、青みのある色相から開始できるよう設定しています。

今回のようなCSS変数とhsl()関数に加えて、@propertyを活用したアニメーション例が以下記事の「色のアニメーション」の章に掲載していますのでこちらもぜひご覧ください。

バリエーション:ふわっと揺れる

▼ ホバーしてお試しください

▼CSS一部抜粋

.text-wrap {
  display: flex;
  gap: 8px;
}

.text-wrap:hover .letter {
  animation: sway 0.4s calc(0.04s * var(--index));
}

@keyframes sway {
  0% {
    transform: translateY(0);
  }
  25% {
    transform: translateY(-0.2em);
  }
  50% {
    transform: translateX(0.5em);
    color: transparent;
  }
  100% {
    transform: translateY(0);
  }
}

@keyframeアットルールを利用したアニメーションの例です。Y方向とX方向に少しだけ動かし、一時的に文字色に透明を指定することでふわっとした印象にしています。animationプロパティの一括指定でanimation-delayの指定位置に基本の実装にもあった--indexを含めた計算式を当てはめることで1文字ずつアニメーションします。スクロール時の演出にも活かせそうな表現です。

バリエーション:グリッチ風

▼ ホバーしてお試しください

▼CSS一部抜粋

/* 背景色の設定 */
body {
  background-color: midnightblue;
}

/* リンクテキストのスタイル */
.text-wrap {
  color: #fff;
  display: flex;
  gap: 8px;
}

.text-wrap:hover .letter {
  animation: glitch 0.8s calc(0.03s * var(--index)) infinite;
  text-shadow: -1px -1px 0 deeppink, 1px 1px 0 cyan;
}

@keyframes glitch {
  0% {
    transform: translate(0, 0) rotate(0deg);
    text-shadow: -1px -1px 0 deeppink, 1px 1px 0 cyan;
  }
  10% {
    transform: translate(-1px, 1px) rotate(-1deg);
  }
  20% {
    transform: translate(1px, -1px) rotate(1deg);
    text-shadow: 1px 2px 0 deeppink, 1px 1px 0 cyan;
  }
  30% {
    transform: translate(-1px, 1px) rotate(0deg);
    text-shadow: -1px -1px 0 deeppink, 1px 1px 0 cyan;
  }
  40% {
    transform: translate(1px, -1px) rotate(-1deg);
  }
  50% {
    transform: translate(-1px, 1px) rotate(1deg);
    text-shadow: 1px 1px 0 deeppink, 1px 1px 0 cyan;
  }
  60% {
    transform: translate(1px, -1px) rotate(0deg);
  }
  70% {
    transform: translate(-1px, 1px) rotate(-1deg);
    text-shadow: -1px -1px 0 deeppink, 1px 1px 0 cyan;
  }
  80% {
    transform: translate(1px, -1px) rotate(1deg);
    text-shadow: 1px 1px 0 deeppink, 1px 2px 0 cyan;
  }
  90% {
    transform: translate(-1px, 1px) rotate(0deg);
  }
  100% {
    transform: translate(0.5px, -0.5px) rotate(-0.5deg);
    text-shadow: -1px -1px 0 deeppink, 1px 1px 0 cyan;
  }
}

一つ前の実装例よりもさらに指定を細かくし、色数を増やしてグリッチ風の演出を作っています。animationプロパティの一括指定の際にinfiniteを追加しているため、ホバーしている間はアニメーションが続きます。フォントサイズを大きくする場合は、あわせてtranslatetext-shadowで動かす値を大きくするなどバランス調整が必要です。

まとめ

一文字ずつ変化するアニメーションのバリエーションを紹介してきました。 実現したい演出に近いアニメーションや制作中のサイトの雰囲気に合う実装例はあったでしょうか? よりよいサイト制作のヒントになれば幸いです。

また、実際に本記事のコードをサイトへ適用する場合に調整したいポイントを、前回記事の以下の章で紹介しています。こちらも閲覧ください。

参考リンク

実装例制作にあたり参考にした記事を掲載します。

上記記事ではとくに、例3: 文字ごとに区切ったテキストの章を参考にしました。

澤田 ナオミ

ウェブデザイナーの経験を経て、フロントエンドエンジニアとしてICSに入社しました。お絵かき、ゲーム、音楽、映画、お笑いが大好きです。

この担当の記事一覧