transform は要素の 描き方 を変えます。移動、拡大縮小、回転、傾斜です。下の点線の枠は、transform がなければ各ボックスがあるはずの場所を示しています:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Transform functions</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.row { display: flex; gap: 40px; padding: 30px 20px; }
.slot { outline: 2px dashed #9ca3af; width: 90px; height: 60px; }
.box { width: 90px; height: 60px; background: #2563eb; color: white; display: grid; place-items: center; }
.t1 { transform: translate(20px, 10px); }
.t2 { transform: scale(1.3); background: #16a34a; }
.t3 { transform: rotate(20deg); background: #f59e0b; color: #111827; }
.t4 { transform: skewX(-20deg); background: #111827; }
</style>
</head>
<body>
<div class="row">
<div class="slot"><div class="box t1">translate</div></div>
<div class="slot"><div class="box t2">scale</div></div>
<div class="slot"><div class="box t3">rotate</div></div>
<div class="slot"><div class="box t4">skewX</div></div>
</div>
</body>
</html>
translate(20px, 10px) は青いボックスを右に 20px、下に 10px 動かします。scale(1.3) は緑のボックスを中心から 30% 大きくします。rotate(20deg) は琥珀色のボックスを時計回りに回転させ、skewX(-20deg) は暗い色のボックスを傾けます。どの場合も、点線の枠 (要素がレイアウトで取る場所) はそのままです。
| 関数 | 働き | 例 |
|---|
translate(x, y) | 移動 | translate(20px, -10px)、translate(-50%, -50%) |
translateX(x) / translateY(y) | 1 つの軸で移動 | translateY(-4px) |
scale(s) / scale(x, y) | 拡大縮小 (1 = 元のサイズ、0.5 = 半分、負の値で反転) | scale(1.1)、scale(-1, 1) |
scaleX(s) / scaleY(s) | 1 つの軸で拡大縮小 | scaleX(0.5) |
rotate(angle) | 時計回りに回転 (負の値で反時計回り) | rotate(45deg)、rotate(0.25turn) |
skew(x, y) / skewX(a) / skewY(a) | 傾斜 | skewX(-15deg) |
matrix(a, b, c, d, e, f) | 上のすべてを 1 つの行列で。主にブラウザが返す形 | |
none | transform なし | |
translate() のパーセントは、親ではなく要素 自身 のサイズが基準です。回転には、度、回転数、回転し続けるアニメーションを扱う専用のページがあります: CSS rotate。
スライダーを動かしてください。1 行目は組み立てた transform の値、2 行目はそこからブラウザが計算した行列で、どんな transform でも getComputedStyle が返すのはこの形です:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Transform playground</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.controls { display: grid; grid-template-columns: repeat(2, max-content); gap: 4px 16px; }
.stage { height: 170px; display: grid; place-items: center; background: #f3f4f6; margin: 10px 0; overflow: hidden; }
#box { width: 80px; height: 80px; background: #2563eb; color: white; display: grid; place-items: center; border-radius: 6px; }
code { display: block; }
</style>
</head>
<body>
<div class="controls">
<label>translateX <input type="range" data-f="translateX" data-u="px" min="-120" max="120" value="0"></label>
<label>scale <input type="range" data-f="scale" data-u="" min="0.5" max="1.8" step="0.1" value="1"></label>
<label>rotate <input type="range" data-f="rotate" data-u="deg" min="-180" max="180" value="0"></label>
<label>skewX <input type="range" data-f="skewX" data-u="deg" min="-40" max="40" value="0"></label>
</div>
<div class="stage"><div id="box">Box</div></div>
<code id="value"></code>
<code id="matrix"></code>
<script>
const box = document.getElementById('box');
const inputs = document.querySelectorAll('input');
const update = () => {
const t = [...inputs].map((i) => i.dataset.f + '(' + i.value + i.dataset.u + ')').join(' ');
box.style.transform = t;
document.getElementById('value').textContent = 'transform: ' + t + ';';
document.getElementById('matrix').textContent = 'computed: ' + getComputedStyle(box).transform;
};
inputs.forEach((i) => i.addEventListener('input', update));
update();
</script>
</body>
</html>
transform した要素は別の場所に描かれますが、ページは transform がないかのように配置されます。隣の要素は位置を保ちます。これが transform がホバーの効果やアニメーションに向いている理由で、周りの何も配置し直されません。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>No reflow</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.row { display: flex; gap: 10px; padding: 24px 10px; }
.row div { width: 80px; height: 50px; background: #e5e7eb; display: grid; place-items: center; }
.row .grow-t { transform: scale(1.5); background: #16a34a; color: white; }
.row .grow-w { width: 120px; height: 75px; background: #f59e0b; }
</style>
</head>
<body>
<div class="row" id="a"><div>A</div><div class="grow-t">scale(1.5)</div><div id="c1">C</div></div>
<div class="row" id="b"><div>A</div><div class="grow-w">width 120px</div><div id="c2">C</div></div>
<p id="out"></p>
<script>
document.getElementById('out').textContent =
'C starts at x = ' + document.getElementById('c1').offsetLeft + 'px next to the scaled box, and at x = ' +
document.getElementById('c2').offsetLeft + 'px next to the resized one.';
</script>
</body>
</html>
真ん中の 2 つのボックスは、どちらも 1.5 倍に見えます。scale したほうは隣と重なり、C はそのままです。width と height で大きくしたほうは、C を 40px 先に押し出します。
1 つの transform に、空白で区切って複数の関数を書きます。順番で結果が変わります。左から右に読んで、各関数はそれより前の関数が残した座標系の中で働くからです:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Transform order</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.stage { position: relative; height: 170px; background: #f3f4f6; }
.box { position: absolute; top: 20px; left: 20px; width: 60px; height: 40px; opacity: 0.85; }
.a { transform: translateX(120px) rotate(45deg); background: #2563eb; }
.b { transform: rotate(45deg) translateX(120px); background: #16a34a; }
</style>
</head>
<body>
<div class="stage"><div class="box a"></div><div class="box b"></div></div>
<p id="out"></p>
<script>
const center = (el) => { const r = el.getBoundingClientRect(); return Math.round(r.left + r.width / 2) + ', ' + Math.round(r.top + r.height / 2); };
document.getElementById('out').textContent =
'blue (translate then rotate) center: ' + center(document.querySelector('.a')) +
' | green (rotate then translate) center: ' + center(document.querySelector('.b'));
</script>
</body>
</html>
青いボックスはまっすぐ 120px 動いてから、その場で回転しました。緑のボックスは先に回転したので「x 軸」が斜めを向いていて、translateX(120px) で斜め下に動きます。同じ関数で、違う場所です。
もう 1 つの落とし穴: 2 つ目の transform の宣言は 1 つ目を 置き換え ます。カードに transform: rotate(-2deg) があり、ホバーのルールが transform: scale(1.05) だと、ホバーしたときに回転が消えます。両方を書く (rotate(-2deg) scale(1.05)) か、下の個別のプロパティを使いましょう。
拡大縮小、回転、傾斜は、固定点である transform の原点 を中心に行われ、初期値は中央です。キーワード、長さ、パーセントで変えられます:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>transform-origin</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.row { display: flex; gap: 50px; padding: 20px 30px 40px; }
.slot { outline: 2px dashed #9ca3af; width: 80px; height: 50px; }
.box { width: 80px; height: 50px; background: #2563eb; transform: rotate(25deg); }
.tl { transform-origin: top left; }
.br { transform-origin: bottom right; }
</style>
</head>
<body>
<div class="row">
<div><code>center</code><div class="slot"><div class="box"></div></div></div>
<div><code>top left</code><div class="slot"><div class="box tl"></div></div></div>
<div><code>bottom right</code><div class="slot"><div class="box br"></div></div></div>
</div>
</body>
</html>
3 つのボックスはどれも 25 度回転します。1 つ目は真ん中を軸にし、2 つ目は左上の角を枠の角に固定したまま、3 つ目は右下の角を固定したまま回転します。ボタンから広がるドロップダウンメニューには、scale() と transform-origin: top を使います。
translate、scale、rotate プロパティ
現在のブラウザには、関数と同じ働きをしつつ、それぞれ独立して指定やアニメーションができる 3 つの個別のプロパティもあります:
.card {
rotate: -2deg;
transition: scale 0.2s ease-out;
}
.card:hover {
scale: 1.05; /* the rotation stays, because it is a different property */
}
宣言した順番に関係なく、常に transform の値の前に transform: translate(...) rotate(...) scale(...) と書いたかのように組み合わされます。書き方に注意しましょう。translate: 10px 20px と scale: 1.2 は、関数のかっこなしで値を書きます。
translate(-50%, -50%) で中央に置く
translate() のパーセントは要素自身のサイズが基準なので、top: 50%; left: 50%; transform: translate(-50%, -50%) で、absolute で配置したどんなサイズの要素でも親の中央に置けます。flexbox や grid との比較は div の中央寄せのページ にあります。
よくある間違い
transform の宣言が 2 つ。 後のほうが完全に勝ちます。関数をまとめるか、rotate、scale、translate のプロパティを使いましょう。
- インライン要素に transform を付ける。 普通の
<span> や <a> は transform を無視します。inline-block にしましょう。
- 隣の要素が動くと思う。 transform はレイアウトを変えません。スペースを作るには
width、margin、gap を変えます。
- 関数の順番の間違い。
translate() の前に rotate() を書くと、回転した軸に沿って動きます。
position: fixed の子要素で起きる思わぬ動き。 transform した要素は fixed の子孫要素の包含ブロックになるので、中の固定ヘッダーが一緒にスクロールします。z-index のための新しいスタッキングコンテキストも作ります。