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

지문 인식 애니메이션 9종 — 스캔 진행률 값으로 갈리는 인증 버튼

아홉 가지 지문 인식 애니메이션 중 하나는 스캔 진행률 값이 지문 능선을 순서대로 밝히고, 하나는 실패 시 스프링 감쇠 값으로 흔들리다 멈추며, 하나는 압력 값이 커질수록 리플 반경과 진폭 값이 함께 커집니다.

아홉 데모는 전부 실제 지문 센서나 WebAuthn API를 연결하지 않은 시뮬레이션이라, 버튼을 누르면 정해둔 스캔·성공·실패 흐름이 계산으로 재생될 뿐 실키나 실제 인증 요청은 오가지 않습니다. 화면은 <button> 하나에 지문 모양 생체 인증 아이콘을 얹고 click·keydown(Space·Enter) 이벤트로 사람의 조작을 받으며, 05는 여기에 pointerdown·pointermove·pointerup까지 더해 누르는 동안의 압력 값을 매 프레임 받습니다. 아홉 항목 모두 requestAnimationFrame 루프 안에서 값을 다시 계산해 그리고, css 트랜지션이나 키프레임에 값 결정을 맡기지 않습니다.

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

아홉 개의 해부 — 어떤 값이 인증 상태에 반응하는가

다음 표에는 svg·css 요소가 화면을 얼마나 채웠는지·움직임이 얼마나 강했는지·몇 프레임이나 값이 갱신됐는지 세 값이 항목별로 들어 있습니다.

항목 누적면적% 강도_평균 움직인프레임
01 12.57% 63.8 12/23
02 3.40% 36.1 19/23
03 5.36% 132.2 16/23
04 3.19% 158.6 15/23
05 3.58% 107.7 19/23
06 3.04% 38.8 22/23
07 1.80% 136.8 23/23
08 12.00% 76.7 20/23
09 3.99% 23.9 21/23

아홉 항목 중 누적면적이 가장 넓은 항목은 01 하나(스캔 라인이 위에서 아래로 전체 능선을 훑고 지나가 밝기 값이 넓게 바뀝니다)이고, 가장 좁은 항목은 07 하나(진행 링 자체가 얇은 선이라 채워지는 면적이 작습니다)이며, 강도가 가장 센 항목도 04 하나(path 점 10개가 매 프레임 한꺼번에 좌표를 바꿔 순간 변화량이 큽니다)뿐입니다. 강도가 가장 낮은 항목은 09(스케일 값이 1.0과 1.06 사이에서만 오가 변화폭이 작습니다)이고, 움직인프레임이 가장 많은 항목은 07(23/23 — 스프링이 두 목표값 사이를 쉬지 않고 오가 한 프레임도 멈추지 않습니다)이며 가장 적은 항목은 01(12/23 — 스캔·유지·복귀가 끝나면 GAP_MS 동안 값이 그대로 멈춰 있습니다)입니다.

아홉 항목을 값을 미는 계산 방식으로 다시 나누면 네 무리로 갈립니다.

01·02 — 경과 시간 하나를 함수에 넣어 값을 바로 구하는 것

01은 SWEEP_SPEED·BASE_MS로 정한 진행 시간(DUR) 안에서 스캔 라인 y좌표가 TOP에서 BOTTOM까지 선형으로 내려가고, 그 y좌표가 능선 정점(apex)을 지난 거리를 GLOW_BAND로 나눈 값이 그대로 능선 밝기가 됩니다. 02는 파동이 뜬 뒤 지난 시간(age)을 TAU로 나눈 지수 함수 PULSE_MAX_R×(1-e^(-age/TAU))로 반지름을 구합니다. 둘 다 목표를 뒤쫓는 스프링이 아니라 경과 시간 하나가 그 프레임의 값을 바로 정합니다.

03·07 — 감쇠·스프링 물리로 목표를 지나쳤다 되돌아오는 값

03은 decayMs(SHAKE_DAMP×40)를 지수 감쇠식 e^(-t/decayMs)와 cos(t×FREQ)에 곱해 아이콘 x좌표 오프셋을 구하고, 값이 SETTLE_MS 안에 0으로 잦아듭니다. 07은 목표와 현재 값의 차이에 STIFFNESS를 곱하고 속도에 DAMPING을 곱해 뺀 힘을 매 프레임 속도·위치에 더하는 스프링 적분이라, 목표를 살짝 지나쳤다가(오버슛) 되돌아옵니다.

05·06·09 — 사인 함수 합성이 흔들림 값을 만드는 것

05는 각도(θ)마다 반지름을 BASE_R + AMPLITUDE×pressure×sin(WAVE_K×θ - t×WAVE_SPEED)로 계산해, 누르는 압력 값이 클수록 물결의 진폭이 커집니다. 06은 FREQS 세 값으로 서로 주기가 다른 사인을 WEIGHTS 비율로 가중합한 값을 불투명도에 매핑해, Math.random 없이도 결정적으로 반짝입니다. 09는 sin(t×OMEGA) 값 하나로 아이콘 크기를 오가게 하고, 능선마다 RIDGE_PHASE_STEP만큼 위상을 어긋나게 둡니다.

04·08 — 좌표·지연을 직접 보간·매겨 자리를 정하는 값

04는 원 위의 점 10개와 체크마크 점 10개를 같은 순서로 맞춰 두고, 진행값 p(0~1)로 두 좌표를 lerp해 매 프레임 새 path d 문자열을 만듭니다. 08은 격자 셀마다 (행+열) 값에 DIAG_STEP을 곱한 지연을 매기고, 지연을 뺀 경과 시간을 CYCLE_MS로 나눈 나머지에 사인 함수를 걸어 셀 하나가 빛나는 삼각 펄스를 구합니다.

패턴별 코드

01스캔 라인 스윕 — 스캔 진행률 값이 위에서 아래로 내려가며 지나간 자리의 지문 능선 밝기 값이 순서대로 올라가는 방식

스캔 진행률에 따라 스캔 라인의 y좌표가 TOP(6)에서 BOTTOM(124)까지 BASE_MS/SWEEP_SPEED 시간 동안 선형으로 내려가고, 그 y좌표가 능선 정점(apex)을 지난 뒤 GLOW_BAND 거리 안에서 능선 밝기(act)가 0에서 1로 올라갑니다. 정점이 위쪽(작은 y)인 능선일수록 먼저 활성돼, 스캔이 끝나면 전체 능선이 순서대로 다 밝아집니다.

  function apply(scanY) {
    beam.setAttribute('y1', scanY.toFixed(2));
    beam.setAttribute('y2', scanY.toFixed(2));
    for (var i = 0; i < ridges.length; i++) {
      var apex = parseFloat(ridges[i].getAttribute('data-apex'));
      var act = Math.max(0, Math.min(1, (scanY - apex) / GLOW_BAND + 0.001));
      ridges[i].style.opacity = (0.26 + 0.74 * act).toFixed(3);
    }
  }
스캔 라인 스윕 — 스캔 진행률 값이 위에서 아래로 내려가며 지나간 자리의 지문 능선 밝기 값이 순서대로 올라가는 방식
새 창에서 열기

01 · 스캔 라인 스윕 — 스캔 진행률 값이 위에서 아래로 내려가며 지나간 자리의 지문 능선 밝기 값이 순서대로 올라가는 방식 — 측정 누적면적 12.571% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

BASE_MS$subject-blue-lightGLOW_BAND

02대기 파동 — 인증 대기 상태에서 원형 파동의 반경 값이 감쇠 함수로 점점 커지며 옅어지는 방식

대기 상태가 이어지는 동안 LAUNCH_GAP마다 새 파동 슬롯이 하나씩 열리고, 각 슬롯은 뜬 뒤 지난 시간(age)을 TAU로 나눈 지수 함수 PULSE_MAX_R×(1-e^(-age/TAU))로 반지름을 키웁니다. 반지름이 PULSE_MAX_R에 가까워질수록 불투명도는 반대로 옅어지다, age가 CUTOFF를 넘으면 슬롯이 반납됩니다.

  function tick(now) {
    for (var i = 0; i < slots.length; i++) {
      var slot = slots[i];
      if (slot.start === null) { continue; }
      var age = now - slot.start;
      if (age > CUTOFF) {
        slot.start = null;
        slot.el.style.opacity = '0';
        continue;
      }
      var radius = PULSE_MAX_R * (1 - Math.exp(-age / TAU));
      var opacity = Math.max(0, 1 - radius / PULSE_MAX_R);
      slot.el.setAttribute('r', radius.toFixed(2));
      slot.el.style.opacity = opacity.toFixed(3);
    }
  }
대기 파동 — 인증 대기 상태에서 원형 파동의 반경 값이 감쇠 함수로 점점 커지며 옅어지는 방식
새 창에서 열기

02 · 대기 파동 — 인증 대기 상태에서 원형 파동의 반경 값이 감쇠 함수로 점점 커지며 옅어지는 방식 — 측정 누적면적 3.397% · 움직인 프레임 19/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

LAUNCH_GAP$subject-blue-deepPULSE_MAX_R

03실패 진동 감쇠 — 인증 실패 시 아이콘이 스프링 감쇠 값을 따라 좌우로 흔들리다 값이 0에 가까워지며 멈추는 방식

인증이 실패하면 AMPLITUDE×e^(-t/decayMs)×cos(t×FREQ) 식이 매 프레임 아이콘의 x좌표 오프셋을 구해, decayMs(SHAKE_DAMP×40)가 클수록 진동이 오래 남고 작을수록 SETTLE_MS 전에 빨리 잦아듭니다. 값이 0에 가까워지면 흔들림이 자연스럽게 멈춥니다.

  function apply(t) {
    var offset = AMPLITUDE * Math.exp(-t / decayMs) * Math.cos(t * FREQ);
    icon.style.transform = 'translateX(' + offset.toFixed(2) + 'px)';
  }

  function runShake(ts) {
    if (!startTs) startTs = ts;
    var t = ts - startTs;
    if (t <= SETTLE_MS) {
      apply(t);
      requestAnimationFrame(runShake);
    } else {
      apply(0);
      status.textContent = STRINGS.idle;
      running = false;
      root.setAttribute('aria-pressed', 'false');
    }
  }
실패 진동 감쇠 — 인증 실패 시 아이콘이 스프링 감쇠 값을 따라 좌우로 흔들리다 값이 0에 가까워지며 멈추는 방식
새 창에서 열기

03 · 실패 진동 감쇠 — 인증 실패 시 아이콘이 스프링 감쇠 값을 따라 좌우로 흔들리다 값이 0에 가까워지며 멈추는 방식 — 측정 누적면적 5.362% · 움직인 프레임 16/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

SETTLE_MS$subject-creamSHAKE_DAMP

04성공 모핑 — 인증에 성공하면 지문 아이콘의 path 값이 체크마크 path 값으로 보간되며 매끈하게 바뀌는 방식

인증에 성공하면 N=10개로 맞춘 원 위 점과 체크마크 점(CHECK_PTS) 사이를 진행값 p(0→1)로 lerp해, MORPH_MS 시간 동안 매 프레임 새 path d 문자열을 만듭니다. p가 1에 도달하면 HOLD_MS만큼 체크 모양을 유지한 뒤 같은 시간 동안 역방향으로 되돌립니다.

  function buildPath(p) {
    var d = '';
    for (var i = 0; i < N; i++) {
      var c = circlePoint(i);
      var k = CHECK_PTS[i];
      var x = lerp(c[0], k[0], p);
      var y = lerp(c[1], k[1], p);
      d += (i === 0 ? 'M' : 'L') + x.toFixed(2) + ',' + y.toFixed(2) + ' ';
    }
    return d + 'Z';
  }
성공 모핑 — 인증에 성공하면 지문 아이콘의 path 값이 체크마크 path 값으로 보간되며 매끈하게 바뀌는 방식
새 창에서 열기

04 · 성공 모핑 — 인증에 성공하면 지문 아이콘의 path 값이 체크마크 path 값으로 보간되며 매끈하게 바뀌는 방식 — 측정 누적면적 3.191% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

MORPH_MS$subject-blue-deepCHECK_PTS

05압력 리플 — 스캔 표면을 누르는 압력 값이 커질수록 동심원 리플의 반경과 진폭 값이 함께 커지는 방식

스캔 표면을 누르면 pointerdown·pointermove가 받은 압력 값(e.pressure)이 리플 계산에 그대로 들어가, 각도(θ)마다 반지름을 BASE_R + AMPLITUDE×pressure×sin(WAVE_K×θ - t×WAVE_SPEED)로 구합니다. 손을 대지 않은 자동 재생에서는 PRESS_CYCLE_MS 주기의 사인 곡선이 압력 값을 대신 만들어 냅니다.

  function buildPath(t, pressure) {
    var d = '';
    for (var i = 0; i <= M; i++) {
      var theta = (i / M) * Math.PI * 2;
      var r = BASE_R + AMPLITUDE * pressure * Math.sin(WAVE_K * theta - t * WAVE_SPEED);
      var x = 60 + r * Math.cos(theta);
      var y = 60 + r * Math.sin(theta);
      d += (i === 0 ? 'M' : 'L') + x.toFixed(2) + ',' + y.toFixed(2) + ' ';
    }
    return d + 'Z';
  }
압력 리플 — 스캔 표면을 누르는 압력 값이 커질수록 동심원 리플의 반경과 진폭 값이 함께 커지는 방식
새 창에서 열기

05 · 압력 리플 — 스캔 표면을 누르는 압력 값이 커질수록 동심원 리플의 반경과 진폭 값이 함께 커지는 방식 — 측정 누적면적 3.583% · 움직인 프레임 19/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

PRESS_CYCLE_MS$subject-blue-lightWAVE_K

06홀로그램 플리커 — 2D 노이즈 값을 기반으로 스캔 아이콘의 깜빡임 강도 값이 미세하게 흔들리는 방식

FREQS 세 값으로 서로 다른 주기를 가진 사인 함수를 WEIGHTS 비율로 가중합한 값(noise)이 아이콘과 스캔 라인 4개의 불투명도를 정해, Math.random 없이도 결정적으로 미세하게 흔들립니다. 스캔 라인마다 LINE_PHASE_STEP만큼 위상이 달라 같은 순간에도 줄마다 다른 밝기로 반짝입니다.

  function noise(t, phaseOffset) {
    var v = 0;
    for (var i = 0; i < 3; i++) {
      v += WEIGHTS[i] * Math.sin(t * FREQS[i] + PHASES[i] + phaseOffset);
    }
    return v / WSUM;
  }
홀로그램 플리커 — 2D 노이즈 값을 기반으로 스캔 아이콘의 깜빡임 강도 값이 미세하게 흔들리는 방식
새 창에서 열기

06 · 홀로그램 플리커 — 2D 노이즈 값을 기반으로 스캔 아이콘의 깜빡임 강도 값이 미세하게 흔들리는 방식 — 측정 누적면적 3.044% · 움직인 프레임 22/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

LINE_PHASE_STEP$subject-skyFREQS

07링 오버슛 — 원형 진행 링이 각도 값으로 채워지다 처리가 끝나는 순간 관성 값만큼 살짝 지나쳤다 되돌아오는 방식

일치율 링은 목표 값과 현재 값의 차이에 STIFFNESS를 곱하고 속도에 DAMPING을 곱해 뺀 힘을 매 프레임 속도·위치에 더하는 스프링 적분으로 채워져, 목표를 살짝 지나쳤다가 되돌아오는 오버슛이 생깁니다. 자동 재생은 FLIP_MS(정착 시간보다 짧은 간격)마다 목표를 AUTO_HIGH·AUTO_LOW 사이로 계속 바꿔, 스프링이 한 번도 완전히 멈추지 않습니다.

  function runManual(ts) {
    if (!manualLastTs) manualLastTs = ts;
    var dt = Math.min(ts - manualLastTs, 32);
    manualLastTs = ts;
    var force = (TARGET - manualPos) * STIFFNESS - manualVel * DAMPING;
    manualVel += force * dt;
    manualPos += manualVel * dt;
    paint(manualPos);
    if (Math.abs(manualVel) > SETTLE_EPS || Math.abs(TARGET - manualPos) > SETTLE_EPS) {
      status.textContent = STRINGS.running;
      requestAnimationFrame(runManual);
    } else {
      manualPos = TARGET;
      manualVel = 0;
      paint(manualPos);
      status.textContent = STRINGS.done;
      manualRunning = false;
      root.setAttribute('aria-pressed', 'false');
    }
  }
링 오버슛 — 원형 진행 링이 각도 값으로 채워지다 처리가 끝나는 순간 관성 값만큼 살짝 지나쳤다 되돌아오는 방식
새 창에서 열기

07 · 링 오버슛 — 원형 진행 링이 각도 값으로 채워지다 처리가 끝나는 순간 관성 값만큼 살짝 지나쳤다 되돌아오는 방식 — 측정 누적면적 1.799% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

FLIP_MS$subject-blue-deepSTIFFNESS

08격자 스캔 글로우 — 스캔 영역을 격자로 나눠 셀마다 다른 지연 값으로 순서대로 빛나는 방식

6×6 격자 셀마다 (행+열) 값에 DIAG_STEP을 곱한 지연이 배정되고, 지연을 뺀 경과 시간을 CYCLE_MS로 나눈 나머지에 사인 함수(pulse)를 걸어 셀 하나가 PULSE_MS 동안 밝아지는 삼각 펄스를 구합니다. 왼쪽 위 셀일수록 지연이 짧아 먼저 밝아지므로, 빛이 대각선 방향으로 번져 나가는 것처럼 보입니다.

  function render(t) {
    for (var i = 0; i < cells.length; i++) {
      var delay = cells[i].diag * DIAG_STEP;
      var local = ((t - delay) % CYCLE_MS + CYCLE_MS) % CYCLE_MS;
      var v = pulse(local);
      cells[i].el.style.opacity = (0.16 + v * 0.84).toFixed(3);
      cells[i].el.style.transform = 'scale(' + (1 + v * 0.22).toFixed(3) + ')';
    }
  }
격자 스캔 글로우 — 스캔 영역을 격자로 나눠 셀마다 다른 지연 값으로 순서대로 빛나는 방식
새 창에서 열기

08 · 격자 스캔 글로우 — 스캔 영역을 격자로 나눠 셀마다 다른 지연 값으로 순서대로 빛나는 방식 — 측정 누적면적 12.0% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

DIAG_STEP$subject-mintCYCLE_MS

09대기 호흡 — 입력이 없는 동안 아이콘 크기 값이 사인 이징 곡선을 따라 커졌다 작아졌다 반복하는 방식

입력이 없는 동안 아이콘 전체 크기가 1 + AMPLITUDE×sin(t×OMEGA) 식을 따라 1.0과 1.06 사이를 오가고, 능선마다 t×OMEGA + i×RIDGE_PHASE_STEP 위상을 따로 둬 밝기가 안에서 바깥으로 번지는 파도처럼 보입니다. 사인 값 하나만으로 크기와 밝기를 동시에 계산해, 조작이 없어도 화면이 완전히 멈춰 보이지 않습니다.

  function render(t) {
    var s = 1 + AMPLITUDE * Math.sin(t * OMEGA);
    icon.style.transform = 'scale(' + s.toFixed(4) + ')';
    for (var i = 0; i < ridges.length; i++) {
      var phase = t * OMEGA + i * RIDGE_PHASE_STEP;
      var b = 0.5 + 0.5 * Math.sin(phase);
      ridges[i].style.opacity = b.toFixed(3);
    }
  }
대기 호흡 — 입력이 없는 동안 아이콘 크기 값이 사인 이징 곡선을 따라 커졌다 작아졌다 반복하는 방식
새 창에서 열기

09 · 대기 호흡 — 입력이 없는 동안 아이콘 크기 값이 사인 이징 곡선을 따라 커졌다 작아졌다 반복하는 방식 — 측정 누적면적 3.988% · 움직인 프레임 21/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

RIDGE_PHASE_STEP$subject-lilacOMEGA

값이 흔들림과 밝기를 바꾸는 곳 — 진행률 값·압력 값·감쇠 값 기준

아홉 항목을 값 기준으로 다시 겹쳐 보면 세 무리로 갈립니다. 진행률 값이 기준인 쪽은 01(스캔 y좌표)·04(모프 진행값 p)·08(대각 지연을 지난 경과 비율)처럼, 진행이 얼마나 됐는지 자체가 그 프레임의 밝기나 좌표를 직접 정합니다. 압력 값이 기준인 쪽은 05 하나로, 로그인 화면에 지문인식 버튼 하나만 올려도 되는 자리라면 누르는 세기가 곧 물결의 반경과 진폭이 되는 이 방식이 어울립니다. 감쇠 값이 기준인 쪽은 02(지수 감쇠 성장)·03(감쇠 진동)·07(스프링 오버슛)처럼, 목표나 나이(age)와의 차이가 시간에 따라 줄어드는 계산이 공통입니다.

06과 09는 이 세 갈래에 들지 않고 각자 다른 값을 씁니다. 06은 사인 세 개를 가중합한 노이즈 값이, 09는 사인 이징 하나가 밝기와 크기를 결정합니다.

이 편의 아홉 항목은 Web Animations API 대신 requestAnimationFrame 루프 안에서 좌표·불투명도 값을 직접 계산해 그립니다. 07의 진행 링은 stroke-dashoffset 값을 스프링 계산 결과로 매겨 채워지는 정도를 나타냅니다.

어디서 깨지는가 — 빠른 재시도, 저사양 기기, reduced-motion

01·03·04는 애니메이션이 도는 동안 running 값이 true라 트리거 함수가 곧바로 return합니다. 그래서 스캔이나 실패 흔들림이 아직 안 끝났는데 버튼을 빠르게 다시 눌러도 재시도는 무시되고, 진행 중이던 값 계산이 끝날 때까지 기다려야 다음 조작이 먹힙니다.

아홉 데모를 한 화면에 전부 재생하면 rAF 루프 아홉 개가 동시에 도는데, 그중 09는 능선 7개마다 매 프레임 사인 함수를 다시 계산하고 06은 아이콘과 줄 4개 각각에 사인 세 개를 가중합한 노이즈 값을 다시 구합니다. 저사양 기기에서는 이 반복 계산이 겹치며 프레임이 밀릴 수 있어, 실제로 붙일 때는 한 화면에 필요한 항목 하나만 남기는 편이 낫습니다.

05·06은 reduce 값을 isDemo 상태와 함께 볼 때만 확인합니다. 자동 재생 단계에서는 reduce가 켜지면 압력 값이나 노이즈 값이 0에 묶이지만, 사람이 한 번 눌러 isDemo가 꺼진 뒤에는 reduce 값을 다시 보지 않아 압력 리플(05)이나 노이즈 플리커(06)가 reduce가 켜져 있어도 실제 조작을 따라 계속 움직입니다.

버튼을 직접 눌러보며 상수 값을 하나씩 바꿔보고 싶다면, xysfva3x 압축 비밀번호로 아홉 항목의 html·scss·js 파일 전체를 받아 코드를 그대로 열어볼 수 있습니다.

이 편의 아홉 항목은 인증 상태 하나(스캔·성공·실패·대기)에 반응하는 버튼 마이크로 인터랙션이라, 회원가입부터 로그아웃까지 화면 전체 흐름을 다루는 로그인 UI 디자인 9종 — 회원가입부터 세션 만료까지와는 계산 단위가 다릅니다. 본인인증 화면 UI 9가지 — 그대로 복사하는 인증 카드는 화면 하나를 통째로 구성하는 카드 레이아웃을 다루는 반면, 이 편은 버튼 하나 안의 값 계산만 다룹니다. 실패를 반복했을 때 다음 시도까지 남은 시간을 좁혀가는 계산은 로그인 실패 횟수 제한 9종 — 값이 계산으로 좁혀지는 잠금 아홉 가지에서 다루고, 이 편의 03(실패 진동)은 그 잠금 전 단계에서 한 번의 실패를 즉시 보여주는 피드백만 맡습니다.

변형 세 가지와 접근성

변형 값 느낌
스캔 라인 속도 늦추기 01 SWEEP_SPEED 1.4 → 0.6 스윕 속도를 1.4→0.6 으로 낮추면 진행률 값이 천천히 올라가, 능선이 순서대로 밝아지는 흐름이 또렷하게 보인다.
실패 흔들림을 더 빨리 멈추기 03 SHAKE_DAMP 18 → 8 SHAKE_DAMP를 18→8로 낮추면 decayMs(=SHAKE_DAMP×40)가 720ms에서 320ms로 줄어 흔들림 값이 0으로 잦아드는 시간이 짧아져, 실패 진동이 한두 번 튀고 바로 멈춘다.
대기 파동 반경 키우기 02 PULSE_MAX_R 40 → 70 최대 반경 값을 40→70 으로 올리면 파동이 더 넓게 퍼진 뒤 옅어져, 대기 상태가 더 여유롭게 보인다.

아홉 항목 모두 <button>에 aria-pressed 값을 상태에 맞춰 true·false로 바꾸고, 안의 svg 아이콘은 aria-hidden으로 감춰 스크린 리더가 그림 대신 버튼 옆 상태 문구(status)를 읽게 합니다. 키보드는 Space·Enter가 곧 human() 뒤 상태 전환을 부르므로 마우스 없이도 전부 조작됩니다. reduced-motion에서는 01·03·04가 진행 시간(DUR·decayMs)을 1ms로 줄여 사실상 즉시 결과 상태로 넘어가고, 07은 목표 값을 곧바로 그려 스프링 계산 자체를 건너뛰며, 08·09는 자동 루프 대신 정해진 위상 하나로 고정된 그림을 한 번만 그립니다.

FAQ

로그인 화면에 이 지문 인식 애니메이션 아홉 개를 전부 넣어도 되나요?

지문 인식 버튼 자리 하나에는 이 중 한 방식만 골라 넣는 편이 낫습니다. 대기 상태를 보여줄 아이콘(01·02·09 중 하나)과 실패·성공 피드백(03·04) 정도만 조합해도 충분하고, 아홉 개를 동시에 넣으면 rAF 루프가 그만큼 겹쳐 불필요하게 무거워집니다.

생체 인증 ui 버튼에 이 코드를 그대로 붙여 써도 되나요?

네, 아홉 항목 모두 외부 라이브러리 없이 svg·css·js 값 계산만으로 동작해, zip 안 코드를 그대로 붙이면 버튼과 상태 문구까지 포함해 바로 움직입니다. 다만 이 코드는 화면 연출을 시뮬레이션한 것이라, 실제 지문 센서나 WebAuthn 인증 로직은 포함돼 있지 않습니다.

아홉 항목 중 계산이 가장 무거운 건 어떤 항목인가요?

09는 능선 7개마다 매 프레임 사인 함수를 다시 계산하고, 06은 아이콘과 줄 4개 각각에 사인 세 개를 가중합한 노이즈 값을 매번 다시 구해 계산량이 상대적으로 많습니다. 나머지 일곱 항목은 대부분 한두 번의 사칙연산으로 그 프레임 값을 구합니다.

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

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