A playful button hover interaction where each character alternates direction, creating a smooth up-and-down swap effect.
Right-click in Elementor, choose “Paste from another site,” and while the popup is open, press cmd/ctrl + v to insert the layout.
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>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));
});
});Some solutions only work on the live site. Always publish and test after each change, as results may not appear in the editor.