๐ค CSS transform
CSS transform lets you visually manipulate elements โ move, rotate, resize, or skew them โ without affecting the document flow.
โ General Syntax
selector {
transform: function(value);
}Multiple transforms can be combined:
transform: scale(1.2) rotate(15deg) translateX(20px);๐น Scale
Resizes the element โ larger or smaller, horizontally and/or vertically.
๐ Syntax
transform: scale(x); /* uniform scale */
transform: scale(x, y); /* scaleX and scaleY separately */๐ Examples:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>CSS Transform: Scale</title>
<style>
.infographic-scale {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-scale[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.sc-inner { padding: 0.5rem; }
.sc-layout { display: flex; flex-direction: column; gap: 1.2rem; }
.sc-toggle-row { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; }
.sc-toggle {
display: flex;
border: 1px solid var(--line);
border-radius: 999px;
overflow: hidden;
}
.sc-toggle-btn {
border: none;
background: var(--paper);
color: var(--ink-soft);
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
font-weight: 700;
padding: 0.45rem 1.1rem;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
}
.sc-toggle-btn + .sc-toggle-btn { border-left: 1px solid var(--line); }
.sc-toggle-btn.sc-toggle-on { background: var(--accent); color: #ffffff; }
.sc-toggle-hint { font-size: 0.75rem; color: var(--ink-soft); }
.sc-code-wrap {
background: #0b0b0b;
border: 1px solid #2a2a2a;
border-radius: 10px;
overflow: hidden;
}
.sc-filetabs { display: flex; background: #161616; border-bottom: 1px solid #2a2a2a; }
.sc-filetab {
border: none;
background: transparent;
color: #8a8a8a;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
font-weight: 700;
padding: 0.6rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.sc-filetab:hover { color: #e5e5e5; }
.sc-filetab.sc-tab-on { color: #ffffff; border-bottom-color: #5b8def; background: #0b0b0b; }
.sc-code {
padding: 1rem 1.8rem 1rem 0.9rem;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
line-height: 1.85;
overflow-x: auto;
}
.sc-codepane { display: none; }
.sc-codepane.sc-pane-on { display: block; }
.sc-line { display: block; white-space: pre; color: #e5e5e5; padding: 0.05rem 0.4rem; border-radius: 5px; }
.sc-line.sc-hl { background: #1c2333; box-shadow: inset 3px 0 0 #5b8def; color: #ffffff; }
.sc-tag { color: #60a5fa; font-weight: 700; }
.sc-attr { color: #f2b96b; }
.sc-str { color: #9ece6a; }
.sc-sel { color: #f472b6; font-weight: 700; }
.sc-prop { color: #c586c0; }
.sc-val { color: #ce9178; }
.sc-comment { color: #6a737d; font-style: italic; }
.sc-browser {
border: 1px solid var(--line);
border-radius: 10px;
overflow: hidden;
background: var(--paper);
}
.sc-browser-bar {
background: var(--paper-alt);
padding: 0.5rem 0.7rem;
display: flex;
align-items: center;
gap: 0.5rem;
border-bottom: 1px solid var(--line);
}
.sc-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--line); }
.sc-tabchip {
background: var(--paper);
border: 1px solid var(--line);
border-bottom: none;
border-radius: 6px 6px 0 0;
font-size: 0.72rem;
color: var(--ink-soft);
padding: 0.25rem 0.6rem;
}
.sc-screen {
background: #fafafa;
padding: 2.2rem 1.4rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 2.4rem;
}
.sc-row {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.sc-stage {
position: relative;
width: 230px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
.sc-ghost {
position: absolute;
width: 150px;
height: 100px;
border: 1.5px dashed #b8b8b8;
border-radius: 8px;
box-sizing: border-box;
}
.sc-box {
position: relative;
width: 150px;
height: 100px;
background: var(--accent);
color: white;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-family: sans-serif;
font-size: 0.72rem;
padding: 0.4rem;
box-sizing: border-box;
transform: scale(1);
transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}
@media (prefers-reduced-motion: reduce) {
.sc-box { transition: none; }
}
.sc-box.sc-scaled-uniform { transform: scale(1.5); }
.sc-box.sc-scaled-wide { transform: scale(1.5, 1); }
.sc-row-label {
font-size: 0.72rem;
color: var(--ink-soft);
font-family: "SFMono-Regular", Consolas, monospace;
}
.sc-note {
font-size: 0.82rem;
color: var(--ink-soft);
padding: 0 0.2rem;
}
.sc-note strong { color: var(--ink); }
.sc-note code {
font-family: "SFMono-Regular", Consolas, monospace;
background: var(--paper-alt);
padding: 0.05rem 0.3rem;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="infographic-scale" id="sc-root">
<div class="sc-inner">
<div class="sc-layout">
<div class="sc-toggle-row">
<div class="sc-toggle" id="sc-toggle">
<button class="sc-toggle-btn sc-toggle-on" data-state="before">Before</button>
<button class="sc-toggle-btn" data-state="after">After</button>
</div>
<span class="sc-toggle-hint">applies <code>transform: scale()</code> to both boxes</span>
</div>
<div class="sc-code-wrap">
<div class="sc-filetabs">
<button class="sc-filetab sc-tab-on" id="sc-tab-css">styles.css</button>
<button class="sc-filetab" id="sc-tab-html">index.html</button>
</div>
<div class="sc-code">
<div class="sc-codepane sc-pane-on" id="sc-pane-css">
<span class="sc-line"><span class="sc-sel">.box</span>,</span>
<span class="sc-line"><span class="sc-sel">.box-wide</span> {</span>
<span class="sc-line"> <span class="sc-prop">width</span>: <span class="sc-val">150px</span>;</span>
<span class="sc-line"> <span class="sc-prop">height</span>: <span class="sc-val">100px</span>;</span>
<span class="sc-line"> <span class="sc-prop">background-color</span>: <span class="sc-val">#3498db</span>;</span>
<span class="sc-line"> <span class="sc-prop">border-radius</span>: <span class="sc-val">8px</span>;</span>
<span class="sc-line">}</span>
<span class="sc-line"></span>
<span class="sc-line"><span class="sc-comment">/* Uniform scale: 1.5x in both directions */</span></span>
<span class="sc-line"><span class="sc-sel">.box</span> {</span>
<span class="sc-line" id="sc-line-uniform"> <span class="sc-prop">transform</span>: <span class="sc-val" id="sc-val-uniform">none</span>;</span>
<span class="sc-line">}</span>
<span class="sc-line"></span>
<span class="sc-line"><span class="sc-comment">/* Non-uniform scale: wider only */</span></span>
<span class="sc-line"><span class="sc-sel">.box-wide</span> {</span>
<span class="sc-line" id="sc-line-wide"> <span class="sc-prop">transform</span>: <span class="sc-val" id="sc-val-wide">none</span>;</span>
<span class="sc-line">}</span>
</div>
<div class="sc-codepane" id="sc-pane-html">
<span class="sc-line"><<span class="sc-tag">div</span> <span class="sc-attr">class</span>=<span class="sc-str">"container"</span>></span>
<span class="sc-line"> <<span class="sc-tag">div</span> <span class="sc-attr">class</span>=<span class="sc-str">"box"</span>>.box</<span class="sc-tag">div</span>></span>
<span class="sc-line"> <<span class="sc-tag">div</span> <span class="sc-attr">class</span>=<span class="sc-str">"box-wide"</span>>.box-wide</<span class="sc-tag">div</span>></span>
<span class="sc-line"></<span class="sc-tag">div</span>></span>
</div>
</div>
</div>
<div class="sc-browser">
<div class="sc-browser-bar">
<span class="sc-dot"></span><span class="sc-dot"></span><span class="sc-dot"></span>
<div class="sc-tabchip">๐ Transform Scale</div>
</div>
<div class="sc-screen">
<div class="sc-row">
<div class="sc-stage">
<div class="sc-ghost"></div>
<div class="sc-box" id="sc-box-uniform">.box<br>scale(1.5)</div>
</div>
<span class="sc-row-label">.box โ uniform scale</span>
</div>
<div class="sc-row">
<div class="sc-stage">
<div class="sc-ghost"></div>
<div class="sc-box" id="sc-box-wide">.box-wide<br>scale(1.5, 1)</div>
</div>
<span class="sc-row-label">.box-wide โ non-uniform scale</span>
</div>
</div>
</div>
<div class="sc-note" id="sc-note">
<strong>Before:</strong> both boxes sit exactly inside the dashed outline โ their real layout size. Toggle to <strong>After</strong> to see <code>transform: scale()</code> grow them past that outline without changing anything else on the page.
</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-scale')
|| document.querySelector('.infographic-scale');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const tabCss = document.getElementById('sc-tab-css');
const tabHtml = document.getElementById('sc-tab-html');
const paneCss = document.getElementById('sc-pane-css');
const paneHtml = document.getElementById('sc-pane-html');
tabCss.addEventListener('click', () => {
tabCss.classList.add('sc-tab-on');
tabHtml.classList.remove('sc-tab-on');
paneCss.classList.add('sc-pane-on');
paneHtml.classList.remove('sc-pane-on');
});
tabHtml.addEventListener('click', () => {
tabHtml.classList.add('sc-tab-on');
tabCss.classList.remove('sc-tab-on');
paneHtml.classList.add('sc-pane-on');
paneCss.classList.remove('sc-pane-on');
});
const toggleBtns = document.querySelectorAll('.sc-toggle-btn');
const boxUniform = document.getElementById('sc-box-uniform');
const boxWide = document.getElementById('sc-box-wide');
const lineUniform = document.getElementById('sc-line-uniform');
const lineWide = document.getElementById('sc-line-wide');
const valUniform = document.getElementById('sc-val-uniform');
const valWide = document.getElementById('sc-val-wide');
const note = document.getElementById('sc-note');
const notes = {
before: '<strong>Before:</strong> both boxes sit exactly inside the dashed outline โ their real layout size. Toggle to <strong>After</strong> to see <code>transform: scale()</code> grow them past that outline without changing anything else on the page.',
after: '<strong>After:</strong> <code>scale(1.5)</code> grows both width and height by 1.5x from the center, while <code>scale(1.5, 1)</code> only stretches the x-axis โ the dashed outline is still the original layout box, since <code>transform</code> never affects surrounding elements or reflows the page.'
};
function setState(state) {
toggleBtns.forEach(b => b.classList.toggle('sc-toggle-on', b.getAttribute('data-state') === state));
const on = state === 'after';
boxUniform.classList.toggle('sc-scaled-uniform', on);
boxWide.classList.toggle('sc-scaled-wide', on);
lineUniform.classList.toggle('sc-hl', on);
lineWide.classList.toggle('sc-hl', on);
valUniform.textContent = on ? 'scale(1.5)' : 'none';
valWide.textContent = on ? 'scale(1.5, 1)' : 'none';
note.innerHTML = notes[state];
tabCss.click();
}
toggleBtns.forEach(btn => {
btn.addEventListener('click', () => setState(btn.getAttribute('data-state')));
});
setState('before');
})();
</script>
</body>
</html>๐น Rotate
Rotates the element around its center (by default).
๐ Syntax:
transform: rotate(angle);Angles in degrees (
deg), radians (rad), turns (turn)
๐ Examples
<div class="box">Rotated</div><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>CSS Transform: Rotate</title>
<style>
.infographic-rotate {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-rotate[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.rt-inner { padding: 0.5rem; }
.rt-layout { display: flex; flex-direction: column; gap: 1.2rem; }
.rt-code-wrap {
background: #0b0b0b;
border: 1px solid #2a2a2a;
border-radius: 10px;
overflow: hidden;
}
.rt-code {
padding: 1rem 1.8rem 1rem 0.9rem;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.8rem;
line-height: 1.85;
overflow-x: auto;
}
.rt-line { display: block; white-space: pre; color: #e5e5e5; padding: 0.05rem 0.4rem; border-radius: 5px; }
.rt-line.rt-hl { background: #1c2333; box-shadow: inset 3px 0 0 #5b8def; color: #ffffff; }
.rt-sel { color: #f472b6; font-weight: 700; }
.rt-prop { color: #c586c0; }
.rt-val { color: #ce9178; }
.rt-cmt { color: #6a9955; font-style: italic; }
.rt-browser {
border: 1px solid var(--line);
border-radius: 10px;
overflow: hidden;
background: var(--paper);
}
.rt-browser-bar {
background: var(--paper-alt);
padding: 0.5rem 0.7rem;
display: flex;
align-items: center;
gap: 0.5rem;
border-bottom: 1px solid var(--line);
}
.rt-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--line); }
.rt-tab {
background: var(--paper);
border: 1px solid var(--line);
border-bottom: none;
border-radius: 6px 6px 0 0;
font-size: 0.72rem;
color: var(--ink-soft);
padding: 0.25rem 0.6rem;
}
.rt-screen {
background: #f5f5f5;
padding: 2.6rem 1.5rem;
display: flex;
justify-content: center;
}
.rt-stage {
position: relative;
width: 150px;
height: 150px;
}
.rt-axis-h, .rt-axis-v {
position: absolute;
background: #cfd6dc;
}
.rt-axis-h { top: 50%; left: -20px; right: -20px; height: 1px; }
.rt-axis-v { left: 50%; top: -20px; bottom: -20px; width: 1px; }
.rt-box {
width: 150px;
height: 150px;
background-color: #e74c3c;
border-radius: 8px;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
font-size: 0.85rem;
box-sizing: border-box;
transform: rotate(45deg);
transition: transform 0.12s linear;
}
.rt-controls {
background: var(--paper-alt);
padding: 0.8rem 0.9rem 0.9rem;
border-top: 1px solid var(--line);
display: flex;
flex-direction: column;
gap: 0.7rem;
}
.rt-control-row {
display: flex;
align-items: center;
gap: 0.6rem;
flex-wrap: wrap;
}
.rt-control-label {
font-size: 0.72rem;
color: var(--ink-soft);
font-family: "SFMono-Regular", Consolas, monospace;
min-width: 60px;
}
.rt-range {
flex: 1;
min-width: 140px;
accent-color: var(--accent);
cursor: pointer;
}
.rt-readout {
font-size: 0.78rem;
color: var(--ink);
font-weight: 700;
font-family: "SFMono-Regular", Consolas, monospace;
min-width: 56px;
text-align: right;
}
.rt-presets {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.rt-preset-btn {
border: 1px solid var(--line);
background: var(--paper);
color: var(--ink-soft);
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.68rem;
font-weight: 700;
padding: 0.28rem 0.55rem;
border-radius: 999px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
}
.rt-preset-btn.rt-preset-on { background: var(--accent); color: #ffffff; border-color: var(--accent); }
.rt-note {
font-size: 0.82rem;
color: var(--ink-soft);
padding: 0 0.2rem;
}
.rt-note strong { color: var(--ink); }
.rt-note code {
font-family: "SFMono-Regular", Consolas, monospace;
background: var(--paper-alt);
padding: 0.05rem 0.3rem;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="infographic-rotate" id="rt-root">
<div class="rt-inner">
<div class="rt-layout">
<div class="rt-code-wrap">
<div class="rt-code">
<span class="rt-line"><span class="rt-sel">.box</span> {</span>
<span class="rt-line"> <span class="rt-prop">width</span>: <span class="rt-val">150px</span>;</span>
<span class="rt-line"> <span class="rt-prop">height</span>: <span class="rt-val">150px</span>;</span>
<span class="rt-line"> <span class="rt-prop">background-color</span>: <span class="rt-val">#e74c3c</span>;</span>
<span class="rt-line" id="rt-line-transform"> <span class="rt-prop">transform</span>: <span class="rt-val" id="rt-val-transform">rotate(45deg)</span>; <span class="rt-cmt" id="rt-cmt-dir">/* clockwise */</span></span>
<span class="rt-line"> <span class="rt-prop">border-radius</span>: <span class="rt-val">8px</span>;</span>
<span class="rt-line">}</span>
</div>
</div>
<div class="rt-browser">
<div class="rt-browser-bar">
<span class="rt-dot"></span><span class="rt-dot"></span><span class="rt-dot"></span>
<div class="rt-tab">๐ Rotate Box</div>
</div>
<div class="rt-screen">
<div class="rt-stage">
<div class="rt-axis-h"></div>
<div class="rt-axis-v"></div>
<div class="rt-box" id="rt-box">Rotated</div>
</div>
</div>
<div class="rt-controls">
<div class="rt-control-row">
<span class="rt-control-label">angle</span>
<input type="range" class="rt-range" id="rt-angle" min="-180" max="180" step="1" value="45">
<span class="rt-readout" id="rt-readout">45deg</span>
</div>
<div class="rt-control-row">
<div class="rt-presets" id="rt-presets">
<button class="rt-preset-btn" data-angle="-180">-180</button>
<button class="rt-preset-btn" data-angle="-90">-90</button>
<button class="rt-preset-btn" data-angle="-45">-45</button>
<button class="rt-preset-btn" data-angle="0">0</button>
<button class="rt-preset-btn rt-preset-on" data-angle="45">45</button>
<button class="rt-preset-btn" data-angle="90">90</button>
<button class="rt-preset-btn" data-angle="180">180</button>
</div>
</div>
</div>
</div>
<div class="rt-note" id="rt-note">
<strong>Sign sets direction, not just size.</strong> A positive angle like <code>45deg</code> rotates clockwise; the same magnitude negative โ <code>-45deg</code> โ rotates counter-clockwise instead. The thin crosshair marks the rotation's pivot: the box's own center, by default.
</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-rotate')
|| document.querySelector('.infographic-rotate');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const angle = document.getElementById('rt-angle');
const readout = document.getElementById('rt-readout');
const box = document.getElementById('rt-box');
const valTransform = document.getElementById('rt-val-transform');
const cmtDir = document.getElementById('rt-cmt-dir');
const lineTransform = document.getElementById('rt-line-transform');
const presets = document.querySelectorAll('.rt-preset-btn');
const note = document.getElementById('rt-note');
function update(deg, fromPreset) {
box.style.transform = `rotate(${deg}deg)`;
valTransform.textContent = `rotate(${deg}deg)`;
readout.textContent = `${deg}deg`;
angle.value = deg;
cmtDir.textContent = deg > 0 ? '/* clockwise */' : deg < 0 ? '/* counter-clockwise */' : '/* no rotation */';
lineTransform.classList.remove('rt-hl');
void lineTransform.offsetWidth;
lineTransform.classList.add('rt-hl');
presets.forEach(b => b.classList.toggle('rt-preset-on', Number(b.getAttribute('data-angle')) === deg));
note.innerHTML = deg < 0
? '<strong>Negative rotates counter-clockwise.</strong> Same magnitude, opposite sign โ <code>-45deg</code> is a mirror image of <code>45deg</code> around the vertical axis, not a different amount of turn.'
: deg > 0
? '<strong>Positive rotates clockwise</strong> โ the default reading direction in CSS. The thin crosshair marks the pivot: the box\'s own center, by default.'
: '<strong>0deg is the resting state</strong> โ no rotation applied. Drag the slider either direction to see the sign flip which way the box turns.';
}
angle.addEventListener('input', () => update(Number(angle.value)));
presets.forEach(btn => {
btn.addEventListener('click', () => update(Number(btn.getAttribute('data-angle'))));
});
update(45);
})();
</script>
</body>
</html>๐นTranslate
Moves the element from its current position, without affecting layout.
๐ Syntax:
transform: translate(x, y);
transform: translateX(value);
transform: translateY(value);๐ Examples:
<div class="container">
<div class="box">โ โ</div>
<div class="box-left">โ</div>
<div class="box-up">โ</div>
</div><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>CSS Transform: Translate</title>
<style>
.infographic-translate {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-translate[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.tr-inner { padding: 0.5rem; }
.tr-layout { display: flex; flex-direction: column; gap: 1.2rem; }
.tr-typebar { display: flex; border: 1px solid var(--line); border-radius: 999px; overflow: hidden; align-self: flex-start; }
.tr-type-btn { border: none; background: var(--paper); color: var(--ink-soft); font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.75rem; font-weight: 700; padding: 0.4rem 0.9rem; cursor: pointer; }
.tr-type-btn + .tr-type-btn { border-left: 1px solid var(--line); }
.tr-type-btn.tr-type-on { background: var(--accent); color: #fff; }
.tr-code-wrap { background: #0b0b0b; border: 1px solid #2a2a2a; border-radius: 10px; overflow: hidden; }
.tr-filetabs { display: flex; background: #161616; border-bottom: 1px solid #2a2a2a; }
.tr-filetab { border: none; background: transparent; color: #fff; font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.78rem; font-weight: 700; padding: 0.6rem 1rem; border-bottom: 2px solid #5b8def; }
.tr-code { padding: 1rem 1.8rem 1rem 0.9rem; font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.78rem; line-height: 1.85; overflow-x: auto; }
.tr-line { display: block; white-space: pre; color: #e5e5e5; padding: 0.05rem 0.4rem; border-radius: 5px; }
.tr-line.tr-hl { background: #1c2333; box-shadow: inset 3px 0 0 #5b8def; color: #ffffff; }
.tr-line.tr-dim { opacity: 0.4; }
.tr-sel { color: #f472b6; font-weight: 700; }
.tr-prop { color: #c586c0; }
.tr-val { color: #ce9178; }
.tr-cmt { color: #6a737d; }
.tr-browser { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--paper); }
.tr-browser-bar { background: var(--paper-alt); padding: 0.5rem 0.7rem; display: flex; align-items: center; gap: 0.5rem; border-bottom: 1px solid var(--line); }
.tr-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--line); }
.tr-tab { background: var(--paper); border: 1px solid var(--line); border-bottom: none; border-radius: 6px 6px 0 0; font-size: 0.72rem; color: var(--ink-soft); padding: 0.25rem 0.6rem; }
.tr-screen {
padding: 1.6rem;
display: flex;
align-items: center;
justify-content: center;
background:
linear-gradient(var(--line) 1px, transparent 1px),
linear-gradient(90deg, var(--line) 1px, transparent 1px);
background-size: 20px 20px;
}
.tr-stage { position: relative; width: 260px; height: 220px; }
.tr-box-outline {
position: absolute;
width: 120px; height: 80px;
border: 1.5px dashed var(--ink-soft);
border-radius: 8px;
opacity: 0.45;
top: 70px; left: 70px;
}
.tr-box {
position: absolute;
top: 70px; left: 70px;
width: 120px; height: 80px;
background-color: #3498db;
color: white;
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-family: sans-serif; font-size: 0.95rem; font-weight: 600;
transform: translate(50px, 20px);
transition: transform 0.1s linear;
box-sizing: border-box;
}
.tr-ghost {
position: absolute;
top: 70px; left: 70px;
width: 120px; height: 80px;
border-radius: 8px;
background: var(--accent-soft);
color: var(--ink-soft);
display: flex; align-items: center; justify-content: center;
font-family: sans-serif; font-size: 0.7rem;
box-sizing: border-box;
}
.tr-controls { background: var(--paper-alt); padding: 0.8rem 0.9rem 1rem; border-top: 1px solid var(--line); }
.tr-control-group { display: none; flex-direction: column; gap: 0.6rem; }
.tr-control-group.tr-group-on { display: flex; }
.tr-control-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.tr-control-label { font-size: 0.72rem; color: var(--ink-soft); font-family: "SFMono-Regular", Consolas, monospace; min-width: 34px; }
.tr-range { flex: 1; min-width: 100px; accent-color: var(--accent); cursor: pointer; }
.tr-readout { font-size: 0.78rem; color: var(--ink); font-weight: 700; font-family: "SFMono-Regular", Consolas, monospace; min-width: 55px; text-align: right; }
.tr-note { font-size: 0.82rem; color: var(--ink-soft); padding: 0 0.2rem; }
.tr-note strong { color: var(--ink); }
.tr-note code { font-family: "SFMono-Regular", Consolas, monospace; background: var(--paper-alt); padding: 0.05rem 0.3rem; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
.tr-box { transition: none; }
}
</style>
</head>
<body>
<div class="infographic-translate" id="tr-root">
<div class="tr-inner">
<div class="tr-layout">
<div class="tr-typebar" id="tr-typebar">
<button class="tr-type-btn tr-type-on" data-mode="both">translate(x, y)</button>
<button class="tr-type-btn" data-mode="x">translateX()</button>
<button class="tr-type-btn" data-mode="y">translateY()</button>
</div>
<div class="tr-code-wrap">
<div class="tr-filetabs">
<button class="tr-filetab">styles.css</button>
</div>
<div class="tr-code">
<span class="tr-line"><span class="tr-sel">.box</span>,</span>
<span class="tr-line"><span class="tr-sel">.box-left</span>,</span>
<span class="tr-line"><span class="tr-sel">.box-up</span> {</span>
<span class="tr-line"> <span class="tr-prop">width</span>: <span class="tr-val">120px</span>;</span>
<span class="tr-line"> <span class="tr-prop">height</span>: <span class="tr-val">80px</span>;</span>
<span class="tr-line"> <span class="tr-prop">background-color</span>: <span class="tr-val">#3498db</span>;</span>
<span class="tr-line">}</span>
<span class="tr-line"></span>
<span class="tr-line" id="tr-line-both"><span class="tr-sel">.box</span> {</span>
<span class="tr-line" id="tr-code-both"> <span class="tr-prop">transform</span>: <span class="tr-val" id="tr-val-both">translate(50px, 20px)</span>;</span>
<span class="tr-line" id="tr-line-both-close">}</span>
<span class="tr-line" id="tr-line-x"><span class="tr-sel">.box-left</span> {</span>
<span class="tr-line" id="tr-code-x"> <span class="tr-prop">transform</span>: <span class="tr-val" id="tr-val-x">translateX(-50px)</span>;</span>
<span class="tr-line" id="tr-line-x-close">}</span>
<span class="tr-line" id="tr-line-y"><span class="tr-sel">.box-up</span> {</span>
<span class="tr-line" id="tr-code-y"> <span class="tr-prop">transform</span>: <span class="tr-val" id="tr-val-y">translateY(-30px)</span>;</span>
<span class="tr-line" id="tr-line-y-close">}</span>
</div>
</div>
<div class="tr-browser">
<div class="tr-browser-bar">
<span class="tr-dot"></span><span class="tr-dot"></span><span class="tr-dot"></span>
<div class="tr-tab">๐ Translate</div>
</div>
<div class="tr-screen">
<div class="tr-stage">
<div class="tr-box-outline"></div>
<div class="tr-ghost">original spot</div>
<div class="tr-box" id="tr-box">โ โ</div>
</div>
</div>
<div class="tr-controls">
<div class="tr-control-group tr-group-on" id="tr-group-both">
<div class="tr-control-row">
<span class="tr-control-label">x</span>
<input type="range" class="tr-range" id="tr-x" min="-90" max="90" step="5" value="50">
<span class="tr-readout" id="tr-x-readout">50px</span>
</div>
<div class="tr-control-row">
<span class="tr-control-label">y</span>
<input type="range" class="tr-range" id="tr-y" min="-90" max="90" step="5" value="20">
<span class="tr-readout" id="tr-y-readout">20px</span>
</div>
</div>
</div>
</div>
<div class="tr-note" id="tr-note">
<strong>The faint square</strong> marks the box's original position โ <code>translate()</code> slides the painted box away from it without ever moving that reserved layout space, so nothing around it reflows. Positive x moves right, positive y moves down.
</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-translate')
|| document.querySelector('.infographic-translate');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const typeButtons = document.querySelectorAll('.tr-type-btn');
const box = document.getElementById('tr-box');
const xRange = document.getElementById('tr-x');
const yRange = document.getElementById('tr-y');
const xReadout = document.getElementById('tr-x-readout');
const yReadout = document.getElementById('tr-y-readout');
const valBoth = document.getElementById('tr-val-both');
const valX = document.getElementById('tr-val-x');
const valY = document.getElementById('tr-val-y');
const note = document.getElementById('tr-note');
const codeBlocks = {
both: ['tr-line-both', 'tr-code-both', 'tr-line-both-close'],
x: ['tr-line-x', 'tr-code-x', 'tr-line-x-close'],
y: ['tr-line-y', 'tr-code-y', 'tr-line-y-close']
};
let mode = 'both';
let x = 50, y = 20;
const notes = {
both: '<strong>The faint square</strong> marks the box\'s original position โ <code>translate(x, y)</code> moves it away from that spot without ever touching the layout, so nothing around it reflows. Positive x is right, positive y is down.',
x: '<strong>translateX()</strong> is shorthand for <code>translate(x, 0)</code> โ it only ever moves along the horizontal axis, vertical position is untouched.',
y: '<strong>translateY()</strong> is shorthand for <code>translate(0, y)</code> โ it only ever moves along the vertical axis, horizontal position is untouched.'
};
function render() {
if (mode === 'both') {
box.style.transform = `translate(${x}px, ${y}px)`;
box.textContent = 'โ โ';
valBoth.textContent = `translate(${x}px, ${y}px)`;
} else if (mode === 'x') {
box.style.transform = `translateX(${x}px)`;
box.textContent = x < 0 ? 'โ' : 'โ';
valX.textContent = `translateX(${x}px)`;
} else {
box.style.transform = `translateY(${y}px)`;
box.textContent = y < 0 ? 'โ' : 'โ';
valY.textContent = `translateY(${y}px)`;
}
xReadout.textContent = `${x}px`;
yReadout.textContent = `${y}px`;
yRange.parentElement.style.display = mode === 'x' ? 'none' : 'flex';
xRange.parentElement.style.display = mode === 'y' ? 'none' : 'flex';
Object.entries(codeBlocks).forEach(([key, ids]) => {
ids.forEach(id => document.getElementById(id).classList.toggle('tr-hl', key === mode));
ids.forEach(id => document.getElementById(id).classList.toggle('tr-dim', key !== mode));
});
}
typeButtons.forEach(btn => {
btn.addEventListener('click', () => {
mode = btn.getAttribute('data-mode');
typeButtons.forEach(b => b.classList.toggle('tr-type-on', b === btn));
note.innerHTML = notes[mode];
if (mode === 'x' && x === 0) x = -50;
if (mode === 'y' && y === 0) y = -30;
render();
});
});
xRange.addEventListener('input', () => { x = Number(xRange.value); render(); });
yRange.addEventListener('input', () => { y = Number(yRange.value); render(); });
render();
})();
</script>
</body>
</html>๐นCombining Multiple Transforms
You can chain multiple transform functions:
<body>
<div class="box">Transform</div>
</body><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Combining Transforms</title>
<style>
.infographic-combine {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-combine[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.tc-inner { padding: 0.5rem; }
.tc-layout { display: flex; flex-direction: column; gap: 1.2rem; }
.tc-steps { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.tc-step-btn {
border: 1px solid var(--line);
background: var(--paper);
color: var(--ink-soft);
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.7rem;
font-weight: 700;
padding: 0.4rem 0.7rem;
border-radius: 999px;
cursor: pointer;
white-space: nowrap;
}
.tc-step-btn.tc-step-on { background: var(--accent); color: #fff; border-color: var(--accent); }
.tc-step-arrow { color: var(--ink-soft); font-size: 0.75rem; }
.tc-play-btn {
margin-left: auto;
border: 1px solid var(--accent);
background: var(--accent-soft);
color: var(--accent);
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.7rem;
font-weight: 700;
padding: 0.4rem 0.8rem;
border-radius: 999px;
cursor: pointer;
}
.tc-code-wrap { background: #0b0b0b; border: 1px solid #2a2a2a; border-radius: 10px; overflow: hidden; }
.tc-filetabs { display: flex; background: #161616; border-bottom: 1px solid #2a2a2a; }
.tc-filetab { border: none; background: transparent; color: #fff; font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.78rem; font-weight: 700; padding: 0.6rem 1rem; border-bottom: 2px solid #5b8def; }
.tc-code { padding: 1rem 1.8rem 1rem 0.9rem; font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.76rem; line-height: 1.85; overflow-x: auto; }
.tc-line { display: block; white-space: pre; color: #e5e5e5; padding: 0.05rem 0.4rem; border-radius: 5px; }
.tc-sel { color: #f472b6; font-weight: 700; }
.tc-prop { color: #c586c0; }
.tc-val { color: #ce9178; }
.tc-fn { transition: opacity 0.15s ease, text-shadow 0.15s ease; opacity: 0.3; }
.tc-fn.tc-fn-on { opacity: 1; text-shadow: 0 0 0 transparent; background: #1c2333; box-shadow: inset 0 0 0 1px #5b8def; border-radius: 3px; padding: 0 2px; }
.tc-browser { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--paper); }
.tc-browser-bar { background: var(--paper-alt); padding: 0.5rem 0.7rem; display: flex; align-items: center; gap: 0.5rem; border-bottom: 1px solid var(--line); }
.tc-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--line); }
.tc-tab { background: var(--paper); border: 1px solid var(--line); border-bottom: none; border-radius: 6px 6px 0 0; font-size: 0.72rem; color: var(--ink-soft); padding: 0.25rem 0.6rem; }
.tc-screen {
padding: 2.4rem 1rem;
display: flex;
align-items: center;
justify-content: center;
background: #f0f0f0;
}
.infographic-combine[data-theme="dark"] .tc-screen { background: #1c1c1c; }
.tc-stage { position: relative; width: 260px; height: 200px; display: flex; align-items: center; justify-content: center; }
.tc-box-outline {
position: absolute;
width: 150px; height: 100px;
border: 1.5px dashed var(--ink-soft);
border-radius: 10px;
opacity: 0.4;
}
.tc-box {
width: 150px; height: 100px;
background-color: #e67e22;
color: white;
border-radius: 10px;
box-shadow: 0 6px 12px rgba(0,0,0,0.3);
display: flex; align-items: center; justify-content: center;
font-family: sans-serif; font-size: 0.85rem;
transform: scale(1) rotate(0deg) translateX(0px);
transition: transform 0.4s cubic-bezier(.34,1.56,.64,1);
box-sizing: border-box;
}
.tc-controls { background: var(--paper-alt); padding: 0.8rem 0.9rem 1rem; border-top: 1px solid var(--line); display: flex; flex-direction: column; gap: 0.6rem; }
.tc-control-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.tc-control-label { font-size: 0.72rem; color: var(--ink-soft); font-family: "SFMono-Regular", Consolas, monospace; min-width: 92px; }
.tc-range { flex: 1; min-width: 100px; accent-color: var(--accent); cursor: pointer; }
.tc-readout { font-size: 0.78rem; color: var(--ink); font-weight: 700; font-family: "SFMono-Regular", Consolas, monospace; min-width: 55px; text-align: right; }
.tc-note { font-size: 0.82rem; color: var(--ink-soft); padding: 0 0.2rem; }
.tc-note strong { color: var(--ink); }
.tc-note code { font-family: "SFMono-Regular", Consolas, monospace; background: var(--paper-alt); padding: 0.05rem 0.3rem; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
.tc-box { transition: none; }
}
</style>
</head>
<body>
<div class="infographic-combine" id="tc-root">
<div class="tc-inner">
<div class="tc-layout">
<div class="tc-steps" id="tc-steps">
<button class="tc-step-btn tc-step-on" data-step="0">base</button>
<span class="tc-step-arrow">โ</span>
<button class="tc-step-btn" data-step="1">+ scale</button>
<span class="tc-step-arrow">โ</span>
<button class="tc-step-btn" data-step="2">+ rotate</button>
<span class="tc-step-arrow">โ</span>
<button class="tc-step-btn" data-step="3">+ translateX</button>
<button class="tc-play-btn" id="tc-play">โถ Play through</button>
</div>
<div class="tc-code-wrap">
<div class="tc-filetabs">
<button class="tc-filetab">styles.css</button>
</div>
<div class="tc-code">
<span class="tc-line"><span class="tc-sel">.box</span> {</span>
<span class="tc-line"> <span class="tc-prop">width</span>: <span class="tc-val">150px</span>;</span>
<span class="tc-line"> <span class="tc-prop">height</span>: <span class="tc-val">100px</span>;</span>
<span class="tc-line"> <span class="tc-prop">background-color</span>: <span class="tc-val">#e67e22</span>;</span>
<span class="tc-line"> <span class="tc-prop">border-radius</span>: <span class="tc-val">10px</span>;</span>
<span class="tc-line"> <span class="tc-prop">box-shadow</span>: <span class="tc-val">0 6px 12px rgba(0,0,0,0.3)</span>;</span>
<span class="tc-line"> <span class="tc-prop">transform</span>: <span class="tc-fn" id="tc-fn-scale">scale(1.5)</span> <span class="tc-fn" id="tc-fn-rotate">rotate(15deg)</span> <span class="tc-fn" id="tc-fn-translate">translateX(20px)</span>;</span>
<span class="tc-line">}</span>
</div>
</div>
<div class="tc-browser">
<div class="tc-browser-bar">
<span class="tc-dot"></span><span class="tc-dot"></span><span class="tc-dot"></span>
<div class="tc-tab">๐ Transform</div>
</div>
<div class="tc-screen">
<div class="tc-stage">
<div class="tc-box-outline"></div>
<div class="tc-box" id="tc-box">Transform</div>
</div>
</div>
<div class="tc-controls">
<div class="tc-control-row">
<span class="tc-control-label">scale</span>
<input type="range" class="tc-range" id="tc-scale" min="1" max="2" step="0.1" value="1.5">
<span class="tc-readout" id="tc-scale-readout">1.5</span>
</div>
<div class="tc-control-row">
<span class="tc-control-label">rotate</span>
<input type="range" class="tc-range" id="tc-rotate" min="-45" max="45" step="5" value="15">
<span class="tc-readout" id="tc-rotate-readout">15deg</span>
</div>
<div class="tc-control-row">
<span class="tc-control-label">translateX</span>
<input type="range" class="tc-range" id="tc-translate" min="-60" max="60" step="5" value="20">
<span class="tc-readout" id="tc-translate-readout">20px</span>
</div>
</div>
</div>
<div class="tc-note" id="tc-note">
<strong>This is the untransformed box</strong> โ the dashed outline and the solid box are still the same shape. Step through to add each function one at a time and watch how the order changes the result.
</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-combine')
|| document.querySelector('.infographic-combine');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const box = document.getElementById('tc-box');
const stepBtns = document.querySelectorAll('.tc-step-btn');
const playBtn = document.getElementById('tc-play');
const note = document.getElementById('tc-note');
const fnScale = document.getElementById('tc-fn-scale');
const fnRotate = document.getElementById('tc-fn-rotate');
const fnTranslate = document.getElementById('tc-fn-translate');
const scaleRange = document.getElementById('tc-scale');
const rotateRange = document.getElementById('tc-rotate');
const translateRange = document.getElementById('tc-translate');
const scaleReadout = document.getElementById('tc-scale-readout');
const rotateReadout = document.getElementById('tc-rotate-readout');
const translateReadout = document.getElementById('tc-translate-readout');
let step = 0;
let vals = { scale: 1.5, rotate: 15, translate: 20 };
const notes = [
'<strong>This is the untransformed box</strong> โ the dashed outline and the solid box are still the same shape. Step through to add each function one at a time.',
'<strong>scale(1.5)</strong> applied first, growing the box around its own center โ nothing has moved yet, it\'s just bigger.',
'<strong>rotate(15deg)</strong> now spins the already-enlarged box around its center. Because scale ran first, the rotation pivots the bigger shape.',
'<strong>translateX(20px)</strong> runs last, in the box\'s own (now rotated) coordinate system โ so "right" means right along the tilted box\'s own axis, not the page\'s horizontal. <strong>Order changes the outcome</strong>: reordering these three functions would land the box somewhere else entirely.'
];
function currentTransform() {
const parts = [];
if (step >= 1) parts.push(`scale(${vals.scale})`);
if (step >= 2) parts.push(`rotate(${vals.rotate}deg)`);
if (step >= 3) parts.push(`translateX(${vals.translate}px)`);
return parts.length ? parts.join(' ') : 'none';
}
function render() {
box.style.transform = currentTransform();
fnScale.classList.toggle('tc-fn-on', step >= 1);
fnRotate.classList.toggle('tc-fn-on', step >= 2);
fnTranslate.classList.toggle('tc-fn-on', step >= 3);
fnScale.textContent = `scale(${vals.scale})`;
fnRotate.textContent = `rotate(${vals.rotate}deg)`;
fnTranslate.textContent = `translateX(${vals.translate}px)`;
scaleReadout.textContent = vals.scale;
rotateReadout.textContent = `${vals.rotate}deg`;
translateReadout.textContent = `${vals.translate}px`;
stepBtns.forEach(b => b.classList.toggle('tc-step-on', Number(b.getAttribute('data-step')) === step));
note.innerHTML = notes[step];
}
stepBtns.forEach(btn => {
btn.addEventListener('click', () => {
step = Number(btn.getAttribute('data-step'));
render();
});
});
playBtn.addEventListener('click', () => {
step = 0;
render();
let i = 0;
const timer = setInterval(() => {
i++;
if (i > 3) { clearInterval(timer); return; }
step = i;
render();
}, 700);
});
scaleRange.addEventListener('input', () => { vals.scale = Number(scaleRange.value); render(); });
rotateRange.addEventListener('input', () => { vals.rotate = Number(rotateRange.value); render(); });
translateRange.addEventListener('input', () => { vals.translate = Number(translateRange.value); render(); });
render();
})();
</script>
</body>
</html>๐น Transform Origin
Change the pivot point (default is center):
transform-origin: top left;<body>
<div class="box">โป</div>
</body><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Transform Origin</title>
<style>
.infographic-origin {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-origin[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.to-inner { padding: 0.5rem; }
.to-layout { display: flex; flex-direction: column; gap: 1.2rem; }
.to-code-wrap { background: #0b0b0b; border: 1px solid #2a2a2a; border-radius: 10px; overflow: hidden; }
.to-filetabs { display: flex; background: #161616; border-bottom: 1px solid #2a2a2a; }
.to-filetab { border: none; background: transparent; color: #fff; font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.78rem; font-weight: 700; padding: 0.6rem 1rem; border-bottom: 2px solid #5b8def; }
.to-code { padding: 1rem 1.8rem 1rem 0.9rem; font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.78rem; line-height: 1.85; overflow-x: auto; }
.to-line { display: block; white-space: pre; color: #e5e5e5; padding: 0.05rem 0.4rem; border-radius: 5px; }
.to-line.to-hl { background: #1c2333; box-shadow: inset 3px 0 0 #5b8def; color: #ffffff; }
.to-sel { color: #f472b6; font-weight: 700; }
.to-prop { color: #c586c0; }
.to-val { color: #ce9178; }
.to-cmt { color: #6a737d; }
.to-browser { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--paper); }
.to-browser-bar { background: var(--paper-alt); padding: 0.5rem 0.7rem; display: flex; align-items: center; gap: 0.5rem; border-bottom: 1px solid var(--line); }
.to-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--line); }
.to-tab { background: var(--paper); border: 1px solid var(--line); border-bottom: none; border-radius: 6px 6px 0 0; font-size: 0.72rem; color: var(--ink-soft); padding: 0.25rem 0.6rem; }
.to-screen {
padding: 2.2rem 1rem;
display: flex;
align-items: center;
justify-content: center;
background: #ecf0f1;
}
.infographic-origin[data-theme="dark"] .to-screen { background: #1c1c1c; }
.to-stage { position: relative; width: 220px; height: 220px; display: flex; align-items: center; justify-content: center; }
.to-box-outline {
position: absolute;
width: 120px; height: 120px;
border: 1.5px dashed var(--ink-soft);
border-radius: 8px;
opacity: 0.4;
}
.to-box {
position: relative;
width: 120px; height: 120px;
background-color: #9b59b6;
color: white;
border-radius: 8px;
box-shadow: 0 8px 16px rgba(0,0,0,0.25);
display: flex; align-items: center; justify-content: center;
font-family: sans-serif; font-size: 1.4rem;
transform-origin: bottom right;
transform: rotate(45deg);
transition: transform 0.4s cubic-bezier(.34,1.56,.64,1), transform-origin 0.3s ease;
box-sizing: border-box;
}
.to-pivot-dot {
position: absolute;
width: 10px; height: 10px;
border-radius: 50%;
background: #fff;
border: 2px solid var(--accent);
z-index: 3;
transform: translate(-50%, -50%);
transition: left 0.3s ease, top 0.3s ease;
box-shadow: 0 0 0 3px rgba(37,99,235,0.15);
}
.to-controls { background: var(--paper-alt); padding: 0.8rem 0.9rem 1rem; display: flex; flex-direction: column; gap: 0.7rem; border-top: 1px solid var(--line); }
.to-control-row { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; }
.to-control-label { font-size: 0.72rem; color: var(--ink-soft); font-family: "SFMono-Regular", Consolas, monospace; min-width: 60px; }
.to-posgrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 3px;
width: 108px;
border: 1px solid var(--line);
border-radius: 8px;
overflow: hidden;
background: var(--line);
}
.to-pos-btn {
border: none;
background: var(--paper);
height: 32px;
cursor: pointer;
position: relative;
}
.to-pos-btn::after {
content: "";
position: absolute;
top: 50%; left: 50%;
width: 6px; height: 6px;
border-radius: 50%;
background: var(--ink-soft);
transform: translate(-50%, -50%);
opacity: 0.5;
}
.to-pos-btn.to-pos-on { background: var(--accent-soft); }
.to-pos-btn.to-pos-on::after { background: var(--accent); opacity: 1; }
.to-range { flex: 1; min-width: 100px; accent-color: var(--accent); cursor: pointer; }
.to-readout { font-size: 0.78rem; color: var(--ink); font-weight: 700; font-family: "SFMono-Regular", Consolas, monospace; min-width: 55px; text-align: right; }
.to-note { font-size: 0.82rem; color: var(--ink-soft); padding: 0 0.2rem; }
.to-note strong { color: var(--ink); }
.to-note code { font-family: "SFMono-Regular", Consolas, monospace; background: var(--paper-alt); padding: 0.05rem 0.3rem; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
.to-box, .to-pivot-dot { transition: none; }
}
</style>
</head>
<body>
<div class="infographic-origin" id="to-root">
<div class="to-inner">
<div class="to-layout">
<div class="to-code-wrap">
<div class="to-filetabs">
<button class="to-filetab">styles.css</button>
</div>
<div class="to-code">
<span class="to-line"><span class="to-sel">.box</span> {</span>
<span class="to-line"> <span class="to-prop">width</span>: <span class="to-val">120px</span>;</span>
<span class="to-line"> <span class="to-prop">height</span>: <span class="to-val">120px</span>;</span>
<span class="to-line"> <span class="to-prop">background-color</span>: <span class="to-val">#9b59b6</span>;</span>
<span class="to-line"> <span class="to-prop">border-radius</span>: <span class="to-val">8px</span>;</span>
<span class="to-line"></span>
<span class="to-line"><span class="to-cmt"> /* pivot point */</span></span>
<span class="to-line" id="to-line-origin"> <span class="to-prop">transform-origin</span>: <span class="to-val" id="to-val-origin">bottom right</span>;</span>
<span class="to-line"></span>
<span class="to-line"><span class="to-cmt"> /* rotates around that pivot */</span></span>
<span class="to-line" id="to-line-rotate"> <span class="to-prop">transform</span>: <span class="to-val" id="to-val-rotate">rotate(45deg)</span>;</span>
<span class="to-line">}</span>
</div>
</div>
<div class="to-browser">
<div class="to-browser-bar">
<span class="to-dot"></span><span class="to-dot"></span><span class="to-dot"></span>
<div class="to-tab">๐ Transform Origin</div>
</div>
<div class="to-screen">
<div class="to-stage">
<div class="to-box-outline"></div>
<div class="to-box" id="to-box">โป</div>
<div class="to-pivot-dot" id="to-pivot"></div>
</div>
</div>
<div class="to-controls">
<div class="to-control-row">
<span class="to-control-label">origin</span>
<div class="to-posgrid" id="to-pos-grid">
<button class="to-pos-btn" data-pos="left top" title="top left"></button>
<button class="to-pos-btn" data-pos="center top" title="top center"></button>
<button class="to-pos-btn" data-pos="right top" title="top right"></button>
<button class="to-pos-btn" data-pos="left center" title="center left"></button>
<button class="to-pos-btn" data-pos="center center" title="center (default)"></button>
<button class="to-pos-btn" data-pos="right center" title="center right"></button>
<button class="to-pos-btn" data-pos="left bottom" title="bottom left"></button>
<button class="to-pos-btn" data-pos="center bottom" title="bottom center"></button>
<button class="to-pos-btn to-pos-on" data-pos="right bottom" title="bottom right"></button>
</div>
</div>
<div class="to-control-row">
<span class="to-control-label">rotate</span>
<input type="range" class="to-range" id="to-angle" min="0" max="360" step="5" value="45">
<span class="to-readout" id="to-angle-readout">45deg</span>
</div>
</div>
</div>
<div class="to-note" id="to-note">
<strong>The ringed dot</strong> is the pivot set by <code>transform-origin</code> โ every transform swings around that exact point, not the box's center. Move the pivot around the grid and rotate again to see the same 45ยฐ turn land completely differently depending on where the pivot sits.
</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-origin')
|| document.querySelector('.infographic-origin');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const box = document.getElementById('to-box');
const pivot = document.getElementById('to-pivot');
const posBtns = document.querySelectorAll('#to-pos-grid .to-pos-btn');
const angleRange = document.getElementById('to-angle');
const angleReadout = document.getElementById('to-angle-readout');
const valOrigin = document.getElementById('to-val-origin');
const valRotate = document.getElementById('to-val-rotate');
const lineOrigin = document.getElementById('to-line-origin');
const lineRotate = document.getElementById('to-line-rotate');
const BOX_SIZE = 120;
const originMap = {
'left top': { css: 'top left', x: 0, y: 0 },
'center top': { css: 'top', x: 0.5, y: 0 },
'right top': { css: 'top right', x: 1, y: 0 },
'left center': { css: 'left', x: 0, y: 0.5 },
'center center': { css: 'center', x: 0.5, y: 0.5 },
'right center': { css: 'right', x: 1, y: 0.5 },
'left bottom': { css: 'bottom left', x: 0, y: 1 },
'center bottom': { css: 'bottom', x: 0.5, y: 1 },
'right bottom': { css: 'bottom right', x: 1, y: 1 }
};
let origin = 'right bottom';
let angle = 45;
function render() {
const o = originMap[origin];
box.style.transformOrigin = o.css;
box.style.transform = `rotate(${angle}deg)`;
const boxRect = box.getBoundingClientRect();
const stageRect = box.parentElement.getBoundingClientRect();
const centerX = stageRect.width / 2;
const centerY = stageRect.height / 2;
const px = centerX - BOX_SIZE / 2 + o.x * BOX_SIZE;
const py = centerY - BOX_SIZE / 2 + o.y * BOX_SIZE;
pivot.style.left = px + 'px';
pivot.style.top = py + 'px';
valOrigin.textContent = o.css;
valRotate.textContent = `rotate(${angle}deg)`;
angleReadout.textContent = `${angle}deg`;
}
posBtns.forEach(btn => {
btn.addEventListener('click', () => {
origin = btn.getAttribute('data-pos');
posBtns.forEach(b => b.classList.toggle('to-pos-on', b === btn));
lineOrigin.classList.add('to-hl');
setTimeout(() => lineOrigin.classList.remove('to-hl'), 500);
render();
});
});
angleRange.addEventListener('input', () => {
angle = Number(angleRange.value);
lineRotate.classList.add('to-hl');
render();
});
render();
})();
</script>
</body>
</html>๐น Transition (Smooth Movement)
You can use transition to animate transforms smoothly:
<body>
<div class="box">Hover Me</div>
</body><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hover Transform</title>
<style>
.infographic-hover {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-hover[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.ho-inner { padding: 0.5rem; }
.ho-layout { display: flex; flex-direction: column; gap: 1.2rem; }
.ho-hint-row { display: flex; align-items: center; gap: 0.7rem; align-self: flex-start; flex-wrap: wrap; }
.ho-hint {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.75rem;
color: var(--ink-soft);
}
.ho-sim-btn {
border: 1px solid var(--accent);
background: var(--accent-soft);
color: var(--accent);
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.72rem;
font-weight: 700;
padding: 0.4rem 0.8rem;
border-radius: 999px;
cursor: pointer;
}
.ho-sim-btn.ho-sim-on { background: var(--accent); color: #fff; }
.ho-code-wrap { background: #0b0b0b; border: 1px solid #2a2a2a; border-radius: 10px; overflow: hidden; }
.ho-filetabs { display: flex; background: #161616; border-bottom: 1px solid #2a2a2a; }
.ho-filetab { border: none; background: transparent; color: #fff; font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.78rem; font-weight: 700; padding: 0.6rem 1rem; border-bottom: 2px solid #5b8def; }
.ho-code { padding: 1rem 1.8rem 1rem 0.9rem; font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.78rem; line-height: 1.85; overflow-x: auto; }
.ho-line { display: block; white-space: pre; color: #e5e5e5; padding: 0.05rem 0.4rem; border-radius: 5px; }
.ho-line.ho-hl { background: #1c2333; box-shadow: inset 3px 0 0 #5b8def; color: #ffffff; }
.ho-line.ho-dim { opacity: 0.45; }
.ho-sel { color: #f472b6; font-weight: 700; }
.ho-prop { color: #c586c0; }
.ho-val { color: #ce9178; }
.ho-cmt { color: #6a737d; }
.ho-browser { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--paper); }
.ho-browser-bar { background: var(--paper-alt); padding: 0.5rem 0.7rem; display: flex; align-items: center; gap: 0.5rem; border-bottom: 1px solid var(--line); }
.ho-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--line); }
.ho-tab { background: var(--paper); border: 1px solid var(--line); border-bottom: none; border-radius: 6px 6px 0 0; font-size: 0.72rem; color: var(--ink-soft); padding: 0.25rem 0.6rem; }
.ho-screen {
padding: 2.6rem 1rem;
display: flex;
align-items: center;
justify-content: center;
background:
linear-gradient(var(--line) 1px, transparent 1px),
linear-gradient(90deg, var(--line) 1px, transparent 1px);
background-size: 20px 20px;
}
.ho-stage { position: relative; width: 220px; height: 160px; display: flex; align-items: center; justify-content: center; }
.ho-box-outline {
position: absolute;
width: 150px; height: 100px;
border: 1.5px dashed var(--ink-soft);
border-radius: 10px;
opacity: 0.4;
}
.ho-box {
width: 150px; height: 100px;
background-color: #2980b9;
color: white;
border-radius: 10px;
box-shadow: 0 6px 12px rgba(0,0,0,0.2);
display: flex; align-items: center; justify-content: center;
font-family: sans-serif; font-size: 0.85rem;
cursor: pointer;
box-sizing: border-box;
transition: transform 0.3s ease;
}
.ho-box:hover,
.ho-box.ho-force-hover {
transform: scale(1.2) rotate(10deg);
}
.ho-controls { background: var(--paper-alt); padding: 0.8rem 0.9rem 1rem; display: flex; flex-direction: column; gap: 0.7rem; border-top: 1px solid var(--line); }
.ho-control-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.ho-control-label { font-size: 0.72rem; color: var(--ink-soft); font-family: "SFMono-Regular", Consolas, monospace; min-width: 84px; }
.ho-range { flex: 1; min-width: 100px; accent-color: var(--accent); cursor: pointer; }
.ho-readout { font-size: 0.78rem; color: var(--ink); font-weight: 700; font-family: "SFMono-Regular", Consolas, monospace; min-width: 55px; text-align: right; }
.ho-segmented { display: flex; border: 1px solid var(--line); border-radius: 999px; overflow: hidden; flex-wrap: wrap; }
.ho-seg-btn { border: none; background: var(--paper); color: var(--ink-soft); font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.68rem; font-weight: 700; padding: 0.32rem 0.6rem; cursor: pointer; white-space: nowrap; }
.ho-seg-btn + .ho-seg-btn { border-left: 1px solid var(--line); }
.ho-seg-btn.ho-seg-on { background: var(--accent); color: #fff; }
.ho-note { font-size: 0.82rem; color: var(--ink-soft); padding: 0 0.2rem; }
.ho-note strong { color: var(--ink); }
.ho-note code { font-family: "SFMono-Regular", Consolas, monospace; background: var(--paper-alt); padding: 0.05rem 0.3rem; border-radius: 4px; }
</style>
</head>
<body>
<div class="infographic-hover" id="ho-root">
<div class="ho-inner">
<div class="ho-layout">
<div class="ho-code-wrap">
<div class="ho-filetabs">
<button class="ho-filetab">styles.css</button>
</div>
<div class="ho-code">
<span class="ho-line"><span class="ho-sel">.box</span> {</span>
<span class="ho-line"> <span class="ho-prop">width</span>: <span class="ho-val">150px</span>;</span>
<span class="ho-line"> <span class="ho-prop">height</span>: <span class="ho-val">100px</span>;</span>
<span class="ho-line"> <span class="ho-prop">background-color</span>: <span class="ho-val">#2980b9</span>;</span>
<span class="ho-line"><span class="ho-cmt"> /* smooth transformation */</span></span>
<span class="ho-line" id="ho-line-transition"> <span class="ho-prop">transition</span>: <span class="ho-prop" style="color:#c586c0">transform</span> <span class="ho-val" id="ho-val-duration">0.3s</span> <span class="ho-val" id="ho-val-ease">ease</span>;</span>
<span class="ho-line">}</span>
<span class="ho-line"></span>
<span class="ho-line"><span class="ho-cmt">/* on hover: scale up and rotate */</span></span>
<span class="ho-line" id="ho-line-hover"><span class="ho-sel">.box:hover</span> {</span>
<span class="ho-line" id="ho-code-hover"> <span class="ho-prop">transform</span>: <span class="ho-val">scale(1.2) rotate(10deg)</span>;</span>
<span class="ho-line">}</span>
</div>
</div>
<div class="ho-browser">
<div class="ho-browser-bar">
<span class="ho-dot"></span><span class="ho-dot"></span><span class="ho-dot"></span>
<div class="ho-tab">๐ Hover Transform</div>
</div>
<div class="ho-screen">
<div class="ho-stage">
<div class="ho-box-outline"></div>
<div class="ho-box" id="ho-box">Hover Me</div>
</div>
</div>
<div class="ho-controls">
<div class="ho-control-row">
<span class="ho-control-label">duration</span>
<input type="range" class="ho-range" id="ho-duration" min="0" max="1" step="0.05" value="0.3">
<span class="ho-readout" id="ho-duration-readout">0.3s</span>
</div>
<div class="ho-control-row">
<span class="ho-control-label">easing</span>
<div class="ho-segmented" id="ho-ease-seg">
<button class="ho-seg-btn ho-seg-on" data-ease="ease">ease</button>
<button class="ho-seg-btn" data-ease="linear">linear</button>
<button class="ho-seg-btn" data-ease="ease-in">ease-in</button>
<button class="ho-seg-btn" data-ease="cubic-bezier(.34,1.56,.64,1)">bouncy</button>
</div>
</div>
</div>
</div>
<div class="ho-note" id="ho-note">
<strong>The transition lives on the base <code>.box</code> rule</strong>, not on <code>:hover</code> โ it has to, so the box animates smoothly both entering <em>and</em> leaving the hover state. If you only put <code>transition</code> inside <code>:hover</code>, the box would snap back instantly on mouse-out.
</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-hover')
|| document.querySelector('.infographic-hover');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const box = document.getElementById('ho-box');
const durationRange = document.getElementById('ho-duration');
const durationReadout = document.getElementById('ho-duration-readout');
const valDuration = document.getElementById('ho-val-duration');
const valEase = document.getElementById('ho-val-ease');
const easeSeg = document.querySelectorAll('#ho-ease-seg .ho-seg-btn');
const lineTransition = document.getElementById('ho-line-transition');
const lineHover = document.getElementById('ho-line-hover');
const codeHover = document.getElementById('ho-code-hover');
let simulated = false;
let duration = 0.3;
let ease = 'ease';
function render() {
box.style.transition = `transform ${duration}s ${ease}`;
valDuration.textContent = `${duration}s`;
valEase.textContent = ease;
durationReadout.textContent = `${duration}s`;
box.classList.toggle('ho-force-hover', simulated);
box.title = simulated ? 'Click to reset' : 'Click to simulate hover';
}
function pulse() {
simulated = true;
lineHover.classList.add('ho-hl');
codeHover.classList.add('ho-hl');
render();
clearTimeout(pulse._t);
pulse._t = setTimeout(() => {
simulated = false;
lineHover.classList.remove('ho-hl');
codeHover.classList.remove('ho-hl');
render();
}, Math.max(duration * 1000, 300) + 500);
}
box.addEventListener('click', () => {
simulated = !simulated;
lineHover.classList.toggle('ho-hl', simulated);
codeHover.classList.toggle('ho-hl', simulated);
render();
});
durationRange.addEventListener('input', () => {
duration = Number(durationRange.value);
lineTransition.classList.add('ho-hl');
render();
pulse();
});
easeSeg.forEach(btn => {
btn.addEventListener('click', () => {
ease = btn.getAttribute('data-ease');
easeSeg.forEach(b => b.classList.toggle('ho-seg-on', b === btn));
lineTransition.classList.add('ho-hl');
render();
pulse();
});
});
box.addEventListener('mouseenter', () => {
lineHover.classList.add('ho-hl');
codeHover.classList.add('ho-hl');
});
box.addEventListener('mouseleave', () => {
if (!simulated) {
lineHover.classList.remove('ho-hl');
codeHover.classList.remove('ho-hl');
}
});
render();
})();
</script>
</body>
</html>โ Summary Table
Transform | Description | Example |
|---|---|---|
| Resize the element |
|
| Rotate the element |
|
| Move the element |
|
| Combine multiple effects |
|
| Set pivot point for transformations |
|