Text Animations
Scramble Text On Hover

Info

A playful text hover effect where the words briefly scramble before settling into the new label. It works great for buttons, links, cards, and small interactive labels where you want the text to feel more dynamic.

Text Animations
May 1, 2026

Required

No items found.

Setup

00

Copy structure to Elementor

Right-click in Elementor, choose “Paste from another site,” and while the popup is open, press cmd/ctrl + v to insert the layout.

Copy To Elementor
Download JSON Template
00

Add External Scripts

Place the scripts before the body tag of your project. If you have added them before for another setup, skip this step.

<!-- GSAP Core -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/gsap.min.js"></script>

<!-- GSAP ScrambleText Plugin -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/ScrambleTextPlugin.min.js"></script>
00

Add Custom Javascript

Paste the script through Elementor → Custom Code and set it to load after the closing body tag.

document.addEventListener("DOMContentLoaded", () => {
  if (document.body.classList.contains("elementor-editor-active")) return;
  if (!window.gsap || !window.ScrambleTextPlugin) return;

  gsap.registerPlugin(ScrambleTextPlugin);

  const defaultAnim = {
    duration:    0.8,
    ease:        "none",
    reset:       true,
    touch:       false,
    chars:       "upperAndLowerCase",
    tweenLength: true,
    revealDelay: 0,
    speed:       1,
    delimiter:   "chars"
  };

  const isTouch    = window.matchMedia("(hover: none), (pointer: coarse)").matches;
  const getNumber  = (v, fb) => { const n = parseFloat(v); return Number.isFinite(n) ? n : fb; };
  const getBoolean = (v, fb = false) => {
    if (v === undefined || v === "") return fb;
    if (v === "true") return true;
    if (v === "false") return false;
    return fb;
  };
  const getDelimiter = value => {
    const v = value || defaultAnim.delimiter;
    if (v === "words") return " ";
    if (v === "chars") return "";
    return defaultAnim.delimiter === "words" ? " " : "";
  };

  document.querySelectorAll('[data-scramble-hover-element="text"]').forEach(textEl => {
    const wrap     = textEl.closest('[data-scramble-hover-element="wrap"]') || textEl;
    const original = textEl.textContent;
    if (!original.trim()) return;

    const swap        = textEl.dataset.scrambleHoverSwap                   || original;
    const duration    = getNumber (textEl.dataset.scrambleHoverDuration,    defaultAnim.duration);
    const ease        = textEl.dataset.scrambleHoverEase                   || defaultAnim.ease;
    const reset       = getBoolean(textEl.dataset.scrambleHoverReset,       defaultAnim.reset);
    const allowTouch  = getBoolean(textEl.dataset.scrambleHoverTouch,       defaultAnim.touch);
    const chars       = textEl.dataset.scrambleHoverChar                   || defaultAnim.chars;
    const tweenLength = getBoolean(textEl.dataset.scrambleHoverTweenLength, defaultAnim.tweenLength);
    const revealDelay = getNumber (textEl.dataset.scrambleHoverRevealDelay, defaultAnim.revealDelay);
    const speed       = getNumber (textEl.dataset.scrambleHoverSpeed,       defaultAnim.speed);
    const delimiter   = getDelimiter(textEl.dataset.scrambleHoverDelimiter);

    let isSwapped = false;

    const play = text => {
      gsap.killTweensOf(textEl);
      gsap.to(textEl, {
        duration, ease, overwrite: true,
        scrambleText: { text, chars, tweenLength, revealDelay, speed, delimiter }
      });
    };

    const enter = () => play(swap);
    const leave = () => { if (reset) play(original); };

    if (isTouch) {
      if (!allowTouch) return;
      wrap.addEventListener("click", () => {
        if (reset) {
          isSwapped = !isSwapped;
          play(isSwapped ? swap : original);
        } else {
          play(swap);
        }
      });
      return;
    }

    wrap.addEventListener("mouseenter", enter);
    wrap.addEventListener("mouseleave", leave);
  });
});
00

Publish and preview live

Some solutions only work on the live site. Always publish and test after each change, as results may not appear in the editor.

Required Attribute

Text

Add data-scramble-hover-element="text" to the text element you want to animate.

This is the only required attribute. By default, the text itself becomes the hover trigger, so when the user hovers over the text, the scramble animation runs.

Settings

Wrapper

If you want the animation to trigger when hovering over a parent wrapper instead of the text itself, add data-scramble-hover-element="wrap" to the wrapper div.

This is useful for buttons, cards, links, or larger clickable areas where the text sits inside another element.

Swap Text

By default, the text scrambles back into its original value. You can change the hover text by adding data-scramble-hover-swap="VALUE" to the text element. For example, you could change Read More to View Project when the user hovers.

It’s best to keep the new text close in length to the original text, otherwise the layout may shift or feel a bit awkward during the animation.

Animation Duration

By default, the scramble animation runs for 0.8 seconds. You can adjust the speed by adding data-scramble-hover-duration="VALUE" to the text element. For example, use 0.4 for a faster animation, 0.8 for the default speed, or 1.2 for a slower effect.

Scramble Speed

By default, the scramble speed is 1. You can adjust the intensity by adding data-scramble-hover-speed="VALUE" to the text element. For example, use 0.6 for a calmer effect, 1 for the default feel, or 2 for a faster and more energetic scramble.

Reveal Delay

By default, reveal delay is set to 0 seconds, meaning the final text starts revealing immediately. You can delay the reveal by adding data-scramble-hover-reveal-delay="VALUE" to the text element. For example, use 0.2 if you want the scramble to run briefly before the final letters start appearing.

Animation Ease

By default, the animation uses none, meaning it plays at a consistent speed. You can change this by adding data-scramble-hover-ease="VALUE" to the text element. You can use any valid GSAP ease, such as power1.out, power2.out, power3.inOut, or none.

Scramble Characters

By default, the script uses upperAndLowerCase characters while scrambling. You can change this by adding data-scramble-hover-char="VALUE" to the text element. For example, use 0123456789 if you want a number-style scramble effect.

Common values include upperCase, lowerCase, upperAndLowerCase, or your own custom characters.

Tween Length

By default, tweenLength is set to true, so the text length smoothly changes if the swap text has a different number of characters. You can disable this by adding data-scramble-hover-tween-length="false" to the text element.

For example, if the original text is 50 characters and the replacement text is 100 characters, the number of characters will gradually move from 50 to 100 during the animation instead of jumping immediately to 100.

Delimiter

By default, the delimiter is set to chars, meaning the text scrambles character by character. You can change this by adding data-scramble-hover-delimiter="VALUE" to the text element. Use chars for letter-by-letter scrambling, or words if you want the text to reveal word by word instead.

Reset on Mouse Leave

By default, reset is set to true, so the text scrambles back to the original text when the mouse leaves. You can disable this by adding data-scramble-hover-reset="false" to the text element. Use false if you want the changed text to stay after hover instead of returning to the original text.

Touch Devices

By default, the effect is disabled on touch devices because there is no real hover state. You can enable it by adding data-scramble-hover-touch="true" to the text element.

On touch devices, the animation runs on click instead of hover. If reset is true, each click toggles between the original text and the swap text. If reset is false, clicking always animates to the swap text.

Global Default Values

You can also adjust the default global values directly inside the script. These values apply to every scramble hover text unless you override them with attributes on the text element.

To change the default animation behavior across all scramble hover elements, edit the values inside the defaultAnim object in the code.

const defaultAnim = {
  duration: 0.8,
  ease: "none",
  reset: true,
  touch: false,
  chars: "upperAndLowerCase",
  tweenLength: true,
  revealDelay: 0,
  speed: 1,
  delimiter: "chars"
};