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

마이크로 인터랙션 사이트 UI 패턴 9종 — 토스트부터 스크롤 스파이까지

마이크로 인터랙션 사이트에 흔한 아홉 개 중 하나는 알림이 쌓인 순서 값으로 사라지는 시점을 다시 구해 뒤 알림 위치를 당기고, 하나는 스크롤된 위치에서 각 제목의 겹침 비율 값을 비교해 활성 항목을 바꾸며, 또 하나는 입력 값이 바뀔 때마다 대기 시간 값을 처음부터 다시 셉니다.

이 아홉 화면은 실제 서비스나 진짜 알림·검색 데이터가 아니라 가상의 값으로 만든 것이며, 09 는 navigator.clipboard 를 호출하지 않고 라벨 전환만으로 복사 성공을 흉내 냅니다. 09 를 뺀 나머지 여덟은 클릭·키다운·포인터·입력 중 하나를 실제로 걸어 두었고, 09 도 클릭이 값을 바꾸는 실제 리스너입니다 — 아홉 개 모두 자동 재생처럼 보이는 순간에도 사람이 직접 만지면 같은 함수가 실행됩니다.

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

버튼·로딩 같은 낱개 컴포넌트와 달리 이 아홉 개는 화면 어딘가에서 이미 흔히 보이는 UI 조각인데도, 정작 마이크로 인터랙션 디자인을 처음 만들 때 참고할 실측 값은 잘 안 보입니다. 아래 표는 아홉 인터랙션이 화면을 실제로 얼마나 흔들었는지 잰 값이고, 값이 클릭 한 번으로 뒤집히는 것(01·08·09) → 키보드가 범위·위치 값을 다시 세는 것(02·03·06) → 옵저버·입력이 값을 계속 흘리는 것(04·05·07) 순으로 묶었습니다.

마이크로 인터랙션 사례 아홉 개의 해부 — 값이 언제 다시 계산되는가

실측 한눈에

항목 초점 누적면적 % 강도 평균 움직인 프레임
01 토스트 알림 큐 쌓인 순서 값 → 표시 시간 재계산 19.39 34.3 10/23
02 다이얼로그 포커스 트랩 포커스 요소 범위 값 → 탭 순환 20.11 73.6 15/23
03 키보드 탭 활성 탭 위치·폭 값 → 밑줄 이동 0.95 55.4 8/23
04 스크롤 스파이 목차 겹침 비율 값 비교 → 활성 항목 교체 2.21 136.3 10/23
05 인터섹션 리빌 가시 비율 값 → 1회 등장 후 고정 21.37 92.4 8/23
06 키보드 단축키 조합 값 매칭 → 강조·안내 갱신 20.50 82.0 12/23
07 디바운스 검색 입력 값 → 대기 시간 재시작 20.59 137.1 19/23
08 테마 저장 저장된 상태 값 → 초기 테마 결정 6.72 84.6 12/23
09 클립보드 복사 완료 상태 값 → 라벨 전환 21.30 73.7 8/23

03 은 밑줄 하나만 그리는 화면이라 누적면적이 9종 중 가장 작은 0.95%이고, 05 는 카드 전체가 새로 나타나 21.37%로 가장 큽니다 — 둘 다 밑줄과 카드라는 대상 자체의 크기 차이일 뿐, 계산이 덜 된 게 아닙니다. 강도 평균은 07 이 137.1 로 가장 높아 진행 막대가 배경과 또렷하게 갈리는 픽셀 값을 만든다는 뜻이고, 01 은 34.3 으로 가장 낮아 반투명 알림 행의 인접 색 차이가 완만하다는 뜻입니다. 07 이 19/23 으로 가장 자주 움직인 것과 달리 03·05·09 세 항목이 8/23 에 머무는 건 값이 바뀌는 순간에만 프레임이 갱신되고 그 사이는 정지해 있는 구조라 그런 것이지, 계산이 도중에 끊긴 게 아닙니다.

클릭 한 번이 값을 뒤집는 축 — 01·08·09

이 셋은 클릭 이벤트 하나가 상태 값을 바로 바꿉니다. 01 은 add() 가 새 알림을 밀어 넣을 때마다 items 배열의 순서로 dismissAfter(기본 시간 + 순서 × STACK_STEP_MS)를 계산하고, MAX_ROWS(3)를 넘으면 제일 오래된 항목을 즉시 remove()합니다. 08 은 클릭마다 dark 값을 뒤집어 localStorage에 적고 트랙·놉의 실측 사각형 차이로 이동 거리(--x)를 다시 구하며, resize 리스너도 같은 place()를 다시 불러 값을 갱신합니다. 09 는 줄 번호 버튼을 두 번 눌러야 범위가 정해지는데, 첫 클릭은 anchor를 저장만 하고 두 번째 클릭에서 range()가 시작·끝 줄 값의 차이로 sweep 위치를 계산합니다.

키보드가 범위·위치 값을 다시 세는 축 — 02·03·06

이 셋은 클릭으로 상태를 연 뒤 키다운이 실제 계산을 이어받습니다. 02 는 모달이 열릴 때 measure()가 [data-f] 요소 개수를 다시 재고, Tab·Shift+Tab마다 setIndex()가 그 범위 안에서 모듈러 연산으로 순환합니다. 03 은 방향키로 활성 탭이 바뀔 때마다 place()가 getBoundingClientRect()로 그 탭의 실제 위치·폭을 재서 밑줄의 --x·--w 값으로 씁니다. 06 은 combo()가 눌린 키에서 ctrl+s 같은 조합 값을 다시 구해 등록된 ACTIONS 목록과 비교하고, 일치하면 trigger()가 해당 힌트를 강조합니다.

옵저버·입력이 값을 계속 흘리는 축 — 04·05·07

이 셋은 클릭 한 번이 아니라 스크롤·타이핑이 이어지는 동안 값이 계속 다시 계산됩니다. 04 는 IntersectionObserver가 제목마다 넘겨주는 intersectionRatio 값을 배열에 채워 두고, 그중 가장 큰 값을 가진 항목을 매번 활성으로 고릅니다. 05 는 같은 API 로 가시 비율이 임계값(0.4)을 넘는 순간 한 번만 revealed 값을 뒤집고 io.unobserve()로 관찰 자체를 끊어 재실행을 막습니다. 07 은 입력마다 startedAt을 갱신해 WAIT_MS(480ms) 대기를 처음부터 다시 세고, 그사이 requestAnimationFrame이 매 프레임 경과 비율(--r)을 다시 계산해 진행 막대를 채웁니다.

패턴별 코드

01토스트 알림 큐 — 쌓인 순서 값으로 사라지는 시점을 계산하는 알림 목록

여러 알림이 동시에 쌓인 상태가 되면 각자의 표시 시간에 쌓인 순서 값을 더해 사라지는 시점을 다시 계산하고, 하나가 먼저 사라지면 뒤에 놓인 알림들의 위치 값도 그만큼 당겨집니다. 폼 제출·저장 완료 알림, 여러 작업이 겹치는 대시보드에 붙이기 좋은 형태입니다.

쌓인 순서 값이 사라질 때마다 다시 매겨지는 계산

  // 계산 겹: 표시 시간 = 기본 시간 + 쌓인 순서 값 × 간격. 하나가 사라지면 남은 항목의 순서 값을
  // 다시 매겨 위치(translateY)를 그 값으로 다시 계산한다 — 단순 reflow 가 아니라 순서를 다시 센다.
  function reindex() {
    items.forEach(function (item, i) {
      item.el.style.setProperty('--i', String(i));
    });
  }

  function remove(item) {
    var i = items.indexOf(item);
    if (i === -1) { return; }
    items.splice(i, 1);
    item.el.classList.add('tq__row--out');
    clearTimeout(item.timer);
    setTimeout(function () { item.el.remove(); }, 240);
    reindex();
  }
토스트 알림 큐 — 쌓인 순서 값으로 사라지는 시점을 계산하는 알림 목록
새 창에서 열기

01 · 토스트 알림 큐 — 쌓인 순서 값으로 사라지는 시점을 계산하는 알림 목록 — 측정 누적면적 19.387% · 움직인 프레임 10/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

STACK_STEP_MSMAX_ROWS--i

02다이얼로그 포커스 트랩 — 열린 상태에서 이동 범위 값을 안쪽으로 가두는 모달

모달이 열린 상태가 되면 탭 이동이 가능한 요소 목록을 다시 구해 그 범위 값 안에서만 순서가 돌게 하고, 마지막 요소에서 이동한 상태가 되면 위치 값이 첫 요소로 되돌아갑니다. 확인 모달, 결제·로그인 팝업에서 포커스가 모달 밖으로 새는 것을 막는 용도입니다.

열릴 때마다 이동 범위 값을 다시 재는 계산

  function measure() {
    focusables = Array.prototype.slice.call(dialog.querySelectorAll('[data-f]'));
  }

  function setIndex(i) {
    var len = focusables.length;
    index = ((i % len) + len) % len;
    marker.style.setProperty('--i', String(index));
    focusables[index].focus({ preventScroll: true });
  }
다이얼로그 포커스 트랩 — 열린 상태에서 이동 범위 값을 안쪽으로 가두는 모달
새 창에서 열기

02 · 다이얼로그 포커스 트랩 — 열린 상태에서 이동 범위 값을 안쪽으로 가두는 모달 — 측정 누적면적 20.114% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

[data-f]preventScroll--i

03키보드 탭 — 방향키로 바뀐 활성 값만큼 밑줄 위치를 다시 구하는 목록

방향키로 활성 탭이 바뀐 상태가 되면 그 탭의 가로 위치와 폭 값을 다시 구해 밑줄이 그 값까지 이동하고, 화면 밖 탭은 목록이 그 값만큼 옆으로 밀려 보이게 됩니다. 설정 화면의 카테고리 탭, 대시보드 상단 탭 메뉴에 쓰는 형태입니다.

실제 위치·폭을 재서 밑줄 이동 값으로 쓰는 계산

  function place(i) {
    var tab = tabs[i];
    var containerRect = tabsEl.getBoundingClientRect();
    var rect = tab.getBoundingClientRect();
    var x = rect.left - containerRect.left + tabsEl.scrollLeft;
    var w = rect.width;
    underline.style.setProperty('--x', x.toFixed(1) + 'px');
    underline.style.setProperty('--w', w.toFixed(1) + 'px');
    var left = x;
    var right = x + w;
    if (left < tabsEl.scrollLeft) { tabsEl.scrollLeft = left; }
    else if (right > tabsEl.scrollLeft + tabsEl.clientWidth) { tabsEl.scrollLeft = right - tabsEl.clientWidth; }
  }
키보드 탭 — 방향키로 바뀐 활성 값만큼 밑줄 위치를 다시 구하는 목록
새 창에서 열기

03 · 키보드 탭 — 방향키로 바뀐 활성 값만큼 밑줄 위치를 다시 구하는 목록 — 측정 누적면적 0.953% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

getBoundingClientRect--x--w

04스크롤 스파이 목차 — 겹침 비율 값을 비교해 활성 항목을 바꾸는 목차

페이지를 스크롤한 상태가 되면 각 제목이 뷰포트와 겹치는 비율 값을 다시 구해 가장 큰 값을 가진 항목을 활성으로 바꾸고, 목차의 강조 표시 위치도 그 항목의 위치 값까지 옮겨갑니다. 긴 문서의 사이드 목차, 기술 문서 페이지에서 지금 읽는 위치를 알려주는 용도입니다.

겹침 비율 값을 프레임마다 비교해 최댓값을 고르는 계산

  var io = new IntersectionObserver(function (entries) {
    entries.forEach(function (entry) {
      var i = Number(entry.target.getAttribute('data-h'));
      ratios[i] = entry.intersectionRatio;
    });
    var best = 0;
    for (var k = 1; k < ratios.length; k++) { if (ratios[k] > ratios[best]) { best = k; } }
    if (ratios[best] > 0) { activate(best); }
  }, { root: pane, threshold: [0, .25, .5, .75, 1] });
  sections.forEach(function (s) { io.observe(s); });
스크롤 스파이 목차 — 겹침 비율 값을 비교해 활성 항목을 바꾸는 목차
새 창에서 열기

04 · 스크롤 스파이 목차 — 겹침 비율 값을 비교해 활성 항목을 바꾸는 목차 — 측정 누적면적 2.205% · 움직인 프레임 10/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

IntersectionObserverintersectionRatio--i

05인터섹션 리빌 — 진입 비율 값이 기준을 넘는 순간 한 번 나타나는 요소

요소가 화면에 보이는 비율 값이 기준을 넘은 상태가 되면 나타나는 계산이 한 번 실행되고, 그 값을 지나 다시 화면 밖으로 나가도 상태 값은 나타난 채로 남아 다시 실행되지 않습니다. 소개 페이지의 문단별 등장, 포트폴리오 섹션 진입 연출에 쓰는 형태입니다.

가시 비율 값이 기준을 넘는 순간 한 번만 실행하는 계산

  var io = new IntersectionObserver(function (entries) {
    var entry = entries[0];
    if (!revealed && entry.intersectionRatio >= 0.4) {
      revealed = true;
      card.classList.add('ir__card--in');
      io.unobserve(card);
    }
  }, { root: pane, threshold: [0, .4, .8] });
  io.observe(card);
인터섹션 리빌 — 진입 비율 값이 기준을 넘는 순간 한 번 나타나는 요소
새 창에서 열기

05 · 인터섹션 리빌 — 진입 비율 값이 기준을 넘는 순간 한 번 나타나는 요소 — 측정 누적면적 21.367% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

IntersectionObserverunobserveintersectionRatio

06키보드 단축키 — 입력 조합 값을 판별해 동작을 찾는 안내

키 입력이 조합된 상태가 되면 그 조합 값을 다시 구해 등록된 목록과 비교하고, 일치하는 값이 있으면 그 동작이 실행되며 화면 한쪽의 안내 목록에서 같은 항목이 잠깐 강조됩니다. 생산성 도구의 명령 단축키, 에디터 화면에서 흔히 보는 형태입니다.

조합 값을 등록 목록과 매칭해 강조를 켜는 계산

  function combo(e) {
    var mod = e.ctrlKey || e.metaKey ? 'ctrl+' : '';
    return mod + String(e.key).toLowerCase();
  }

  function trigger(key) {
    var hit = hints.filter(function (h) { return h.getAttribute('data-combo') === key; })[0];
    if (!hit) { return; }
    hints.forEach(function (h) { h.classList.remove('ks__hint--hit'); });
    clearTimeout(flashTimer);
    clearTimeout(resetTimer);
    void hit.offsetWidth;
    hit.classList.add('ks__hint--hit');
    result.textContent = ACTIONS[key];
    resetTimer = setTimeout(function () {
      hit.classList.remove('ks__hint--hit');
      result.textContent = '대기 중';
    }, 900);
  }
키보드 단축키 — 입력 조합 값을 판별해 동작을 찾는 안내
새 창에서 열기

06 · 키보드 단축키 — 입력 조합 값을 판별해 동작을 찾는 안내 — 측정 누적면적 20.504% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

ctrlKeydata-comboaria-live

07디바운스 검색 — 마지막 입력 값 뒤 대기 시간이 지나야 실행되는 검색

입력 값이 바뀐 상태가 될 때마다 대기 시간을 재는 값을 처음부터 다시 시작하고, 그 값이 끝까지 줄어들 때까지 더 이상 입력이 없는 상태가 유지되면 그제서야 검색이 실행됩니다. 자동완성 검색창, 실시간 필터 목록에서 요청 수를 줄이는 표준적인 방식입니다.

대기 시간 값을 입력마다 처음부터 다시 재는 계산

  function onInput(value) {
    input.value = value;
    startedAt = Date.now();
    bar.style.setProperty('--r', '0');
    if (raf) { cancelAnimationFrame(raf); }
    raf = requestAnimationFrame(tick);
    clearTimeout(timer);
    timer = setTimeout(function () { search(value); }, WAIT_MS);
  }
디바운스 검색 — 마지막 입력 값 뒤 대기 시간이 지나야 실행되는 검색
새 창에서 열기

07 · 디바운스 검색 — 마지막 입력 값 뒤 대기 시간이 지나야 실행되는 검색 — 측정 누적면적 20.589% · 움직인 프레임 19/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

WAIT_MSrequestAnimationFrame--r

08테마 저장 — 저장된 상태 값을 읽어 초기 테마를 정하는 토글

테마가 바뀐 상태가 되면 그 값이 저장되고, 페이지를 다시 연 상태가 되면 저장된 값을 읽어 화면이 그려지기 전에 초기 테마를 그 값으로 맞춰 둡니다. 다크모드 전환 스위치, 여러 페이지를 오가는 서비스에 쓰는 형태입니다.

실측 이동 거리와 저장된 상태 값을 함께 쓰는 계산

  function apply(next, persist) {
    dark = next;
    track.setAttribute('aria-pressed', String(dark));
    card.setAttribute('data-theme', dark ? 'dark' : 'light');
    valueEl.textContent = dark ? '어둡게' : '밝게';
    place();
    if (persist) {
      try { window.localStorage.setItem(KEY, dark ? 'dark' : 'light'); } catch (err) { /* 무시 */ }
    }
  }
테마 저장 — 저장된 상태 값을 읽어 초기 테마를 정하는 토글
새 창에서 열기

08 · 테마 저장 — 저장된 상태 값을 읽어 초기 테마를 정하는 토글 — 측정 누적면적 6.717% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

localStoragegetBoundingClientRect--x

09클립보드 복사 — 성공 상태 값에 따라 라벨이 잠시 바뀌는 기본 버튼

복사가 끝난 상태가 되면 라벨이 완료 표시로 바뀌는 계산이 실행되고, 일정 시간이 지나 그 값이 초기화되면 원래 라벨로 되돌아갑니다. 코드 블록 옆 복사 버튼, 공유 링크 복사에 쓰는 형태이며, 이 데모는 줄 번호 두 개를 눌러 범위를 고르는 변형입니다.

줄 번호 값의 차이로 범위를 다시 구하는 계산

  function select(startN, endN) {
    var r = range(startN, endN);
    sweep.style.setProperty('--top', r.top + 'px');
    sweep.style.setProperty('--h', r.height + 'px');
    sweep.classList.add('cc__sweep--on');
    nums.forEach(function (btn) {
      var n = Number(btn.getAttribute('data-n'));
      btn.classList.toggle('cc__num--sel', n >= r.lo && n <= r.hi);
    });
    label.textContent = r.lo + '-' + r.hi + '줄 복사됨';
    label.classList.add('cc__label--done');
    clearTimeout(resetTimer);
    resetTimer = setTimeout(function () {
      label.textContent = '범위를 선택하면 복사됩니다';
      label.classList.remove('cc__label--done');
    }, 900);
  }
클립보드 복사 — 성공 상태 값에 따라 라벨이 잠시 바뀌는 기본 버튼
새 창에서 열기

09 · 클립보드 복사 — 성공 상태 값에 따라 라벨이 잠시 바뀌는 기본 버튼 — 측정 누적면적 21.3% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

--top--hdata-n

값이 패턴을 바꾸는 곳 — 순서 값·비율 값·대기 시간 값

이 아홉 개는 결국 값 하나가 어떻게 흘러 들어오는지로 나뉩니다. 앞서 다룬 마이크로 인터랙션 예시 아홉 개를 값의 성격을 기준으로 다시 묶으면 아래 세 갈래가 됩니다.

순서 값 — 클릭 한 번이 다음 계산의 재료가 되는 곳

01 은 알림이 쌓인 순서(items.indexOf(item))에 간격을 곱해 사라지는 시점을 구하고, 02 는 열릴 때마다 다시 잰 포커스 요소 개수를 범위로 삼아 그 안에서 순서가 돈다. 03 은 활성 탭의 인덱스가 바뀔 때마다 그 탭의 실제 위치 값을 다시 구해 밑줄을 옮긴다는 점에서, 셋 다 "지금 몇 번째인가"가 다음 계산의 재료입니다.

비율 값 — 옵저버가 흘리는 값을 비교하는 곳

04 는 제목마다 들어오는 intersectionRatio 값을 배열에 채워 두고 그중 최댓값을 가진 항목을 활성으로 고르며, 05 는 같은 값이 임계값(0.4)을 한 번 넘는 순간만 계산을 실행하고 이후에는 값이 다시 낮아져도 재실행하지 않습니다. 둘 다 스크롤이라는 연속 동작에서 나오는 비율 값을 계산의 재료로 씁니다.

대기 시간 값 — 타이머가 처음부터 다시 세는 곳

07 은 입력마다 대기 시간 값을 0부터 다시 세고, 06 은 조합이 맞아떨어진 뒤 강조를 되돌리는 데 900ms 타이머를 쓰며, 09 도 복사 라벨을 되돌리는 데 같은 900ms 타이머를 씁니다. 08 은 이 셋과 달리 시간이 아니라 localStorage에 남은 값을 다음 로드 때 그대로 읽어 초기 상태를 정한다는 점에서, 값이 "언제" 대신 "어디에" 남는지가 다른 축입니다.

어디서 깨지는가 — 빠른 연속 입력·포커스 이동·저사양 기기

빠른 연속 입력 — 값이 채 자리 잡기 전에 다시 들어오는 지점

01 은 MAX_ROWS(3)를 넘는 순간 제일 오래된 알림을 즉시 remove()하는데, 클릭으로 연속해서 알림을 추가하면 방금 나타난 알림이 채 다 보이기도 전에 사라지는 순서 값 충돌이 생길 수 있습니다. 07 은 WAIT_MS(480ms)보다 빠르게 계속 타이핑하면 대기 시간 값이 매번 0으로 리셋돼 검색이 한 번도 실행되지 않는 상태가 이어지는데, 사용자는 이걸 검색이 멈춘 오류로 오인하기 쉽습니다.

포커스 이동 — 값을 다시 재는 시점을 놓치는 지점

02 는 모달이 열리는 순간에만 measure()로 [data-f] 개수를 다시 재기 때문에, ntw2bmzv 안 원본에서 모달이 열린 뒤 조건부로 필드를 추가하는 화면에 이 코드를 붙인다면 open() 뒤에도 measure()를 다시 불러 범위 값을 갱신해야 새로 생긴 요소가 순환에서 빠지지 않습니다. 03 은 좁은 컨테이너에서 방향키를 연달아 누르면 tabsEl.scrollLeft를 옮기는 스크롤과 밑줄의 --x·--w 트랜지션이 서로 다른 시점에 끝나 잠깐 어긋나 보일 수 있습니다.

저사양 기기 — 매 호출마다 다시 재는 비용이 드는 지점

03 은 방향키를 누를 때마다 getBoundingClientRect()를 두 번(탭·컨테이너) 호출해 레이아웃 값을 강제로 다시 재는데, 저사양 기기에서 탭 수가 많아지면 이 강제 리플로우 비용이 다른 여덟 항목보다 먼저 프레임을 떨어뜨릴 수 있습니다. 04 는 IntersectionObserver 콜백 안에서 배열 전체를 매번 순회해 최댓값을 다시 찾는데, 목차 항목이 아주 많아지면 이 비교 비용도 같이 늘어납니다.

변형 세 가지와 접근성

변형 바꾸는 값 바뀌는 느낌
디바운스 검색 대기 시간 07 WAIT_MS 480ms → 900ms 값을 늘리면 계속 입력하는 동안 검색이 거의 실행되지 않아 요청 수는 줄지만 입력을 멈춘 뒤 결과가 뜨기까지 체감 지연이 커지고, 줄이면 반응은 빠르지만 타이핑 중간중간에도 검색이 자주 실행됩니다
토스트 쌓임 간격 01 STACK_STEP_MS 900ms → 1500ms 간격을 늘리면 알림이 쌓일수록 뒤쪽 알림이 훨씬 오래 남아 화면에 여러 개가 동시에 떠 있는 시간이 늘고, 줄이면 먼저 쌓인 알림부터 빨리 정리돼 큐가 짧게 유지됩니다
탭 밑줄 이동 시간 03 $duration $dur-enter(600ms) → $dur-loop(2s) 값을 늘리면 밑줄이 도착하기까지 한참 걸려 탭을 빠르게 여러 번 누르면 이전 위치에서 계속 뒤처져 보이고, 줄이면 거의 즉시 따라붙어 반응이 빠르게 느껴집니다

접근성

01 의 목록은 aria-live="polite"로 새 알림이 스크린 리더에 조용히 읽히게 했고, 02 는 role="dialog"·aria-modal="true"로 모달임을 알리며 Escape로도 닫을 수 있습니다. 03 은 role="tablist"·aria-selected·tabindex 조합으로 방향키 탐색을, 04 는 aria-current로 지금 활성 항목을, 06 은 aria-live="polite" 결과 문구를, 08 은 aria-pressed로 토글 상태를 각각 스크린 리더에 전달합니다. 아홉 개 모두 @media (prefers-reduced-motion: reduce)에서 트랜지션을 껐고, 이 값이 켜진 상태가 되면 05 처럼 등장 애니메이션 없이 최종 상태를 곧바로 보여줍니다.

FAQ

이 아홉 개는 실제 마이크로 인터랙션 사례를 그대로 옮긴 건가요?

아니요. 알림 문구·검색어 목록·단축키 조합은 전부 이 데모를 위해 만든 가상의 값이고, 실제 서비스나 진짜 API 키는 어디에도 없습니다. 09 는 navigator.clipboard를 호출하지 않고 라벨 전환으로만 복사 성공을 흉내 내므로, 실제 클립보드 복사가 필요하면 원본 코드에 그 호출을 직접 추가해야 합니다.

아홉 개 중 자동으로 재생되는 것과 손으로 눌러야 값이 바뀌는 것은 어떻게 구분하나요?

데모 화면은 관찰하기 쉽도록 아홉 개 전부 시드 고정 시퀀스로 자동 재생되지만, 계산 자체는 01 클릭·02 클릭+키다운·03 클릭+키다운·04 옵저버+클릭·05 옵저버+클릭·06 키다운·07 입력·08 클릭+리사이즈·09 클릭까지 아홉 개 전부 실제 사용자 입력 리스너로도 똑같이 동작합니다. 화면을 직접 클릭하거나 입력창에 타이핑하면 자동 재생이 멈추고 그 입력이 값을 계산합니다.

이 아홉 개를 한 페이지에 전부 붙여도 되나요?

계산 방식끼리 충돌하지는 않지만, 04·07 처럼 스크롤·입력마다 값을 다시 계산하는 항목을 여러 개 겹치면 저사양 기기에서 프레임이 함께 떨어질 수 있습니다. 실제 화면에는 그 자리에 필요한 만큼만 골라 붙이고, 03 처럼 getBoundingClientRect()를 자주 호출하는 항목은 특히 개수를 아껴 쓰는 편이 안전합니다.

더 읽을거리

이 아홉 개는 페이지 종류에 맞춰 파생된 섹션이 아니라 어떤 화면에도 붙일 수 있는 범용 UI 유틸리티라는 점에서, 아래 이웃 편들과는 계산이 붙는 자리 자체가 다릅니다.

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

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