Optical Type Strain
Transform runs on the element box. Skewing a word shears the line, not each letter.
Drag across the type
Spans
Each character is its own span so a glyph can move without taking the word with it. Distance to the pointer is Math.hypot. Outside 120px the glyph resets.
Put the strain on the word box instead, and the line shears as one piece.
The line shears as one piece.
Force
A hard radius makes a visible circle. Smoothstep eases force from 1 at the pointer to 0 at 120px. Push is 14px, skew is 12deg, Y is scaled by 0.65 so the line does not jump. Values go on el.style.transform — translate3d, skewX, and scaleY. Transforms only — layout would reflow while you move.
const t = 1 - dist / 120;
const force = t * t * (3 - 2 * t);
const nx = dx / dist;
const ny = dy / dist;
el.style.transform = `translate3d(${nx * 14 * force}px, ${ny * 14 * force * 0.65}px, 0) skewX(${-nx * 12 * force}deg) scaleY(${1 + force * 0.18})`;Timing
Pointer move sets data-active: 80ms. Pointer leave clears it: 200ms back to identity. Attack slower than 80ms lags the pointer.
.ots-strain {
transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1);
}
.ots-root[data-active="true"] .ots-strain {
transition-duration: 80ms;
transition-timing-function: cubic-bezier(0.2, 0, 0, 1);
}Wrap
inline-block letters wrap mid-word. Group them in a word span with white-space: nowrap. Spaces stay their own spans so wrap points stay on word boundaries.