패턴

[Speed Dial] 버튼 하나가 여러 동작으로 펼쳐지는 플로팅 액션 메뉴

Gmail 작성 버튼처럼, + 버튼을 누르면 공유·편집·삭제 같은 액션이 위로 차례로 떠오르는 FAB 메뉴입니다.

ReactTypeScriptCSS transformtransitionuseClickOutside
라이브 데모
새 탭에서 열기
데모 불러오는 중…

제작 과정

한 줄 요약 + 버튼 하나를 누르면 여러 개의 동작 버튼이 위로 차례로 펼쳐지고, 바깥을 누르면 다시 접히는 플로팅 메뉴예요.

이럴 때 필요해요

화면 구석에 자주 쓰는 동작(글쓰기공유추가)을 늘 띄워두고 싶은데, 버튼을 여러 개 깔면 화면이 복잡해져요. 스피드다이얼은 평소엔 버튼 하나로 접어두고, 필요할 때만 펼쳐서 선택하게 해줍니다. 모바일 앱의 우하단 동그란 + 버튼이 바로 이거예요. 공간은 아끼면서 동작은 빠르게 꺼낼 수 있죠.

어떻게 동작하나

  1. 메인 + 버튼을 누르면 open 상태가 켜지고, 위에 쌓인 액션 버튼들이 보이게 돼요.

  2. 각 액션은 위치를 옮기는 게 아니라 transform(위로 살짝 이동 + 크기)과 opacity만 바꿔서 떠오르듯 나타나요 이 방식은 브라우저가 부드럽게 처리해 끊김이 적어요.

  3. 버튼마다 시작 시간을 조금씩 늦춰서(stagger, 시차) 하나씩 차례로 뜨는 계단 효과를 줘요. 메인 버튼에 가까운 것부터 뜨게 순서를 뒤집었어요.

  4. 메뉴가 열려 있을 때 바깥 영역을 누르면 useClickOutside가 감지해 자동으로 닫아요.

핵심은 이거예요

열고 닫는 걸 "요소를 새로 그리는" 대신 transformopacity 값만 바꿔서 표현한다는 점이에요. 이것만 잡으면 애니메이션이 가볍고, 닫혔을 때는 pointer-events: none으로 안 보이는 버튼이 눌리는 사고만 막아주면 끝이에요.

놓치기 쉬운 것

  • 닫힌 상태의 액션 버튼은 투명해도 그 자리에 남아 있어요. pointer-events: nonetabIndex=-1을 안 주면 안 보이는 버튼이 클릭탭 포커스로 잡혀요.

  • 펼침 순서(stagger)는 인덱스에 지연 시간을 곱해서 만드는데, 열 때와 닫을 때 순서를 반대로 해야 자연스러워요.

  • 바깥 클릭 닫기는 메뉴가 열렸을 때만 감지하도록 켜고 꺼야 불필요한 이벤트를 줄여요.

이런 곳에 써요

  • 모바일/태블릿 화면 우하단의 빠른 작업 버튼(새 글, 추가, 공유)

  • 관리자 화면에서 행마다 두기엔 많은 액션을 한 버튼으로 묶을 때

  • 지도캔버스처럼 화면을 넓게 쓰는 도구의 떠 있는 도구 모음

소스 코드

· 데모 페이지에서 자동 추출
import type { Metadata } from "next";
import { SpeedDialDemo } from "./-components/SpeedDialDemo";

export const metadata: Metadata = {
  title: "Speed Dial FAB (데모)",
  description:
    "플로팅 액션 버튼을 누르면 액션들이 차례로 펼쳐지는 스피드다이얼. transform + stagger + 외부 클릭 닫기.",
  robots: { index: false, follow: false },
};

export default function Page() {
  return (
    <main className="flex min-h-[100dvh] items-center justify-center bg-gradient-to-br from-violet-50 via-white to-fuchsia-50/60">
      <SpeedDialDemo />
    </main>
  );
}
18조회수

댓글