Skip to content

How to combine translate, rotate, skew and scale

Nine controls drive one two-dimensional transform: translateX and translateY in px (-240 to 240), scaleX and scaleY (-3 to 3), rotate (-360 to 360 degrees), skewX and skewY (-89 to 89 degrees) and the transform-origin X and Y percentages (0 to 100). Every keystroke redraws the card on the stage, and the code panel holds the exact string that Copy CSS puts on the clipboard.

The export composes the functions in a fixed order - translate, then rotate, then skew, then scale - and drops every function that is still at its identity value, so an untouched page exports transform: none. A browser probe confirmed that the reduced string renders the same matrix as the full one: translate(77px, 0px) alone and translate(77px, 0px) rotate(0deg) skew(0deg, 0deg) scale(1, 1) both compute to matrix(1, 0, 0, 1, 77, 0).

  1. Pick a preset or stay on Custom. Tilt is translate(18px, -12px) rotate(-12deg) skew(-8deg, 2deg) scale(1.04, 1.04); Zoom is scale(1.55, 1.55) with rotate(4deg); Flip mirrors the X axis with scale(-1, 1). Changing any field switches the select back to Custom.
  2. Type a value into any of the nine fields. The stage and the code panel update on every keystroke and the preview already uses the clamped value, so 999 in translateX moves the card to 240px.
  3. Click away to commit. When the field loses focus the stored number is written back into it, so field, stage and code panel always agree; clearing a field restores the number that was stored before instead of switching it to zero.
  4. Place the origin with the two percentage fields. The 12px marker on the card shows the origin point (50% 50% by default), and the export adds a transform-origin line only when the origin is not 50% 50% and at least one transform function is active.
  5. Press Copy CSS for the .transformed-element declaration, or Reset to return all nine fields and the preset to their defaults.

Defaults, the fixed function order and the values written back on commit

One transform, four functions in a fixed order

The workbench writes translate(...) first, then rotate(...), then skew(...), then scale(...). Functions still at their identity are left out: a translate-only edit exports transform: translate(77px, 0px); and when nothing is set the block is transform: none; with no transform-origin line.

The order is not cosmetic. In the browser probe translate(100px, 0px) rotate(45deg) computed matrix(0.707107, 0.707107, -0.707107, 0.707107, 100, 0), while rotate(45deg) translate(100px, 0px) computed matrix(0.707107, 0.707107, -0.707107, 0.707107, 70.7107, 70.7107): the translation is carried around by the rotation when it comes second.

Ranges and the value written back on commit

Offset -240 to 240 px, scale -3 to 3, rotate -360 to 360 degrees, skew -89 to 89 degrees and origin 0 to 100 %. While you type, the preview stays inside those bounds; on blur the clamped number replaces the field text, so 999 in origin Y stores 100 % and -300 in translateX stores -240 px.

The nine inputs accept any step, so the 1.04 scale written by the Tilt preset is not flagged as invalid. Skew stops at 89 degrees because tan(90 degrees) is unbounded, and the origin marker cannot leave the card because the origin never leaves 0-100 %.

What this page does not do

The page is two-dimensional: no translateZ, rotateX/rotateY, perspective or matrix() input, and the stage carries a single element. The selector is fixed (.transformed-element) and the panel exports one transform declaration plus an optional transform-origin; there is no media query or CSS variable output.

Everything runs in the page. Driving all nine fields, the three non-default presets, Copy CSS and Reset produced zero network requests in the probe; only the shared analytics tag loads with the page. On a 390px viewport the card is 118x118 (150x150 on desktop) with the same 12px origin marker and no horizontal overflow.

Recent tools: