GODRICH175

CSS バリデーション アニメーション9選 — フォーム入力エラーの見せ方

CSS バリデーションのアニメーションは、揺れではなく色・アイコン・グローのような別の合図で、フォームの値が合っているか間違っているかを伝える動きです。以下9つはコピペで動く形にまとめ、会員登録フォームで実際に起きる順番に並べました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9つは人気順ではなく、入力フォーム エラー表示をフォームを入力する人が実際に体験する順番で並べています。入力中にリアルタイムで出るメッセージ(01)からニックネームの使用可否(02)へ、検証を通過したフィールドが浮き上がる合図(03)と保存完了バッジ(04)を経て、消えずに残るエラー(05)と確認中の状態アイコン(06)、パスワード強度(07)、送信ボタンの一連の流れ(08)、最後はモーションを減らした人にも合図が残るか(09)という順です。

01リアルタイムのインラインエラー

html css バリデーションで何が見えるか

メールアドレス欄に不正な形式の値を入れると、その下にあらかじめ確保した行がわずかに滑り込んでから、また消えます。

opacitytranslateY2s 루프

コードのどこを見るか

使っているのは opacitytranslateY だけなので、メッセージが現れている間も入力欄の高さ自体は変わりません。

.fv1__msg {
  opacity: 0;
  transform: translateY(-6px);
}
@keyframes fv1-in {
  0%, 6% { opacity: 0; transform: translateY(-6px); }
  28%, 78% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-6px); }
}
.fv1.is-demo .fv1__msg { animation: fv1-in $duration $easing infinite; }

02ラベルの状態色クロスフェード

画面で起きること

ニックネーム欄の上のラベルが、グレーの「ニックネーム」からミント色の「使用できるニックネームです」に変わります。

opacity 크로스페이드absolute 겹침2s 루프

コードの読み方

color そのものを遷移させているのではなく、同じ位置に重ねた2枚のラベルが opacity で入れ替わっています。

@keyframes fv2-ok {
  0%, 20% { opacity: 0; }
  40%, 90% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes fv2-neutral {
  0%, 20% { opacity: .6; }
  40%, 90% { opacity: 0; }
  100% { opacity: .6; }
}
.fv2.is-demo .fv2__label--ok { animation: fv2-ok $duration $easing infinite; }
.fv2.is-demo .fv2__label--neutral { animation: fv2-neutral $duration $easing infinite; }

03フィールド検証のグローリング

どんな動きか

検証に通ったフィールドの背後に、柔らかいミント色のリングが浮き上がります。

box-shadow 레이어scale 펄스opacity

どこを変えるか

入力欄の裏に重ねた box-shadow レイヤーが opacity 0→1 と scale(.94→1.04) を組み合わせ、ふわっと膨らむような印象を作っています。

.fv3__glow {
  position: absolute;
  inset: -6px;
  border-radius: $r-control;
  box-shadow: 0 0 0 4px rgba(76, 212, 166, .55);
  opacity: 0;
  transform: scale(.94);
}
@keyframes fv3-glow {
  0%, 15% { opacity: 0; transform: scale(.94); }
  45% { opacity: 1; transform: scale(1.04); }
  75%, 100% { opacity: 0; transform: scale(.94); }
}
.fv3.is-demo .fv3__glow { animation: fv3-glow $duration $easing infinite; }

04成功バッジのバウンスチェック

何が見えるか

保存が終わると円形バッジが弾むように現れ、その中のチェックが線を引くように完成します。

scale 바운스stroke-dashoffsetopacity

コードのどこを見るか

バッジは scale(.4→1.12→1) でバウンスし、チェックはSVGの polyline 一本の stroke-dashoffset を24から0まで減らして描いています。

@keyframes fv4-badge {
  0%, 10% { opacity: 0; transform: scale(.4); }
  35% { opacity: 1; transform: scale(1.12); }
  50% { transform: scale(1); }
  85%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes fv4-draw {
  0%, 30% { stroke-dashoffset: 24; }
  55%, 100% { stroke-dashoffset: 0; }
}
.fv4__path { stroke-dasharray: 24; stroke-dashoffset: 24; }
.fv4.is-demo .fv4__badge { animation: fv4-badge $duration $easing infinite; }
.fv4.is-demo .fv4__path { animation: fv4-draw $duration linear infinite; }

05エラーフィールドの呼吸リング

画面で起きること

シェイクと違い、このフィールド自体は一切動きません。

box-shadow 브리딩opacityscale

コードの読み方

周りの淡いコーラル色のリングが opacityscale(.97↔1.03) の間をゆっくり呼吸するように往復し、直っていないエラーを静かに伝え続けます。

.fv5__ring {
  position: absolute;
  inset: -5px;
  border-radius: $r-control;
  box-shadow: 0 0 0 3px $error-soft;
  opacity: 0;
  transform: scale(.97);
}
@keyframes fv5-breathe {
  0%, 100% { opacity: 0; transform: scale(.97); }
  50% { opacity: 1; transform: scale(1.03); }
}
.fv5.is-demo .fv5__ring { animation: fv5-breathe $duration $easing infinite; }

06ステータスアイコンのスピナー→チェック・クロス変化

どんな動きか

ユーザー名の重複確認中、フィールド右側のアイコンがスピナーで回った後、ミント色のチェックへ、続いてコーラル色のクロスへ引き継がれます。

rotateopacity 크로스페이드svg 3겹

どこを変えるか

3つのアイコンを同じ位置に重ねて opacity でクロスフェードさせ、rotate はスピナーだけに掛けています。

@keyframes fv6-spinner-show { 0%, 32% { opacity: 1; } 38%, 100% { opacity: 0; } }
@keyframes fv6-check-show { 0%, 32% { opacity: 0; } 40%, 62% { opacity: 1; } 70%, 100% { opacity: 0; } }
@keyframes fv6-cross-show { 0%, 62% { opacity: 0; } 70%, 96% { opacity: 1; } 100% { opacity: 0; } }
.fv6.is-demo .fv6__spinner {
  animation: fv6-spin $dur-base linear infinite,
             fv6-spinner-show $duration $easing infinite;
}
.fv6.is-demo .fv6__check { animation: fv6-check-show $duration $easing infinite; }
.fv6.is-demo .fv6__cross { animation: fv6-cross-show $duration $easing infinite; }

073段階パスワード強度バー

何が見えるか

同じトラックの上に、弱・中・強の3本のバーが重なっています。

scaleXopacity 단계 교체transform-origin:left

コードのどこを見るか

それぞれ scaleX(transform-origin: left)で伸びたあと opacity で次の段階に引き継がれるため、色がじわじわ変わるのではなくバーそのものが差し替わって見えます。

.fv7__fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 100%;
  transform-origin: left center;
  transform: scaleX(0);
  opacity: 0;
}
@keyframes fv7-strong {
  0%, 68% { transform: scaleX(.64); opacity: 0; }
  78%, 96% { transform: scaleX(1); opacity: 1; }
  100% { opacity: 0; transform: scaleX(0); }
}
.fv7.is-demo .fv7__fill--strong { animation: fv7-strong $duration $easing infinite; }

08送信ボタンのローディング→成功変化

画面で起きること

1つのボタンが、押される動き(scale(1→.96→1))→ローディングスピナー→ミント色のチェックの順に、1つのループの中で切り替わっていきます。

scalerotateopacity 크로스페이드

コードの読み方

ラベルが opacity で消えた場所でスピナーが rotate で回り、opacityscale のクロスフェードでチェックへ引き継がれます。

@keyframes fv8-press { 0%, 8% { transform: scale(1); } 14% { transform: scale(.96); } 22%, 100% { transform: scale(1); } }
@keyframes fv8-check-show {
  0%, 66% { opacity: 0; transform: scale(.5); }
  74% { opacity: 1; transform: scale(1.15); }
  86%, 100% { opacity: 1; transform: scale(1); }
}
.fv8.is-demo { animation: fv8-press $duration $easing infinite; }
.fv8.is-demo .fv8__check { animation: fv8-check-show $duration $easing infinite; }

09モーション削減時は静止表示に

どんな動きか

通常はステータスアイコンが scale(.5→1.15→1)opacity でポップインしてから消えるループですが、モーションを減らす設定にすると、アニメーションが完全に切れ、チェックと文言は動かず最終状態のまま固定表示されます。

scale+opacity 팝prefers-reduced-motion정지 상태 고정

どこを変えるか

@keyframes fv9-pop {
  0%, 10% { opacity: 0; transform: scale(.5); }
  40% { opacity: 1; transform: scale(1.15); }
  55%, 85% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(.5); }
}
.fv9.is-demo .fv9__status { animation: fv9-pop $duration $easing infinite; }
@media (prefers-reduced-motion: reduce) {
  .fv9.is-demo .fv9__status { animation: none !important; opacity: 1; transform: scale(1); }
}

どこで落とし穴にはまるか — クロスフェードの罠

02番を作っているときに実際に踏んだ罠です。中立ラベルと成功ラベルを opacity でクロスフェードさせるとき、同じキーフレーム1つを animation-direction: reverse で鏡のように逆再生すればコードが短くなって楽だと考えました。ところが reverse は進行率(0〜100%)を反転させるだけで、キーフレームが完全に対称でない限り、ループの中間あたりで両方のラベルの opacity が同時に1になる瞬間が生まれ、文字が重なって見えてしまいました。レンダリングしたフレームを1枚ずつ開いて初めて気づき、直した方法は reverse をやめて、opacity の値がすべての段で正確に反対になる2つ目の @keyframes(fv2-neutral)を新しく書くことでした。9つ全部のvanilla・Reactコードが入った圧縮ファイルのパスワードはmk7s3ujkで、02番フォルダのこの2つの @keyframes を並べて見ると違いがそのまま見えます。

アクセシビリティ

9つとも prefers-reduced-motion: reduce でアニメーションを切ります。ただし03・04・07・08は形の変化そのものが情報なので、アニメーションだけ切ると結果自体が見えなくなってしまいます。これらの項目は途中の動きを飛ばし、最終状態(グローが点いた状態、チェックが描き終わった状態、バーが満タンの状態、送信ボタンにチェックが出た状態)をそのまま表示します。MDNの prefers-reduced-motion のドキュメントによれば、この値はOSの設定をそのまま読み取るため、モーションを減らしても検証結果という情報自体は残す必要があります。transformopacity だけを動かしているのは、web.devの高性能アニメーションガイドにもある通り、この2つはレイアウトの再計算を伴わないためです。

揺れでエラーを伝えるバージョンはCSS 揺れるエラーアニメーション9選、チェックボックス・ラジオボタンの検証表示はCSS カスタムチェックボックス9選にあります。

FAQ

css バリデーションチェックで色を直接遷移させず opacity クロスフェードを使うのはなぜですか?

colorborder-color を直接遷移させると、ブラウザは毎フレーム中間色を計算する必要があり、色がCSS変数から来ている画面(ダークモードなど)では中間色がおかしく見えることがあります。最終的な色をすでに持つ要素を2つ重ねて opacity だけを動かせば、この計算自体が不要になり、色は最初のフレームから正しい値のままです。

チェックアイコンは stroke-dashoffset の代わりに棒2本の回転で描いてもいいですか?

描けますが、2本の棒が同じ頂点から始まっていないと、チェックが斜め一本の線に見えてしまう不具合がよくあります。04番のようにSVGの polylinestroke-dasharraystroke-dashoffset で描けば、この始点のずれ自体が起きません。

9つを1つのフォームに全部同時に使っても大丈夫ですか?

動きますが、05の呼吸リングと03のグローのように常時動くループを何個も並べると、画面がせわしなく見えます。02・06のように状態が変わったときだけ動くものは残しつつ、05のように常にループするものは1画面に1つ以下にとどめるのがおすすめです。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。