- CSS
CSS transformの使い方|translate・rotate早見表
CSSのtransformとは、要素を「移動(translate)・回転(rotate)・拡大縮小(scale)・傾斜(skew)」させるプロパティです。 まわりの要素の配置は変えずに、その要素の見た目だけを変形します。ホバー時の動き、中央寄せ、アイコンの回転など、使いどころの多い指定です。
この記事では、全transform関数を書式・単位・用途つきの早見表にまとめ、関数を書く順番で結果が変わる様子、translate(-50%, -50%) の中央寄せと flex・grid の比較、hover で回転が消える上書き問題と translate・rotate・scale の個別プロパティによる解決を、再現コードつきで扱います。さらに、transform を付けた要素の子孫で position: fixed や z-index の挙動が変わる点まで確かめました。数値はすべて Chromium 141(Playwright 1.56 のヘッドレス実行)で測ったものです。
transform関数の早見表
幅120px・高さ80pxの箱に指定したときの結果です(「見た目の箱」は getBoundingClientRect() の幅×高さ)。
| 関数 | 書式の例 | 単位 | 動き | 見た目の箱 | 主な用途 |
|---|---|---|---|---|---|
translate(x, y) |
translate(50px, 20px) |
長さ・% | 右に50px、下に20px移動 | 120×80 | 位置の微調整、中央寄せ |
translateX(x) / translateY(y) |
translateX(50px) |
長さ・% | 1方向だけ移動 | 120×80 | スライドイン |
scale(n) / scale(x, y) |
scale(1.5) |
数値・% | 拡大縮小 | 180×120 | ホバーで拡大 |
scaleX(n) / scaleY(n) |
scaleX(-1) |
数値・% | 1方向だけ拡大縮小。負の値で反転 | 120×80 | 左右反転 |
rotate(角度) |
rotate(45deg) |
deg・turn・rad・grad | 時計回りに回転 | 141.42×141.42 | 矢印・アイコンの回転 |
skew(x, y) |
skew(20deg, 0) |
角度 | 斜めに傾ける | 149.12×80 | 斜めの帯、平行四辺形 |
skewX(角度) / skewY(角度) |
skewY(10deg) |
角度 | 1方向だけ傾ける | 120×101.16 | 斜めの区切り |
matrix(a, b, c, d, tx, ty) |
matrix(1, 0, 0, 1, 50, 20) |
数値 | 2Dの変形を6つの数値で指定 | 120×80 | ツールが出力した値 |
translateZ(z) / translate3d(x, y, z) |
translate3d(10px, 20px, 30px) |
長さ(zは%不可) | 奥行き方向を含む移動 | 120×80 | 3D表現 |
scaleZ(n) / scale3d(x, y, z) |
scale3d(2, 2, 1) |
数値 | 3軸の拡大縮小 | 240×160 | 3D表現 |
rotateX(角度) |
rotateX(60deg) |
角度 | 横軸を中心に回転(上下が縮んで見える) | 120×40 | カードをめくる動き |
rotateY(角度) |
rotateY(60deg) |
角度 | 縦軸を中心に回転(左右が縮んで見える) | 60×80 | カードの裏返し |
rotateZ(角度) |
rotateZ(45deg) |
角度 | rotate() と同じ |
141.42×141.42 | 3D指定と並べるとき |
rotate3d(x, y, z, 角度) |
rotate3d(1, 1, 0, 45deg) |
数値+角度 | 任意の軸で回転 | 114.14×85.86 | 斜め軸の回転 |
perspective(距離) |
perspective(400px) rotateY(60deg) |
長さ | 遠近感を付ける | 61.03×91.94 | 3D回転に奥行きを出す |
matrix3d(16個の数値) |
(省略) | 数値 | 3Dの変形を16個の数値で指定 | — | ツールが出力した値 |
複数の関数は半角スペースで区切って並べます(カンマではありません)。 例:transform: translateX(100px) rotate(45deg) scale(1.2);
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「CSS transformの使い方|translate・rotate早見表」
検証に使った環境
| 項目 | 内容 |
|---|---|
| ブラウザ | Chromium 141.0.7390.37(ヘッドレス) |
| 実行 | Playwright 1.56.0(Python) |
| ビューポート | 1000×800px |
| 共通CSS | * { margin: 0 }、body { font: 16px/1.5 'DejaVu Sans' } |
| 測り方 | getBoundingClientRect()(変形後の見た目の箱)と getComputedStyle().transform(計算済みの行列) |
getBoundingClientRect() は「変形後の図形をすっぽり囲む、傾いていない長方形」を返します。45度回した120×80の箱が141.42×141.42になるのはそのためです。
transformの基本
書き方
.box {
transform: translate(50px, 20px);
}
.box:hover {
transform: translate(50px, 20px) scale(1.1);
transition: transform 0.2s;
}
4つの基本関数を実測する
<!doctype html>
<meta charset="utf-8">
<style>
.wrap { position: absolute; left: 300px; top: 300px; }
.box { width: 120px; height: 80px; background: #ddd; }
</style>
<div class="wrap"><div class="box" style="transform: translate(50px, 20px)"></div></div>
<div class="wrap"><div class="box" style="transform: scale(1.5)"></div></div>
<div class="wrap"><div class="box" style="transform: rotate(45deg)"></div></div>
<div class="wrap"><div class="box" style="transform: skew(20deg, 0)"></div></div>
<script>
document.querySelectorAll('.box').forEach(el => {
const r = el.getBoundingClientRect();
console.log(el.style.transform, '=>', r.x, r.y, r.width, r.height,
getComputedStyle(el).transform);
});
</script>
実行結果(Chromium 141。元の位置は x=300, y=300):
translate(50px, 20px) => x=350 y=320 120×80 matrix(1, 0, 0, 1, 50, 20)
scale(1.5) => x=270 y=280 180×120 matrix(1.5, 0, 0, 1.5, 0, 0)
rotate(45deg) => x=289.29 y=269.29 141.42×141.42 matrix(0.707107, 0.707107, -0.707107, 0.707107, 0, 0)
skew(20deg, 0) => x=285.44 y=300 149.12×80 matrix(1, 0, 0.36397, 1, 0, 0)
scale(1.5) で左上が (270, 280) に動いているのは、変形の基準点が要素の中心だからです。 中心を固定したまま四方に広がるので、左と上にも30px・20pxずつはみ出します。基準点は transform-origin で変えられます(後述)。
計算済みの値は、どの関数で書いても matrix(...) という行列に変換されて表示されます。開発者ツールで matrix が見えていれば、transform が解釈されている合図です。
単位と書き方の決まり
CSS.supports('transform', 値) で、書き方が有効かどうかを確かめました。
| 書き方 | 有効か | 補足 |
|---|---|---|
rotate(45deg) |
有効 | 角度には単位が必要 |
rotate(45) |
無効 | 単位なしは 0 だけ有効(rotate(0) は有効) |
rotate(0.25turn)/rotate(3.14rad)/rotate(100grad) |
有効 | 1turn = 360deg |
translate(50px, 20px) |
有効 | カンマ区切り |
translate(50) |
無効 | 長さにも単位が必要 |
translate(10px 20px) |
無効 | 関数の中はスペース区切りにできない |
translateX(50%) |
有効 | % はその要素自身の大きさが基準 |
scale(1.5)/scale(150%) |
有効 | 単位なしの倍率、または % |
無効な値が1つでもあると、その transform の宣言全体が無視されます。 「回転も移動もしない」ときは、まず単位の付け忘れとカンマの有無を確認すると早く見つかります。
translate の % は親ではなく自分の大きさが基準です。120×80の箱に translate(100%, 50%) を指定すると、右に120px・下に40px動きました。この性質が、あとで出てくる中央寄せの translate(-50%, -50%) に使われています。
rotate(0.25turn) は90度回転で、120×80の箱が80×120の見た目になりました。角度の向きは、正の値が時計回りです。
関数の順番で結果が変わる
rotate→translate と translate→rotate の比較
transform に並べた関数は、書いた順に「座標軸ごと」変形していきます。 先に回転すると、その後の移動は回転した軸に沿って行われます。
100×100pxの箱(元の位置 x=300, y=300)で比べました。
.a { transform: rotate(90deg) translate(100px, 0); }
.b { transform: translate(100px, 0) rotate(90deg); }
実測:
rotate(90deg) translate(100px, 0) => x=300 y=400 matrix(0, 1, -1, 0, 0, 100) ← 下に100px動いた
translate(100px, 0) rotate(90deg) => x=400 y=300 matrix(0, 1, -1, 0, 100, 0) ← 右に100px動いた
.a は「先に90度回したので、X軸が下向きになった。その軸に沿って100px進んだ」結果、下に動いています。.b は右に100px動かしてから、その場で回転しています。
ほかの組み合わせも同じ考え方で説明できます。
| 指定 | 左上の位置 | 説明 |
|---|---|---|
scale(2) translate(50px, 0) |
x=350(見た目で100px移動) | 2倍に拡大した座標で50px進むので、実際は100px |
translate(50px, 0) scale(2) |
x=300(見た目で50px移動) | 50px動かしてから、その場で2倍 |
rotate(45deg) translateX(100px) |
x=350, y=350 | 右下45度の方向へ100px(縦横に約70.71pxずつ) |
translateX(100px) rotate(45deg) |
x=379.29, y=279.29 | 右に100px動かしてから、その場で回転 |
(scale の2行は、拡大で左上が50px左に広がった状態 x=250 を起点に見た移動量です。)
「移動してから、その場で回転・拡大」にしたいときは translate → rotate → scale の順に書くと、直感どおりの結果になります。
個別プロパティなら順番を気にしなくてよい
translate・rotate・scale は、transform の関数としてだけでなく、独立したプロパティとしても書けます。 MDN の translate のページでは、主要ブラウザで2022年8月から使える(Baseline の Widely available)と案内されています。
.box {
translate: 100px 0; /* 値はスペース区切り */
rotate: 90deg;
scale: 2;
}
実測(100×100pxの箱、元の位置 x=300, y=300):
translate:100px 0; rotate:90deg => x=400 y=300
rotate:90deg; translate:100px 0(順を逆に) => x=400 y=300 ← 書く順を変えても同じ
transform: translate(100px,0) rotate(90deg) => x=400 y=300 ← これと同じ結果
translate:100px 0; rotate:90deg; scale:2 => x=350 y=250 200×200
個別プロパティは、書いた順に関係なく「translate → rotate → scale」の順で適用されます。 そのあとに transform プロパティの内容が続きます。併用したときの実測は次のとおりです。
translate:100px 0; transform:rotate(90deg) => x=400 y=300(移動→回転)
rotate:90deg; transform:translate(100px,0) => x=300 y=400(回転→移動。下に動く)
translate:50px 0; transform:translate(50px,0) => x=400 y=300(合計100px。両方が足される)
個別プロパティを使ったとき、計算済みの transform は none のままで、translate・rotate・scale それぞれの計算値に値が入ります。
書式の違いにも気を付けます。
| 書き方 | 有効か |
|---|---|
translate: 10px 20px |
有効(スペース区切り) |
translate: 10px, 20px |
無効(カンマは使わない) |
translate: 50% 0 10px |
有効(3つめはZ) |
rotate: 45deg/rotate: x 45deg/rotate: 1 1 0 45deg |
有効(軸も指定できる) |
scale: 1.5/scale: 2 0.5/scale: 150% |
有効 |
skew には個別プロパティがないので、傾けたいときは transform: skew(...) を使います。
中央寄せ:translate(-50%, -50%) と flex・grid の比較
仕組み
.parent { position: relative; }
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
top: 50%; left: 50% は子の左上の角を親の中央に置きます。そこから translate(-50%, -50%) で自分の幅と高さの半分だけ戻すと、子の中心が親の中央に重なります。
親600×300px、子200×100pxで測りました(親の左上からの位置)。
| 方法 | 子の位置 (x, y) | 中央か |
|---|---|---|
top:50%; left:50% だけ |
(300, 150) | 左上の角が中央に来ている |
+ transform: translate(-50%, -50%) |
(200, 100) | 中央 |
親 display:flex; justify-content:center; align-items:center |
(200, 100) | 中央 |
親 display:grid; place-items:center |
(200, 100) | 中央 |
子 position:absolute; inset:0; margin:auto(幅・高さ指定あり) |
(200, 100) | 中央 |
どれも同じ位置になりました。子の大きさを奇数(201×101px)にした場合も、translate と grid の両方で (199.5, 99.5) と同じ値でした。
違いが出るのは「幅を決めていない長い文」
子に幅を指定せず、長めの文章を入れると差が出ます。
position:absolute; top:50%; left:50%; translate(-50%,-50%) => 幅 300px・4行(高さ96px)
親 display:grid; place-items:center => 幅 600px・2行(高さ48px)
left: 50% を指定した時点で、子が使える幅は「親の右半分(300px)」になります。 その幅で折り返してから左へ戻すので、中央には来るものの、幅が親の半分までに限られます。grid や flex の中央寄せでは親の幅いっぱいまで使えます。
| 状況 | 向いている方法 |
|---|---|
| 通常のレイアウトで中身を中央に置きたい | 親に display: grid; place-items: center(または flex) |
| ほかの要素の上に重ねたい(バッジ、読み込み中の表示、モーダル) | position: absolute + translate(-50%, -50%) |
| 重ねたうえで幅を広く使いたい | inset: 0; margin: auto に width: fit-content などを組み合わせる |
| 横だけ中央に重ねたい | left: 50%; translate: -50% 0 |
個別プロパティで書くと translate: -50% -50%; となり、ホバーで scale を足しても中央寄せが消えません(次の節の話につながります)。
hoverで回転が消える:上書き問題と解決
再現
.card { transform: rotate(10deg); }
.card:hover { transform: scale(1.2); } /* 拡大だけ足したつもり */
実測(ホバー前 → ホバー後の計算済み transform):
ホバー前: matrix(0.984808, 0.173648, -0.173648, 0.984808, 0, 0) ← 10度回転
ホバー後: matrix(1.2, 0, 0, 1.2, 0, 0) ← 拡大だけ。回転がなくなった
transform は1つのプロパティなので、後から書いた宣言が丸ごと置き換えます。 「足す」のではなく「入れ替える」動きです。中央寄せの translate(-50%, -50%) を付けた要素に :hover { transform: scale(1.1) } を書くと、ホバーした瞬間に位置が右下へ飛ぶのも同じ理由です。
解決1:すべての関数を書き直す
.card { transform: rotate(10deg); }
.card:hover { transform: rotate(10deg) scale(1.2); }
ホバー後: matrix(1.18177, 0.208378, -0.208378, 1.18177, 0, 0) ← 回転+拡大
解決2:個別プロパティに分ける(おすすめ)
.card { rotate: 10deg; }
.card:hover { scale: 1.2; }
ホバー前: transform none / rotate 10deg / scale none
ホバー後: transform none / rotate 10deg / scale 1.2 ← rotate は残ったまま
rotate と scale は別のプロパティなので、片方を書いてももう片方は消えません。 アニメーションでも「回転は無限ループ、拡大はホバー時だけ」のように別々に動かせます。
解決3:CSS変数で値だけ差し替える
.card {
--r: 10deg;
--s: 1;
transform: rotate(var(--r)) scale(var(--s));
}
.card:hover { --s: 1.2; }
ホバー後: matrix(1.18177, 0.208378, -0.208378, 1.18177, 0, 0) ← 解決1と同じ結果
| 方法 | 向いている場面 |
|---|---|
| すべて書き直す | 指定が1〜2か所だけのとき |
| 個別プロパティ | 移動・回転・拡大を別々に切り替えたいとき(順番は translate→rotate→scale で固定) |
| CSS変数 | skew を含む、または関数の順番を自分で決めたいとき |
inline要素に効かない
span や a など display: inline の要素は、transform の対象になりません。
再現コード:
<span style="transform: rotate(90deg)">inline text</span>
修正コード:
<span style="display: inline-block; transform: rotate(90deg)">inline text</span>
実測:
span(inline)+ rotate(90deg) => 80.13×19 ← 向きが変わらない
span(inline-block)+ rotate(90deg) => 24×80.13 ← 90度回って縦長に
a(inline)+ scale(2) => 28.3×19 ← 拡大されない
a(inline-block)+ scale(2) => 56.59×48
flexの子の span + rotate(90deg) => 24×113.88 ← 回る(flexの子はブロック扱いになる)
img(inline)+ rotate(90deg) => 40×20 が 20×40 に ← 回る(画像は対象)
inline の span でも、計算済みの transform には matrix(0, 1, -1, 0, 0, 0) と表示されました。 開発者ツールで値が入っていても、見た目が変わっていなければ display を確認します。inline-block・block・inline-flex のどれかに変えると効きます。画像(img)や、flex・grid の子になっている要素は、そのままで変形できます。
transform-originで基準点を変える
transform-origin は、回転や拡大の「軸になる点」です。初期値は 50% 50%(要素の中心)。
100×100pxの箱(元の位置 x=300, y=300)を rotate(90deg) で回し、基準点だけを変えて測りました。
| transform-origin | 計算値 | 回転後の左上 (x, y) | 動き |
|---|---|---|---|
center(初期値) |
50px 50px | (300, 300) | その場で回る |
top left/0 0 |
0px 0px | (200, 300) | 左上の角を軸に、左側へ倒れる |
bottom right/100% 100% |
100px 100px | (400, 300) | 右下の角を軸に、右側へ倒れる |
50% 100% |
50px 100px | (350, 350) | 下辺の中央を軸に回る |
left |
0px 50px | (250, 350) | 左辺の中央を軸に回る |
20px 80px |
20px 80px | (300, 360) | 任意の点を軸に回る |
拡大でも基準点が効きます。
scale(2) => x=250 y=250 200×200(中心から四方に広がる)
scale(2); transform-origin: top left => x=300 y=300 200×200(左上を固定して右下へ広がる)
| やりたいこと | 指定 |
|---|---|
| 下線を左から右へ伸ばす | transform-origin: left; + scaleX(0) → scaleX(1) |
| メニューを上から開く | transform-origin: top; + scaleY(0) → scaleY(1) |
| 時計の針のように根元で回す | transform-origin: bottom center; + rotate() |
| 吹き出しを左上から広げる | transform-origin: top left; + scale() |
SVGの中では基準点の初期値が違う
SVG の図形(rect など)に CSS の transform を書くと、Chromium 141 では transform-origin の計算値が 0px 0px、transform-box が view-box でした。基準点が SVG 全体の左上になるため、rotate すると図形が大きく振り回されます(実測で、50×50pxの rect が SVG の左外 x=-50 へ移動)。その場で回したいときは次の2行を足します。
rect {
transform-box: fill-box; /* 図形自身の箱を基準にする */
transform-origin: center;
}
これで x=0 の位置のまま回転しました。
transformはレイアウトに影響しない
transform は「描く位置と形」だけを変え、まわりの要素の配置は元の箱のまま計算されます。
100×100pxの箱に scale(2); transform-origin: top left を付け、すぐ下に次の要素を置いて測りました。
見た目の箱(getBoundingClientRect) => 200×200
offsetWidth / offsetHeight => 100 / 100 ← レイアウト上の大きさは元のまま
次の要素の位置 => y=100 ← 拡大した箱の下に潜り込んで重なる
overflow:auto の親の scrollHeight => 200(clientHeight は 124)← スクロール範囲は広がる
translateY(50px) でも同じで、動かした箱の次の要素は元の位置(y=224)のままでした。
| 性質 | 影響 |
|---|---|
| まわりの要素は動かない | ホバーで拡大しても隣がずれない。その代わり重なる |
offsetWidth は変形前の値 |
変形後の大きさを知りたいときは getBoundingClientRect() |
| スクロール範囲は広がる | 画面外へ translateX(100%) で逃がした要素が横スクロールを生むことがある。親に overflow: hidden(または clip)を付ける |
位置を変えるときに margin や top・left ではなく transform を使うと、まわりの配置を計算し直さずに済むため、アニメーションがなめらかになりやすいという利点があります。
子孫のposition: fixedが画面に固定されなくなる
transform を付けた要素は、子孫の position: fixed にとっての基準(包含ブロック)になります。 本来は画面(ビューポート)基準で固定される要素が、transform を付けた先祖を基準にするようになり、スクロールすると一緒に流れます。
再現コード:
<style> body { height: 3000px; margin: 0; } </style>
<div style="height: 200px"></div>
<div class="ancestor" style="margin-left: 100px; width: 400px; height: 300px; transform: translateX(0)">
<div class="header" style="position: fixed; top: 0; left: 0; width: 100%; height: 40px">header</div>
</div>
実測(fixed の要素の位置と幅。スクロール前 → 150pxスクロール後):
先祖に transform なし : (0, 0) 幅1000 → (0, 0) 幅1000 ← 画面の上端に固定
先祖に transform:translateX(0): (100, 200) 幅400 → (100, 50) 幅400 ← 先祖の左上に付き、スクロールで流れる
translateX(0) のように見た目が変わらない値でも起きます。width: 100% も画面幅(1000px)ではなく先祖の幅(400px)になりました。
どの指定が同じ挙動を起こすかを、同じHTMLで先祖のスタイルだけを変えて調べました。
| 先祖に付けた指定 | fixed の基準が先祖に変わるか |
|---|---|
transform: translateX(0) |
変わる |
translate: 0 0/rotate: 0deg/scale: 1(個別プロパティ) |
変わる |
will-change: transform |
変わる |
perspective: 500px |
変わる |
transform-style: preserve-3d |
変わる |
filter: blur(0) |
変わる |
backdrop-filter: blur(2px) |
変わる |
contain: paint/contain: layout |
変わる |
content-visibility: auto |
変わる |
transform: none/filter: none |
変わらない |
opacity: 0.99/will-change: opacity |
変わらない |
isolation: isolate |
変わらない |
container-type: inline-size |
変わらない |
対処
| 対処 | 内容 |
|---|---|
| fixed の要素を外へ出す | モーダルや固定ヘッダーを body の直下など、transform の付いた要素の外に置く |
| 先祖の transform を外す | アニメーション終了後に transform: none へ戻す(translateX(0) のままにしない) |
dialog 要素や Popover を使う |
showModal() で開いた dialog は最前面の専用レイヤーに描かれる(今回は未実行) |
「モーダルが画面ではなく親の中に表示される」「固定ヘッダーがスクロールで流れる」ときは、先祖を順にたどって上の表の指定がないかを確認します。 フェードインのアニメーションで transform を使い、終了後も translateY(0) が残っているケースがよくあります。
z-indexへの影響(スタッキングコンテキスト)
transform を付けた要素は、新しいスタッキングコンテキスト(重なり順をまとめる単位)を作ります。 その中の子は、いくら大きな z-index を持っていても、外にある要素との重なりは「transform を付けた親の順位」で決まります。
再現コード:
<div class="parent" style="transform: translateX(0)">
<div class="modal" style="position: relative; z-index: 9999; width: 200px; height: 100px">modal</div>
</div>
<div class="sibling" style="position: relative; z-index: 1; width: 200px; height: 100px; margin-top: -50px">sibling</div>
重なっている位置で document.elementFromPoint() を実行し、手前にある要素を調べました。
| parent の指定 | 手前に表示された要素 |
|---|---|
| 指定なし | modal(z-index 9999 が勝つ) |
transform: translateX(0) |
sibling(z-index 1 が手前) |
translate: 0 0 |
sibling |
will-change: transform |
sibling |
opacity: 0.99 |
sibling |
filter: blur(0) |
sibling |
isolation: isolate |
sibling |
position: relative; z-index: 2 |
modal(親の順位が sibling より上) |
parent に transform が付くと、modal の 9999 は parent の中だけで有効になり、parent 自体は z-index: auto(順位0相当)なので、z-index: 1 の sibling が手前に来ます。対処は、transform を付けた親に position: relative; z-index: 2 のように順位を与えるか、手前に出したい要素を親の外へ出すことです。
3Dの基本:perspectiveを付ける
rotateX・rotateY は、そのままだと「縦か横に縮んだ」ように見えるだけです。perspective で視点までの距離を与えると、手前が大きく奥が小さい遠近感が付きます。
rotateY(60deg) => 60×80 ← 横幅が半分に縮むだけ
perspective(400px) rotateY(60deg) => 61.03×91.94 ← 手前側の辺が大きくなり、台形に見える
perspective(400px) translateZ(100px) => 160×106.67 ← 手前に近づいて大きく見える
translateZ(50px)(perspective なし) => 120×80 ← 大きさは変わらない
| 書き方 | 効く範囲 |
|---|---|
transform: perspective(400px) rotateY(60deg); |
その要素だけ。perspective() は先頭に書く |
親に perspective: 400px; |
子すべてに同じ視点で遠近感が付く |
カードの裏返しでは、親に perspective、回す要素に transform-style: preserve-3d、表と裏の面に backface-visibility: hidden を組み合わせます(この組み合わせの見た目は今回は未実行です)。値は小さいほど遠近感が強く、400〜1000px あたりから試すと調整しやすくなります。
コピペで使える定番パターン
/* 1. ホバーで少し浮かせて拡大(個別プロパティ) */
.card { transition: translate 0.2s, scale 0.2s; }
.card:hover { translate: 0 -4px; scale: 1.03; }
/* 2. 重ねて中央に置く */
.overlay-center { position: absolute; top: 50%; left: 50%; translate: -50% -50%; }
/* 3. 開閉の矢印を回す */
.arrow { transition: rotate 0.2s; }
.is-open .arrow { rotate: 180deg; }
/* 4. 下線を左から伸ばす */
.link { position: relative; }
.link::after {
content: ""; position: absolute; left: 0; bottom: 0; width: 100%; height: 2px;
background: currentColor; scale: 0 1; transform-origin: left; transition: scale 0.2s;
}
.link:hover::after { scale: 1 1; }
/* 5. 画像を左右反転 */
.flip { scale: -1 1; }
/* 6. 斜めの帯(中の文字は逆向きに傾けて戻す) */
.band { transform: skewX(-12deg); }
.band > span { display: inline-block; transform: skewX(12deg); }
/* 7. くるくる回る読み込み表示 */
.spinner { animation: spin 1s linear infinite; }
@keyframes spin { to { rotate: 1turn; } }
/* 8. 動きを減らす設定の利用者には変形を控える */
@media (prefers-reduced-motion: reduce) {
.card, .arrow, .link::after { transition: none; }
.spinner { animation: none; }
}
効かないときの確認チェックリスト
| 順番 | 確認すること | 対処 |
|---|---|---|
| 1 | 単位は付いているか(rotate(45)、translate(50) は無効) |
deg・px などを付ける |
| 2 | 関数どうしをカンマで区切っていないか | 半角スペースで区切る |
| 3 | 要素は display: inline ではないか |
inline-block にする |
| 4 | 別のルールの transform に丸ごと上書きされていないか |
個別プロパティかCSS変数に分ける |
| 5 | 関数の順番は意図どおりか | 移動→回転→拡大の順に書く |
| 6 | 基準点は意図どおりか | transform-origin を指定 |
| 7 | 子孫の fixed やモーダルの位置が変わっていないか | fixed の要素を外へ出す |
| 8 | 重なり順が変わっていないか | transform を付けた親に z-index を与える |
よくある質問
Q. transform の translate と、position の top・left はどう違いますか?
A. どちらも位置を動かせますが、translate の % は自分の大きさが基準、top・left の % は親の大きさが基準です。実測で、120×80の箱に translate(100%, 50%) を指定すると 120px・40px 動きました。また transform はまわりの配置を計算し直さないので、動きを付ける用途に向いています。
Q. transform に複数の関数を書くと、どの順で効きますか?
A. 書いた順に、座標軸ごと変形していきます。rotate(90deg) translate(100px, 0) は回した後の軸に沿って進むため下に100px、translate(100px, 0) rotate(90deg) は右に100px動きました。順番で迷うときは、個別プロパティ(translate・rotate・scale)を使うと「移動→回転→拡大」に固定されます。
Q. 個別プロパティと transform は一緒に使えますか?
A. 使えます。個別プロパティ(translate→rotate→scale)が先に適用され、そのあとに transform の内容が続きます。translate: 50px 0 と transform: translate(50px, 0) を両方書くと、合計で100px動きました。
Q. 拡大したら文字や画像がぼやけて見えます。
A. scale は描いたものを引き伸ばすので、大きく拡大すると粗く見えることがあります。文字は font-size、画像は表示サイズより大きい元画像を用意すると、くっきりしやすくなります(見え方の比較は今回は未実行です)。
まとめ
- transform は移動・回転・拡大縮小・傾斜を行い、まわりの配置は変えない。
offsetWidthは元のまま、getBoundingClientRect()が変形後の大きさを返す。 - 角度と長さには単位が必要。
rotate(45)やtranslate(50)は無効で、宣言全体が無視される。 - 関数は書いた順に座標軸ごと変形する。 rotate→translate は下へ、translate→rotate は右へ100px動いた。
- 個別プロパティ
translate・rotate・scaleは順番が固定で、互いに上書きしない。 hover で回転が消える問題はこれで解決できる。 translate(-50%, -50%)の中央寄せは重ねたいとき向け。 幅を決めない長文は親の半分の幅で折り返すので、通常の配置は grid・flex が扱いやすい。- inline 要素には効かない。 計算値に matrix が出ていても、
inline-blockにするまで見た目は変わらない。 - transform を付けた要素は、子孫の
position: fixedの基準になり、z-index の範囲も区切る。 見た目が変わらないtranslateX(0)でも起きる。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「CSS transformの使い方|translate・rotate早見表」
株式会社RJCでは、CSS transformの使い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。