GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

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

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

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

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

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

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

メールアドレス欄に不正な形式の値を入れると、その下にあらかじめ確保した行がわずかに滑り込んでから、また消えます。メッセージが現れる瞬間、フィールドにはサブピクセル単位で収まる translateX の減衰振動が一度走り、同時に枠の外側へ広がるグローの波がエラーをもう一段強調します。

リアルタイムのインラインエラー
新しいタブで開く

01 · リアルタイムのインラインエラー — 計測 累積面積 3.469% · 動いたフレーム 13/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

opacitytranslateY2s 루프

コードのどこを見るか

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

.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ラベルの状態色クロスフェード

画面で起きること

ニックネーム欄の上のラベルが、グレーの「ニックネーム」からミント色の「使用できるニックネームです」に変わります。切り替わる瞬間、フィールドに軽い減衰振動が一度走り、ミント色のグローが広がって静かに収まります。

ラベルの状態色クロスフェード
新しいタブで開く

02 · ラベルの状態色クロスフェード — 計測 累積面積 3.418% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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フィールド検証のグローリング

どんな動きか

検証に通ったフィールドの背後に、柔らかいミント色のリングが浮き上がります。ピークを過ぎるとぱたりと消えるのではなく、二段・三段と弱まる波が余韻のように収まっていきます。

フィールド検証のグローリング
新しいタブで開く

03 · フィールド検証のグローリング — 計測 累積面積 3.533% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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成功バッジのバウンスチェック

何が見えるか

保存が終わると円形バッジが弾むように現れ、その中のチェックが線を引くように完成します。バッジが現れる瞬間には rotate の減衰振動が左右に揺れながら収まり、同じ拍子でバッジの周りにグローリングが広がります。

成功バッジのバウンスチェック
新しいタブで開く

04 · 成功バッジのバウンスチェック — 計測 累積面積 3.188% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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エラーフィールドの呼吸リング

画面で起きること

シェイクと違い、このフィールド自体は一切動きません。呼吸するリングはピーク後にぱたりと止まらず、弱まる波を残しながら収まっていくので、052番のような位置移動なしでも同じ場所で波の余韻を感じられます。

エラーフィールドの呼吸リング
新しいタブで開く

05 · エラーフィールドの呼吸リング — 計測 累積面積 2.63% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

box-shadow 브리딩opacityscale

コードの読み方

周りの淡いコーラル色のリングが opacity と scale(.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ステータスアイコンのスピナー→チェック・クロス変化

どんな動きか

ユーザー名の重複確認中、フィールド右側のアイコンがスピナーで回った後、ミント色のチェックへ、続いてコーラル色のクロスへ引き継がれます。切り替わるたびに減衰振動とその状態色のグローが走り、チェックではごく控えめに、クロスでははっきりと強弱を変えて悪い知らせにより重みを持たせます。

ステータスアイコンのスピナー→チェック・クロス変化
新しいタブで開く

06 · ステータスアイコンのスピナー→チェック・クロス変化 — 計測 累積面積 1.374% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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本のバーが重なっています。各段階に到達する瞬間、トラックが scaleY で一度弾んで収まり、トラックの外周にはその段階の色のグローが波のように広がります。

3段階パスワード強度バー
新しいタブで開く

07 · 3段階パスワード強度バー — 計測 累積面積 1.161% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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つのループの中で切り替わっていきます。チェックが現れる瞬間にはボタンの周りにミント色のグローリングが広がり、rotate の減衰振動が一度揺れて収まります。

送信ボタンのローディング→成功変化
新しいタブで開く

08 · 送信ボタンのローディング→成功変化 — 計測 累積面積 4.267% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scalerotateopacity 크로스페이드

コードの読み方

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

@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 でポップインしてから消えるループですが、モーションを減らす設定にすると、アニメーションが完全に切れ、チェックと文言は動かず最終状態のまま固定表示されます。ポップインの瞬間にはごく淡いグローの波も一度広がりますが、モーション削減ではこれも他と一緒に消えます。

モーション削減時は静止表示に
新しいタブで開く

09 · モーション削減時は静止表示に — 計測 累積面積 0.675% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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)を新しく書くことでした。合図を抑える代わりに画面全体を重ねて積み上げる逆の方向はコラージュ デザインのLPパーツ9選で見られます。9つ全部のvanilla・Reactコードが入った圧縮ファイルのパスワードはmk7s3ujkで、02番フォルダのこの2つの @keyframes を並べて見ると違いがそのまま見えます。

アクセシビリティ

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

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

FAQ

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

color や border-color を直接遷移させると、ブラウザは毎フレーム中間色を計算する必要があり、色がCSS変数から来ている画面(ダークモードなど)では中間色がおかしく見えることがあります。最終的な色をすでに持つ要素を2つ重ねて opacity だけを動かせば、この計算自体が不要になり、色は最初のフレームから正しい値のままです。合否をフィールドではなく表情で伝える側はcss キャラクター アニメーション 9選が担います。

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

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

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

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

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

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