GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

펼쳐지는 버튼 UI 9종 — 모양이 달라지는 아홉 가지 방식

펼쳐지는 버튼은 클릭 값 하나로 버튼 자체의 모양 값이 다른 형태로 바뀌는 인터랙션인데, 아홉 개 중 하나는 열림 값이 바뀌면 버튼의 모서리 둥근 값이 함께 커지고, 하나는 닫힘 값이 켜지면 더하기 표시가 회전하여 닫기 표시로 바뀌며, 또 하나는 펼침 값이 늘어나는 동안 너비 값이

아홉 항목 모두 로드 즉시 도는 자동 시연 루프는 값을 미리 보여주는 보조 장치일 뿐이고, 진짜 계산은 클릭·키보드·포인터 같은 실사용자 입력이 일어나는 순간 다시 돌아갑니다. 아홉 개 중 자동으로만 값이 바뀌는 리스너는 하나도 없고 Math.random 호출도 0번이라, 버튼 하나를 같은 순서로 눌러 보면 매번 같은 모양 값이 나오는 계산입니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개의 해부 — 어떤 모양 값이 언제 바뀌는가

01·02·04·07은 클릭 하나로, 03은 클릭과 키보드(Enter·Space)로, 05는 셸 클릭과 선택지 버튼 두 개의 클릭으로, 06은 입력과 닫기 버튼 클릭과 초점으로, 08은 포인터 진입·이탈과 클릭과 초점으로, 09는 포인터 눌림·뗌과 클릭으로 값을 다시 계산합니다. 아래 표는 아홉 항목의 누적면적·강도·움직인 프레임 실측 값입니다(분모는 캡처 24프레임 중 실제로 값이 바뀐 23프레임입니다).

번호 이름 리스너 누적면적% 강도 움직인프레임/23
01 아코디언 드러냄 전환 click(버튼) 18.30 154.3 8
02 더하기 회전 전환 click(버튼) 0.64 56.7 8
03 모서리 둥근 값 성장 click+keydown(버튼) 1.67 68.6 15
04 너비 관성 전환 click(버튼) 4.33 164.3 14
05 둘로 갈라짐 전환 click(셸)+click(선택지 2) 7.00 100.0 7
06 알약에서 막대로 전환 click(닫기)+input+focus 8.39 141.9 7
07 겹짐 들어올림 전환 click(버튼) 12.56 59.5 8
08 표시와 이름 나타남 전환 pointerenter/leave+click+focus 1.01 68.6 8
09 되튐 복귀 전환 pointerdown+pointerup+click 11.24 79.3 12

09는 누적면적(11.24%)이 07(12.56%)보다 오히려 낮지만, 매 프레임 K(190)·D(14) 스프링 값을 적분해 눌림·되돌림을 계산하는 항목은 04와 09뿐입니다. 그래서 09는 화면을 채우는 면적이 아니라 컴포넌트 한 개 안의 계산 정교함으로 값을 보여 주는 쪽에 가깝습니다.

실측 치수로 목표 값을 정하는 값 — 01·05·08

01 같은 펼침 버튼은 measure()로 답변 목록의 실제 노출 높이를 재 --panel-h로 넘기고, 05는 셸 폭을 실측해 --shell-w로 넘기며, 08은 토글이 일어날 때마다 이름 값의 폭을 다시 재 --label-w로 넘깁니다. 셋 다 짐작한 고정 픽셀이 아니라 지금 화면에 실제로 놓인 크기를 기준으로 목표 값을 정합니다.

여러 단계를 밟으며 값이 쌓이는 값 — 03·07

03은 RADII 배열 [12px, 16px, 24px, 999px]를 클릭·키보드 입력마다 한 칸씩 넘기며 모서리 둥근 값을 단계로 키우고, 07은 마크업에 미리 박힌 --n(뒤에서부터의 차례) 값에 CSS 쪽에서 STEP_PX(34px)를 곱해 카드마다 다른 높이로 들어 올리며 JS는 STAGGER_MS(80ms)로 지연 순서만 심습니다. 둘 다 값 하나가 아니라 여러 단계·여러 요소에 걸쳐 누적되는 계산입니다.

찾기 값 하나가 폭과 목록까지 함께 바꾸는 값 — 06

06은 찾기 값 하나가 켜지고 꺼지는 것만으로 막대 폭·아이콘 자리·닫기 버튼·힌트 목록까지 한 번에 다시 그립니다. 힌트는 SEEDS라는 시드 고정 로컬 배열에서만 골라 채우고, 실제 검색 서버에 값을 보내지 않습니다.

매 프레임 스프링 적분으로 목표를 넘쳤다 되돌아오는 값 — 04·09

04와 09만 CSS transition이 아니라 자바스크립트 tick() 루프가 매 프레임 -K*(cur-target)-D*vel 값을 적분해 너비·형태 값을 그립니다. 01·02·03·05·06·07·08은 자바스크립트가 클래스나 상태 값만 바꾸고, 화면에 보이는 움직임 자체는 SCSS의 transition·animation 선언이 도맡습니다.

CSS 이징 곡선 자체가 오버슈트를 만드는 값 — 02

02는 별도의 자바스크립트 계산 없이 $ease-elastic 큐빅베지어 곡선 하나가 SPIN_DEG(135도)를 지나쳤다가 되돌아오는 값을 만듭니다. 04·09의 자바스크립트 적분과는 다른 방식으로 같은 '지나쳤다 돌아오는' 느낌을 냅니다.

패턴별 코드

01아코디언 드러냄 전환 — 버튼의 열림 값이 바뀌면 둥근 값과 그림자 값이 함께 바뀌며 아래 내용을 내미는 방식

measure()가 하위 항목의 실제 높이를 getBoundingClientRect()로 실측해 --panel-h로 넘기고, 로드 시점에 STAGGER_MS(70ms)만큼 항목 순서대로 늦게 나타나도록 transitionDelay 값을 미리 심어 둡니다. FAQ 항목 여닫기, 설정 묶음 펼침에 쓰는 계산입니다.

  var STAGGER_MS = 70; // 계산 겹: 항목 순서 * 이 값만큼 늦게 나타난다

  // 계산 겹: 하위 항목이 실제로 차지하는 노출 높이를 getBoundingClientRect 로 실측해
  // panel 의 --panel-h 로 넘긴다 — 짐작한 고정값이 아니라 지금 폰트·내용 기준 값이다.
  function measure() {
    var h = 0;
    items.forEach(function (li) { h += li.getBoundingClientRect().height; });
    panel.style.setProperty('--panel-h', h + 'px');
  }
  items.forEach(function (li, i) {
    li.style.transitionDelay = (i * STAGGER_MS) + 'ms';
  });
아코디언 드러냄 전환 — 버튼의 열림 값이 바뀌면 둥근 값과 그림자 값이 함께 바뀌며 아래 내용을 내미는 방식
새 창에서 열기

01 · 아코디언 드러냄 전환 — 버튼의 열림 값이 바뀌면 둥근 값과 그림자 값이 함께 바뀌며 아래 내용을 내미는 방식 — 측정 누적면적 18.301% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

STAGGER_MSmeasuretoggle

02더하기 회전 전환 — 닫힘 값이 켜지면 더하기 표시가 회전하여 닫기 표시로 바뀌는 방식

SPIN_DEG(135)가 90도가 아니라 지나쳤다가 되돌아오는 목표 각도 값을 정하고, 실제 오버슈트는 $ease-elastic 곡선이 .pc__icon의 transform에 걸려 만들어 냅니다. 항목 추가 버튼, 필터 열기 단추에 쓰는 계산입니다.

  var SPIN_DEG = 135; // 계산 겹: 회전 목표 각도 값 — 90도가 아니라 지나쳤다가 되돌아오는 각도
  var OVER_MS = 90;   // 계산 겹: 목표 각도를 넘겼다 되돌리는 여분 시간

  var closed = false;
  function paint() {
    root.classList.toggle('pc--closed', closed);
    btn.setAttribute('aria-pressed', String(closed));
  }

  function toggle() {
    closed = !closed;
    paint();
  }
더하기 회전 전환 — 닫힘 값이 켜지면 더하기 표시가 회전하여 닫기 표시로 바뀌는 방식
새 창에서 열기

02 · 더하기 회전 전환 — 닫힘 값이 켜지면 더하기 표시가 회전하여 닫기 표시로 바뀌는 방식 — 측정 누적면적 0.642% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

SPIN_DEGOVER_MStoggle

03모서리 둥근 값 성장 — 열림 값이 커질수록 버튼의 모서리 둥근 값이 함께 커지는 방식

RADII([12, 16, 24, 999]) 배열 값을 클릭·키보드(Enter·Space) 입력마다 한 단계씩 넘기며 advance()가 모서리 둥근 값을 다시 그리고, human()이 자동 재생을 멈추고 실사용자 조작으로 넘어갑니다. 다단계 폼의 다음 단추, 단계 선택 카드에 쓰는 계산입니다.

  function paint() {
    var r = RADII[step];
    // 둥근 값은 % 가 아니라 px 로 보간해야 성장이 선형으로 보인다 — 999px 는 알약 표기
    btn.style.borderRadius = (r === 999 ? '999px' : r + 'px');
    read.textContent = RADII_TEXT[step];
    fill.style.transform = 'scaleX(' + ((step + 1) / RADII.length) + ')';
    dots.forEach(function (d, i) {
      d.classList.toggle('cr__dot--on', i <= step);
    });
  }

  function advance() {
    step = (step + 1) % RADII.length;
    paint();
  }

  function human() {
    root.classList.remove('is-demo');
    stopAutoplay();
    advance();
  }
  btn.addEventListener('click', human);
  btn.addEventListener('keydown', function (e) {
    if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); human(); }
  });
모서리 둥근 값 성장 — 열림 값이 커질수록 버튼의 모서리 둥근 값이 함께 커지는 방식
새 창에서 열기

03 · 모서리 둥근 값 성장 — 열림 값이 커질수록 버튼의 모서리 둥근 값이 함께 커지는 방식 — 측정 누적면적 1.665% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

RADIIRADII_TEXTadvance

04너비 관성 전환 — 펼침 값이 늘어나는 동안 너비 값이 목표를 살짝 넘었다가 되돌아오는 방식

tick()이 매 프레임 -K*(cur-target)-D*vel 스프링 감쇠 값을 적분해 너비 값을 목표(openW)까지 넘쳤다가 되돌리고, toggle()은 이 목표 값만 바꿀 뿐 너비 자체는 계산하지 않습니다. 본문 중간의 더 보기 단추, 인라인 도구 모음 열기에 쓰는 계산입니다.

  var open = false;
  var cur = REST_W, vel = 0, target = REST_W;
  var raf = null;

  // 스프링 감쇠 계산: 매 프레임 a = -K*(cur-target) - D*vel — 오버슈트 후 안착
  function tick() {
    var a = -K * (cur - target) * 0.001 - D * vel * 0.01;
    vel += a;
    cur += vel;
    if (Math.abs(cur - target) < 0.4 && Math.abs(vel) < 0.05) { cur = target; vel = 0; }
    btn.style.width = cur.toFixed(1) + 'px';
    var p = Math.max(0, Math.min(1, (cur - REST_W) / (openW - REST_W)));
    more.style.opacity = String(p);
    raf = requestAnimationFrame(tick);
  }

  function toggle() {
    open = !open;
    target = open ? openW : REST_W;
    btn.setAttribute('aria-expanded', String(open));
  }
너비 관성 전환 — 펼침 값이 늘어나는 동안 너비 값이 목표를 살짝 넘었다가 되돌아오는 방식
새 창에서 열기

04 · 너비 관성 전환 — 펼침 값이 늘어나는 동안 너비 값이 목표를 살짝 넘었다가 되돌아오는 방식 — 측정 누적면적 4.333% · 움직인 프레임 14/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

REST_WKD

05둘로 갈라짐 전환 — 확정 값이 켜지면 버튼이 두 조각으로 갈라지며 각각 다른 값을 드러내는 방식

measure()가 셸 폭을 실측해 --shell-w로 넘기고, PART_PX(26px)만큼 두 절반이 각자 반대 방향으로 벌어지며 .sd__face--split 값이 드러납니다. 삭제 확인 단추, 이중 선택 확인에 쓰는 계산입니다.

  var PART_PX = 26; // 계산 겹: 두 절반이 벌어지는 거리(px) — 셸 폭 절반 기준 산술

  // 계산 겹: 셸 폭을 getBoundingClientRect 로 실측해 --shell-w 로 넘긴다.
  // 절반은 각자 셸 폭의 50% 로 그려지므로 벌어짐 거리도 이 값에 맞춘다.
  function measure() {
    shell.style.setProperty('--shell-w', shell.getBoundingClientRect().width + 'px');
  }

  var split = false;
  function paint() {
    root.classList.toggle('sd--split', split);
    shell.setAttribute('aria-expanded', String(split));
  }

  function toggle() {
    split = !split;
    paint();
  }
둘로 갈라짐 전환 — 확정 값이 켜지면 버튼이 두 조각으로 갈라지며 각각 다른 값을 드러내는 방식
새 창에서 열기

05 · 둘로 갈라짐 전환 — 확정 값이 켜지면 버튼이 두 조각으로 갈라지며 각각 다른 값을 드러내는 방식 — 측정 누적면적 6.995% · 움직인 프레임 7/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

PART_PXmeasuretoggle

06알약에서 막대로 전환 — 찾기 값이 켜지면 알약 모양 값이 막대 모양 값으로 바뀌며 입력 자리가 생기는 방식

SEEDS 배열은 시드가 고정된 로컬 값이라 실제 검색 API를 부르지 않고, paintHints()가 입력 값이 있을 때만 두 개까지 힌트 값을 켭니다. 머리글의 찾기 단추, 좁은 화면의 찾기 열기에 쓰는 계산입니다.

  // 시드 고정 로컬 값 — 실제 검색 API 는 쓰지 않는다
  var SEEDS = ['샘플 값 1', '샘플 값 2', '샘플 값 3'];
  function paintHints() {
    var hints = root.querySelectorAll('.ps__hint');
    var q = input.value.trim();
    var n = q ? Math.min(2, SEEDS.length) : 0;
    hints.forEach(function (h, i) {
      h.classList.toggle('ps__hint--on', i < n);
      if (i < n) h.textContent = SEEDS[i];
    });
  }
알약에서 막대로 전환 — 찾기 값이 켜지면 알약 모양 값이 막대 모양 값으로 바뀌며 입력 자리가 생기는 방식
새 창에서 열기

06 · 알약에서 막대로 전환 — 찾기 값이 켜지면 알약 모양 값이 막대 모양 값으로 바뀌며 입력 자리가 생기는 방식 — 측정 누적면적 8.393% · 움직인 프레임 7/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

BAR_WSEEDSpaintHints

07겹짐 들어올림 전환 — 자세히 값이 켜지면 뒤에 겹쳐 있던 같은 모양 값들이 위로 차례로 들리는 방식

STEP_PX(34px)와 STAGGER_MS(80ms)가 겹친 카드마다 다른 y 오프셋과 지연 값을 정해, 뒤에 있는 카드일수록 늦게 들리는 순서를 transitionDelay로 만듭니다. 카드 더보기 단추, 알림 목록 열기에 쓰는 계산입니다.

  var STEP_PX = 34; // 계산 겹: 겹침 카드 하나가 위로 들리는 y 오프셋(px) — 스프링 감쇠로 산출
  var STAGGER_MS = 80; // 계산 겹: 뒤 카드부터 이 값씩 늦게 들린다

  ghosts.forEach(function (g, i) {
    g.style.transitionDelay = ((ghosts.length - 1 - i) * STAGGER_MS) + 'ms';
  });
겹짐 들어올림 전환 — 자세히 값이 켜지면 뒤에 겹쳐 있던 같은 모양 값들이 위로 차례로 들리는 방식
새 창에서 열기

07 · 겹짐 들어올림 전환 — 자세히 값이 켜지면 뒤에 겹쳐 있던 같은 모양 값들이 위로 차례로 들리는 방식 — 측정 누적면적 12.562% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

STEP_PXSTAGGER_MStoggle

08표시와 이름 나타남 전환 — 초점 값이 켜지면 표시 크기 값이 커지고 이름 너비 값이 늘어나는 방식

measure()가 paint()를 돌 때마다 이름 값의 실제 폭을 다시 실측해 --label-w로 넘기고, 표시 값이 커지는 계산은 ig--on 클래스가 붙을 때 걸리는 CSS scale(1.25)(ICON_ON과 같은 값)가 맡습니다. 둥근 도구 단추, 이름 나타나는 동작 막대에 쓰는 계산입니다.

  var ICON_ON = 1.25, ICON_OFF = 1; // 계산 겹: 표시 스케일 값 — 초점 값에 따라 곱해진다

  // 계산 겹: 라벨 폭을 getBoundingClientRect 로 실측해 --label-w 로 넘긴다.
  // max-width 전환은 실측 폭과 짝이어야 글자가 중간에 잘리지 않는다.
  function measure() {
    var prev = label.style.maxWidth;
    label.style.maxWidth = 'none';
    var w = Math.ceil(label.getBoundingClientRect().width);
    label.style.maxWidth = prev || '0px';
    tool.style.setProperty('--label-w', w + 'px');
  }

  var on = false;
  function paint() {
    measure();
    root.classList.toggle('ig--on', on);
    tool.setAttribute('aria-pressed', String(on));
  }
표시와 이름 나타남 전환 — 초점 값이 켜지면 표시 크기 값이 커지고 이름 너비 값이 늘어나는 방식
새 창에서 열기

08 · 표시와 이름 나타남 전환 — 초점 값이 켜지면 표시 크기 값이 커지고 이름 너비 값이 늘어나는 방식 — 측정 누적면적 1.013% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

ICON_ONICON_OFFmeasure

09되튐 복귀 전환 — 접힘 값이 켜지면 형태 값이 줄어들다가 반대로 살짝 늘었다가 제 형태로 돌아가는 방식

tick()이 K(190)·D(14) 스프링 값으로 scaleX를 계산해 눌렀다 놓는 순간 형태 값이 줄었다가 반대로 살짝 늘었다가 돌아오고, --press 값이 같은 계산을 칩 목록의 opacity·위치에도 물립니다. 필터 접기 단추, 댓글 창 닫기에 쓰는 계산입니다.

  var K = 190, D = 14; // 계산 겹: 스프링 상수·감쇠 — 눌렀다 놓는 순간의 되튐 값을 정한다

  var collapsed = false;
  var cur = 1, vel = 0, target = 1;
  var raf = null;

  // 스프링 감쇠 계산: 매 프레임 a = -K*(cur-target) - D*vel — 줄어들었다가 반대로 살짝 늘었다가 제 형태
  function tick() {
    var a = -K * (cur - target) * 0.001 - D * vel * 0.01;
    vel += a;
    cur += vel;
    if (Math.abs(cur - target) < 0.004 && Math.abs(vel) < 0.005) { cur = target; vel = 0; }
    btn.style.transform = 'scaleX(' + cur.toFixed(3) + ')';
    var p = Math.max(0, Math.min(1, (1 - cur) / 0.28));
    root.style.setProperty('--press', p.toFixed(3));
    raf = requestAnimationFrame(tick);
  }

  function toggle() {
    collapsed = !collapsed;
    target = collapsed ? 0.72 : 1;
    btn.setAttribute('aria-pressed', String(collapsed));
    root.classList.toggle('eb--collapsed', collapsed);
  }
되튐 복귀 전환 — 접힘 값이 켜지면 형태 값이 줄어들다가 반대로 살짝 늘었다가 제 형태로 돌아가는 방식
새 창에서 열기

09 · 되튐 복귀 전환 — 접힘 값이 켜지면 형태 값이 줄어들다가 반대로 살짝 늘었다가 제 형태로 돌아가는 방식 — 측정 누적면적 11.242% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

KDtick

값이 패턴을 바꾸는 곳 — 둥근 반지름·회전 각도·너비 기준

둥근 반지름 기준은 01과 03이 다르게 잡습니다. 01은 border-radius를 열림 여부 두 값(닫힘·열림)만 오가며 $r-control 토큰 그대로 쓰는 반면, 03은 RADII 배열 값 자체가 [12px, 16px, 24px, 999px] 네 단계로 늘어나 마지막 단계에서 알약 형태(999px)까지 이어집니다.

회전 각도 기준은 02의 SPIN_DEG(135도) 하나가 정합니다. 90도가 아니라 135도를 목표로 잡아 $ease-elastic 곡선이 그 값을 넘겼다가 되돌아오는 폭을 크게 만들고, 되돌아오는 시간 자체는 .pc__icon에 걸린 360ms 전환 값이 정합니다.

너비를 바꾸는 기준은 세 갈래입니다. 04는 K(170)·D(12) 스프링 값으로 목표 너비를 넘쳤다가 돌아오는 관성 계산이고, 09는 같은 스프링 공식을 K(190)·D(14)로 형태 값(scaleX)에 적용해 접힘·복귀를 계산하며, 06은 스프링 계산 없이 700ms $ease-spring CSS 전환 하나로 알약 폭에서 막대 폭까지 늘어납니다. 04·09처럼 접기 펼치기 버튼이 자바스크립트로 값을 미는 쪽과, 06처럼 CSS 전환 값 하나로 끝나는 쪽이 갈립니다.

어디서 깨지는가 — 글자 폭이 바뀌는 순간·좁은 화면·모션 감소

글자 폭이 바뀌는 순간을 다루는 방식이 04와 08에서 갈립니다. 04는 openW를 measure()로 페이지가 열릴 때 딱 한 번만 재고 그 뒤로는 다시 재지 않아, 만약 웹폰트가 늦게 로드돼 안에 든 글자 폭이 나중에 달라지면 목표 너비 값이 어긋난 채로 굳습니다. 08은 반대로 paint()가 돌 때마다(초점·클릭마다) measure()를 다시 불러 --label-w를 갱신하므로 같은 문제가 생기지 않습니다.

좁은 화면(340px 이하)에서는 항목마다 축소 폭이 다릅니다. 06은 알약이 열렸을 때 320px에서 260px로, 09는 버튼이 220px에서 190px로 줄어들지만, 04·09의 스프링 계산 자체에는 리사이즈 리스너가 따로 없어 페이지 로드 시점의 크기를 기준으로 굳고 이후 화면 폭이 바뀌어도 다시 재지 않습니다. 아홉 개 버튼 형태의 html·scss·js 소스 전부가 비번 pv87z5gp 으로 열리는 zip 안에 들어 있어, 위에서 짚은 04·09의 리사이즈 미대응 지점을 직접 코드에서 확인하고 고쳐볼 수 있습니다.

모션을 줄이는 처리는 아홉 항목 모두 자바스크립트에서 window.matchMedia('(prefers-reduced-motion: reduce)')로 판정하고, SCSS 쪽에서도 prefers-reduced-motion 미디어 쿼리로 같은 판정을 한 번 더 걸어 둡니다. 다만 꺼지는 대상은 항목마다 다릅니다 — 01·02·05·06·07·08은 reduce 값이 켜지면 setInterval 자동 재생 대신 곧장 열림·닫힘 같은 최종 상태 하나로 다시 그려 넣고 멈추는 반면, 03은 별도 분기 없이 setInterval 자체만 시작하지 않아 처음 그려진 단계(둥근 값 12px)에서 그대로 멈춥니다. 04·09는 자동 재생을 멈추는 것과 별개로 tick() 안의 목표 값(target)과 현재 값(cur)을 처음부터 같은 수로 맞춰 두어, requestAnimationFrame 루프는 계속 돌아도 화면에는 아무 변화가 나타나지 않습니다.

변형 세 가지와 접근성

이름 값 느낌
아코디언 겹침 간격 01 STAGGER_MS 70 → 140 펼침 항목이 두 배 천천히 차례로 나타나 열림 값의 전파가 한눈에 읽힌다
너비 스프링 강성 04 K 170 → 90 목표 폭을 지나치는 오버슈트가 커지고 느리게 안착해 늘어남이 무겁고 탱탱해 보인다
되튐 감쇠 비율 09 D 14 → 22 되튐이 한 번에 빨리 잦아들어 탄력이 짧고 단단한 쪽으로 돌아간다

접근성은 버튼 요소를 실제로 쓴 일곱 항목(01·02·04·06·07·08·09)이 aria-expanded·aria-pressed·aria-label 값을 상태에 맞춰 갱신합니다 — 01·04·07은 열림 여부를 aria-expanded로, 02·08·09는 눌림 여부를 aria-pressed로, 06은 입력창과 닫기 버튼에 각각 aria-label을 답니다. 03은 <div role="button" tabindex="0">에 keydown(Enter·Space) 리스너까지 달아 버튼이 아닌 요소도 키보드로 조작할 수 있게 했지만, 05는 반대로 .sd__shell이 div 그대로라 role이나 tabindex가 없어 처음 갈라지게 여는 동작은 클릭에서만 되고 안의 그만두기·지우기 버튼만 키보드로 닿습니다. 장식용 아이콘·점(01의 화살표, 03의 단계 점, 08의 동료 아이콘)에는 aria-hidden="true"를 달아 스크린리더가 읽지 않게 뺐습니다.

FAQ

01 같은 아코디언 버튼 ui 는 라이브러리 없이 그대로 붙여넣어도 동작하나요?

네, 동작합니다. 01은 순수 자바스크립트로 getBoundingClientRect()와 transitionDelay만 쓰고 GSAP·jQuery 같은 외부 라이브러리를 부르지 않습니다. HTML·SCSS·JS 세 파일을 그대로 옮기면 같은 열림 값·둥근 값·그림자 값 전환이 그대로 재현됩니다.

02의 더하기 회전과 04·09의 스프링 되돌림은 왜 계산 방식이 다른가요?

02는 자바스크립트 계산 없이 SPIN_DEG(135도)라는 목표 각도 값 하나만 정해 두고, 실제로 지나쳤다 돌아오는 움직임은 CSS $ease-elastic 곡선이 만듭니다. 04·09는 반대로 tick()이 매 프레임 K·D 스프링 값을 직접 적분해 너비·형태 값을 계산하므로, 같은 '되돌아오는' 느낌이라도 하나는 CSS 곡선이, 다른 둘은 자바스크립트 루프가 만드는 값입니다.

이 아홉 가지 버튼 형태 전환을 실제 서비스 폼에 그대로 옮겨 써도 되나요?

형태를 바꾸는 계산(둥근 값·회전 값·너비 값 등)은 그대로 옮길 수 있지만, 05의 삭제 확인처럼 실제 삭제 동작이 필요한 화면은 서버 쪽 처리 로직을 따로 붙여야 합니다. 이 아홉 항목은 화면이 어떻게 반응하는지 보여 주는 프런트엔드 계산이라, 실제 데이터 처리는 각 서비스의 백엔드 검증을 거쳐야 확정됩니다.

더 읽을거리

CSS 버튼 애니메이션 9종은 hover 시 색·크기 값이 트윈으로 바뀌는 장식 축을 다루는 데 비해, 이 편은 클릭 값 하나로 버튼 자체의 모양(둥근·회전·너비·형태) 값이 목표 형태까지 전환되는 계산만 깊게 판다는 점에서 계산 축이 다릅니다.

CSS 아코디언 애니메이션 9종은 패널·목록 콘텐츠의 높이 값이 늘고 줄어드는 아코디언을 다루는 반면, 이 편의 01은 그 패널을 여는 버튼 자체의 둥근 값·그림자 값이 열림 값과 함께 바뀐다는 점에서 콘텐츠 축이 아니라 버튼 축의 계산입니다.

플로팅 버튼 아이콘 버튼 패턴 9종은 아이콘 버튼의 형태 모음을 폭넓게 다루는데, 이 편의 02 더하기 회전은 회전 각도 값 하나가 지나쳤다 되돌아오는 계산 자체를 깊게 본다는 점에서 계산 축이 다릅니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.