CSS講座 / transformとtransition:変形・状態変化・動きの軽減
transformは周囲の通常の配置を変えずに、箱の見た目を移動・回転・拡大します。transitionはプロパティの値が変わる過程を補間するので、対象と時間を選び、動きを減らす場合の表示も用意します。
変形と通常の配置を区別する
translateY(-.5rem)でボタンを上げても、その下の段落が一緒に上がるわけではありません。元の配置上の領域が残り、移動先では重なったり画面外へ出たりする可能性があります。大きく動かす前に余裕を確保します。
translate(1rem, -.5rem)は右へ1rem・上へ.5rem、rotate(15deg)は回転、scale(1.05)は見た目を1.05倍にします。カードの実際の列幅を広げたいなら、Gridやwidthを変更します。通常の非置換インラインのspanなどは変形の対象にならないため、必要ならinline-blockなどを使います。MDNのtransformを参照してください。
関数の順番と変形原点を比べる
変形関数は座標系へ作用するため、移動と回転の順を変えると一般に結果も変わります。下のAとBでは、移動して回転する並びと、回転の影響を受ける方向へ移動する並びを比較します。名前が同じでも順番を無視しないでください。
transform-originは回転や拡大の基準点です。centerからleft topへ変えるだけでも周囲への重なり方が変わります。次をtransform-compare.htmlとしてUTF-8で保存します。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Transform order and origin</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
.comparisons { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); gap: 1rem; }
.slot { padding: 1rem; border: 1px solid #8a9ba7; }
.frame { height: 9rem; display: grid; place-items: center; background: #f0f2f4; }
.box { width: 4rem; height: 2rem; display: grid; place-items: center; background: #166b92; color: white; }
.first { transform: translateX(2rem) rotate(45deg); }
.second { transform: rotate(45deg) translateX(2rem); }
.center { transform: rotate(30deg); transform-origin: center; }
.corner { transform: rotate(30deg); transform-origin: left top; }
</style>
</head>
<body>
<main>
<h1>The same value also varies depending on the order and reference point</h1>
<div class="comparisons">
<section class="slot"><h2>Translate, then rotate</h2><div class="frame"><div class="box first">A</div></div></section>
<section class="slot"><h2>Rotate, then translate</h2><div class="frame"><div class="box second">B</div></div></section>
<section class="slot"><h2>Center origin</h2><div class="frame"><div class="box center">C</div></div></section>
<section class="slot"><h2>Top-left origin</h2><div class="frame"><div class="box corner">D</div></div></section>
</div>
</main>
</body>
</html>
.secondの関数順をAと同じにして相対位置を比べ、元の順へ戻します。CとDは同じ角度なので、.cornerのoriginだけをcenterへ変えて基準点の差を見てleft topへ戻します。Cの回転だけをscale(1.2)へ変える比較では、周囲が再配置されないことも確認し、rotate(30deg)へ戻します。
遷移するプロパティと時間を選ぶ
transition自身が状態を変えるわけではありません。ホバー、フォーカス、クラス変更などで値が変わると遷移します。プロパティ、所要時間、進み方、開始遅延の順で指定でき、遅延の省略時は0秒です。
transition: transform 180ms ease 0ms, background-color 180ms ease 0ms;
180msは0.18秒です。easeは進み方に緩急があり、linearは一定の割合で進みます。まず短い時間と遅延なしで、操作への反応を確認します。
基本のルールへtransitionを置くと、入るときと戻るときの両方へ使えます。:hoverだけに置く場合は、ホバー解除時に宣言も外れます。allで無関係な変更まで対象にせず、この例のtransformとbackground-colorのように意図したものを指定します。すべてのプロパティが同じ補間をするわけではありません。MDNのtransitionを参照してください。
ホバーとキーボードフォーカスを比較する
次をtransition-state.htmlとして保存します。ボタンへマウスを重ねるかTabで移動すると、上への移動と背景色の変化が起こり、フォーカスには濃い枠が付きます。押した後の処理を持たない、状態観察用のボタンです。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Transforms and state transitions exercise</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
.stage { padding: 2rem 1rem; border: 2px dashed #8a9ba7; }
.action { padding: .75rem 1.25rem; border: 1px solid #166b92; border-radius: .4rem;
color: #183044; background: #eaf4fa; transform: translateY(0);
transition: transform 180ms ease, background-color 180ms ease; }
.action:hover, .action:focus-visible { transform: translateY(-.5rem); background: #cfe8f4; }
.neighbor { margin-block: 1rem 0; padding: .75rem; background: #f0f2f4; }
@media (prefers-reduced-motion: reduce) {
.action { transition: none; }
.action:hover, .action:focus-visible { transform: none; }
}
</style>
</head>
<body>
<main>
<h1>The paragraph stays in place as the button moves</h1>
<p>Hover over the button or press Tab to focus it.</p>
<div class="stage">
<button class="action" type="button">Check state</button>
<p class="neighbor">This paragraph stays in place when the button is transformed.</p>
</div>
</main>
</body>
</html>
- 下の段落の位置を覚え、ボタンだけが動くか確認します。
- 二か所の180msを800msへ変えて往復を観察し、180msへ戻します。
- 時間は維持し、進み方だけをlinearへ変えて比べ、easeへ戻します。
- 最初の遷移へだけ400msの遅延を加え、色と移動の開始を比べて削除します。
- Tabでボタンへ入り、Shift+Tabで戻ります。色だけでなくフォーカス枠も見えるか確認します。
動きを減らす条件と副作用を確認する
prefers-reduced-motion: reduceでは、例の装飾的な移動をnoneにし、背景色とフォーカス表示を残します。transitionだけを外すと移動が瞬時になるだけで、移動後の位置は残るため、不要な移動自体も止めています。MDNの動きを減らす設定を参照してください。
OSの設定または開発者ツールのRenderingで条件を再現し、動かなくても状態が分かるか確認します。比較が終わったら再現設定を元へ戻します。
none以外のtransformはスタッキングコンテキストを作り、absoluteやfixedの子の包含ブロックにもなり得ます。ポップアップの位置や前後が変なら、変形した祖先を調べます。位置の基準、重なり順と合わせて確認します。




