L2

·

Text · decode

Text scramble

The decode effect has been around for years and is still the fastest way to make a page feel technical without a single extra asset. It is also the one where the naïve implementation causes real damage, in two ways. It destroys the text: once you start writing random characters into textContent, the real sentence is gone, so a second run scrambles the noise and a screen reader reads gibberish. Cache it in data-text and mirror it into aria-label before the first frame. And it reflows the line: in a proportional font, replacing l with W changes the width, so a headline visibly re-wraps forty times a second. Monospace, or a fixed width, is not a stylistic preference here — it is the fix.

Open the live demo →

How to build

Script only

Free plan gets you part of the way

The panel covers some of this. The rest needs the script, which needs a paid plan — build the panel half now, come back for the rest.

How the two routes work →

Plugins

gsap core · ScrollTrigger

Build time

30 min. The function is fifteen lines.

No-code?

No. The panel animates properties; there is no text-content binding, and no way to express a per-character settle threshold.

01

Webflow structure

element · class · attribute

<b>Heading H1</b> <i>heading-style-h1</i> <u>data-scramble="load"</u> <b>Text</b> <i>hero_meta</i> <u>data-scramble="load"</u> <b>Collection Item</b> <i>service_row</i> <b>Text</b> <i>service_no</i> <b>Text</b> <i>service_name</i> <u>data-scramble="hover"</u> <b>Text</b> <i>service_time</i> <b>Div</b> <i>stat</i> <b>Text</b> <i>stat_value</i> <u>data-scramble="scroll"</u> <b>Text</b> <i>stat_label</i>

02

Styles to set in the Designer

any scrambled element | Set a <strong>monospace</strong> font, or give the element a fixed <code>width</code>/<code>min-width</code>. In a proportional face the line re-wraps on every frame and the whole block jitters. heading-style-h1 | <code>text-transform: uppercase</code> pairs well — the charset is uppercase, so the noise and the real text share a visual weight and the lock-in reads as a settle rather than a swap. service_row | Put the hover listener on the <strong>row</strong>, not the text. Moving between the row number and the name should not re-trigger it. scrambled element | Avoid <code>letter-spacing</code> above about 0.05em. Wide tracking plus changing glyphs is where the jitter becomes obvious even in monospace.

03

Building it with the Interactions panel

no code, free plan

What the panel still can’t do. Only custom code can close these gaps — there is no panel setting for any of them.

See “The script” below for the exact code.

04

Custom attributes

Element settings → Custom attributes

Hero heading, eyebrows | data-scramble | load | Fires once on page load, with a small stagger between elements. List rows, links | data-scramble | hover | Fires on <code>mouseenter</code> of the nearest row. Short duration — 0.55s or it outlasts the hover. Stats, figures | data-scramble | scroll | Fires once when it enters. Pairs well with a counter on the neighbouring number.

05

CSS

Page settings → Inside <head> tag

Inside <head> tag

Copy

06

The script

Page settings → Before </body> tag

Before </body> tag

Copy

<!-- Page settings → Before </body> tag --> <script> document.addEventListener("DOMContentLoaded", () => { gsap.registerPlugin(ScrollTrigger); const CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&$@/\\"; const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches; function scramble(el, duration = 1.1, delay = 0) { // Cache the real string ONCE. After the first frame textContent is noise, // so reading it again would scramble the scramble. const text = el.dataset.text ?? el.textContent; el.dataset.text = text; if (!el.getAttribute("aria-label")) el.setAttribute("aria-label", text); if (reduced) { el.textContent = text; return; } const n = text.length; const state = { p: 0 }; return gsap.to(state, { p: 1, duration, delay, ease: "none", // a linear decode; easing makes it feel broken overwrite: true, // a second hover replaces, never stacks onUpdate() { let out = ""; for (let i = 0; i < n; i++) { // Each character settles a little later than the one before it, // which is what makes it read left-to-right instead of as static. const settleAt = (i / n) * 0.65; if (state.p >= settleAt + 0.35) out += text[i]; else if (text[i] === " ") out += " "; // keep word shape else out += CHARS[(Math.random() * CHARS.length) | 0]; } el.textContent = out; }, onComplete() { el.textContent = text; } // never end on a random frame }); } document.querySelectorAll('[data-scramble="load"]').forEach((el, i) => scramble(el, 1.3, 0.15 + i * 0.12)); document.querySelectorAll('[data-scramble="scroll"]').forEach(el => ScrollTrigger.create({ trigger: el, start: "top 85%", once: true, onEnter: () => scramble(el, 0.9) })); document.querySelectorAll('[data-scramble="hover"]').forEach(el => { const row = el.closest(".service_row") || el; row.addEventListener("mouseenter", () => scramble(el, 0.55)); }); }); </script>

07

Gotchas that will cost you an hour

  1. Cache the text before the first frame. Read textContent a second time and you are scrambling noise — the element permanently fills with garbage and never recovers.
  2. Mirror it into aria-label. Otherwise a screen reader announces whatever random string happened to be there. Two lines, and it is the difference between a nice effect and an inaccessible one.
  3. Monospace, or a fixed width. Proportional glyphs are different widths, so a headline re-wraps on every frame. This is the single most common reason a scramble looks cheap.
  4. Preserve spaces. Scrambling the gaps between words destroys the shape of the sentence, and the effect stops reading as text decoding and starts reading as static.
  5. overwrite: true. Sweep across four rows quickly and without it you get four tweens writing to the same nodes — characters flicker and never settle.
  6. Set the final string in onComplete. A tween can finish a fraction short of 1 and leave one character permanently random. It happens rarely, which is exactly why nobody catches it.
  7. Short durations on hover. 0.55s. Anything longer and the decode is still running after the cursor has left, which feels unresponsive.
08

Push it further

  • Scramble into a different string — cache both, and the element can decode from one word to another. That is the “rotating job title” hero, done well.
  • Weight the charset toward glyphs that suit the brand (hex digits, katakana, box-drawing characters). The charset is the whole personality of the effect.
  • Combine with the reveal system: scramble the heading and fade its paragraph on the same trigger, offset by 0.2s.

Webflow Motion Lab — GSAP 3.15, all plugins free.

17

/ 30