Buttons
Button 03

Info

A playful button hover interaction where each character alternates direction, creating a smooth up-and-down swap effect.

Buttons
May 24, 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

Important

  • Use Elementor 3.11 or above.
  • Before copying, make sure all required widgets and features are active, including Elementor Pro widgets, Editor v4, and Flexbox Containers.
  • Safari may block clipboard access, so Chrome or Firefox is recommended for smoothest copy/paste experience.
  • If copy/paste fails, clear cache/cookies and disable any plugins or browser extensions that may interfere.
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 SplitText -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/SplitText.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.matchMedia("(hover: none), (pointer: coarse)").matches) return;
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;

  const defaults = {
    duration: 0.45,
    amount: 0.015,
    ease: "power2.out",
    reverse: true
  };

  document.querySelectorAll('[data-oura-button-03="button"]').forEach((wrap) => {

    const text = wrap.querySelector("span");
    if (!text) return;

    const clip = document.createElement("span");
    text.parentNode.insertBefore(clip, text);
    clip.appendChild(text);

    const clone = text.cloneNode(true);
    clone.setAttribute("aria-hidden", "true");
    clip.appendChild(clone);

    Object.assign(clip.style,  { position: "relative", display: "inline-block", overflow: "hidden" });
    Object.assign(clone.style, { position: "absolute", left: "0", top: "0" });

    const splitOriginal = new SplitText(text,  { type: "chars" });
    const splitClone    = new SplitText(clone, { type: "chars" });

    const originalItems = splitOriginal.chars;
    const cloneItems    = splitClone.chars;

    const tl = gsap.timeline({ paused: true });

    originalItems.forEach((char, index) => {
      const dir  = index % 2 === 0 ? -100 : 100;
      const time = index * defaults.amount;

      gsap.set(char,              { y: 0 });
      gsap.set(cloneItems[index], { y: `${-dir}%` });

      tl.to(char,              { y: `${dir}%`, duration: defaults.duration, ease: defaults.ease }, time);
      tl.to(cloneItems[index], { y: 0,         duration: defaults.duration, ease: defaults.ease }, time);
    });

    wrap.addEventListener("mouseenter", () => tl.restart());
    wrap.addEventListener("mouseleave", () => defaults.reverse ? tl.reverse() : tl.pause(0));

  });

});
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.