Playground

Button Effects

March 2025

"use client";

import {cn} from "@/app/_lib/cn";
import React, {useState} from "react";

// Styles in tailwind
// --animate-swipe-right: swipe-right 0.6s cubic-bezier(0.83, 0, 0.17, 1) forwards;
// @keyframes swipe-right {
//     0% {
//         transform: translate3d(0, 0, 0);
//     }

//     100% {
//         transform: translate3d(150%, 0, 0);
//     }
// }

export default function ButtonEffects() {
    return (
        <article className="relative grid min-h-96 place-items-center rounded-xl border border-white/10 bg-white/5 p-3 md:aspect-[3/2] md:min-h-0">
            <div className="grid w-full sm:grid-cols-2 content-center items-center justify-center justify-items-center gap-5 sm:gap-y-10 gap-y-7">
                <InvertButton />
                <ArrowSwipeButton />
                <ScaleButton />
                <HoldAndReleaseButton />
            </div>
        </article>
    );
}

const InvertButton = () => {
    return (
        <button
            className={cn(
                // Base styles
                "font-inter group relative w-fit cursor-pointer overflow-hidden rounded-full border border-current px-6 py-2 text-lg font-medium tracking-wide text-white",
                // Effect styles
                "transition-transform duration-700 ease-out will-change-transform hover:scale-x-[1.02] hover:ease-[cubic-bezier(.34,5.56,.64,1)]"
            )}>
            <span
                data-text={"Invert Button"}
                className={cn(
                    "relative block overflow-hidden",
                    // After child
                    "after:absolute after:left-0 after:z-1 after:translate-y-full after:transform after:text-black after:duration-700 after:ease-[cubic-bezier(.4,0,0,1)] after:will-change-transform after:content-[attr(data-text)] group-hover:after:translate-y-0"
                )}>
                <span className="inline-block duration-700 ease-[cubic-bezier(.4,0,0,1)] group-hover:-translate-y-full">
                    Invert Button
                </span>
            </span>

            <span className="absolute inset-0 translate-y-full rounded-[50%_50%_0_0] bg-white transition-all duration-500 ease-[cubic-bezier(.4,0,0,1)] group-hover:translate-y-0 group-hover:rounded-none"></span>
        </button>
    );
};

const ScaleButton = () => {
    return (
        <button
            className={cn(
                // Base styles
                "group relative w-fit cursor-pointer px-6 py-2 text-lg font-medium tracking-wide text-white"
            )}>
            <div className="absolute inset-0 overflow-hidden rounded-full border border-current transition duration-600 ease-[cubic-bezier(0.1,0,0.3,1)] will-change-transform group-hover:scale-[1.1]">
                <span className="absolute -inset-[10%] scale-0 rounded-full bg-current ease-[cubic-bezier(0.1,0,0.3,1)] will-change-transform group-hover:scale-100 group-hover:transition group-hover:duration-600"></span>
                <span className="group absolute -inset-[10%] rounded-full bg-current opacity-0 transition-opacity duration-600 ease-[cubic-bezier(0.1,0,0.3,1)] group-hover:opacity-100 group-hover:delay-400 group-hover:duration-[0.01s]"></span>
            </div>

            <span className="inline-block mix-blend-difference">
                Scale Button
            </span>
        </button>
    );
};

const ArrowSwipeButton = () => {
    return (
        <button
            className={cn(
                // Base styles
                "font-inter group relative inline-flex w-fit cursor-pointer items-center gap-x-2 overflow-hidden rounded-full border border-current py-1 pr-1 pl-6 text-lg font-medium tracking-wide text-white"
            )}>
            <span>Arrow Swipe</span>
            <span className="relative grid size-9 place-items-center overflow-hidden">
                <span className="absolute inset-0 scale-0 transform rounded-full bg-white duration-600 ease-[cubic-bezier(0.83,0,0.17,1)] group-hover:scale-100"></span>
                <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    strokeWidth={1.5}
                    stroke="currentColor"
                    className="group-hover:animate-swipe-right size-5 mix-blend-difference">
                    <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
                    />
                </svg>

                <span className="absolute top-1/2 -translate-x-[150%] -translate-y-1/2 mix-blend-difference">
                    <svg
                        xmlns="http://www.w3.org/2000/svg"
                        fill="none"
                        viewBox="0 0 24 24"
                        strokeWidth={1.5}
                        stroke="currentColor"
                        className="group-hover:animate-swipe-right size-5 mix-blend-difference">
                        <path
                            strokeLinecap="round"
                            strokeLinejoin="round"
                            d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
                        />
                    </svg>
                </span>
            </span>
        </button>
    );
};

const HoldAndReleaseButton = () => {
    const [isMouseDown, setIsMouseDown] = useState(false);

    const handleMouseDown = () => {
        setIsMouseDown(true);
    };
    const handleMouseUp = () => {
        setIsMouseDown(false);
    };
    return (
        <button
            onMouseDown={handleMouseDown}
            onMouseUp={handleMouseUp}
            onTouchStart={handleMouseDown}
            onTouchEnd={handleMouseUp}
            className={cn(
                // Base styles
                "font-inter group relative w-fit cursor-pointer overflow-hidden rounded-full p-0.5 px-6 py-2 text-lg font-medium tracking-wide text-white transition-transform will-change-transform",
                isMouseDown ? "scale-90 duration-1500" : "duration-300"
            )}>
            <span
                className={cn(
                    "absolute inset-0 rounded-full border border-current"
                )}></span>
            <span
                className={cn(
                    "absolute inset-0 origin-left border border-current bg-white transition-transform",
                    isMouseDown
                        ? "scale-x-100 duration-1500"
                        : "scale-x-0 duration-300"
                )}></span>
            <span className="inline-flex items-center gap-x-3 mix-blend-difference">
                <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    strokeWidth={1.5}
                    stroke="currentColor"
                    className="size-5">
                    <path
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
                    />
                </svg>
                Hold and Release
            </span>
        </button>
    );
};
Next

Infinite Carousel