햅틱 패턴

Haptic patterns

길이와 간격을 다르게 조합한 진동 시퀀스. 소리를 끄고도 손끝으로 상태를 알려줍니다.

다른 이름: Vibration patternsTaptic feedback
···
html
<div class="wrap">
  <div class="rows">
    <div class="row"><span class="lbl">tap</span><div class="seq" id="s-tap"><i class="on" style="--w:1"></i></div></div>
    <div class="row"><span class="lbl">success</span><div class="seq" id="s-ok"><i class="on" style="--w:1"></i><i class="off" style="--w:.6"></i><i class="on" style="--w:1"></i></div></div>
    <div class="row"><span class="lbl">warning</span><div class="seq" id="s-warn"><i class="on" style="--w:3"></i></div></div>
  </div>
  <button class="play" id="play" type="button" aria-label="play sound"><span class="tri"></span></button>
</div>
css
.wrap{position:relative;width:92%;height:84%;display:flex;flex-direction:column;justify-content:center;gap:14%}
.row{display:flex;align-items:center;gap:8px}
.lbl{width:22%;font:600 10px/1 monospace;color:var(--muted);text-align:right}
.seq{flex:1;display:flex;gap:3px;height:16px}
.seq i{height:100%;border-radius:3px;flex-grow:var(--w);background:var(--line)}
.seq i.on{background:var(--accent-3)}
.row.go .seq i.on{background:var(--accent)}
.row.go{animation:shake .3s}
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-2px)}75%{transform:translateX(2px)}}
.play{position:absolute;right:0;bottom:0;width:clamp(26px,14%,38px);aspect-ratio:1;border-radius:50%;border:1px solid var(--line);background:var(--surface);display:grid;place-items:center;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.12)}
@media (min-width:460px){
  .lbl{font-size:14px}
  .seq{height:26px}
  .seq i{border-radius:5px}
}
.play .tri{width:0;height:0;border-style:solid;border-width:6px 0 6px 9px;border-color:transparent transparent transparent var(--fg);margin-left:2px}
.play:active{transform:scale(.92)}
js
const rows = ['s-tap', 's-ok', 's-warn'].map((id) => document.getElementById(id).closest('.row'));
let idx = 0;
setInterval(() => {
  rows.forEach((r) => r.classList.remove('go'));
  rows[idx].classList.add('go');
  idx = (idx + 1) % rows.length;
}, 1000);
function thump(ctx, t, dur) {
  const osc = ctx.createOscillator();
  const gain = ctx.createGain();
  osc.type = 'sine';
  osc.frequency.value = 90;
  gain.gain.setValueAtTime(0, t);
  gain.gain.linearRampToValueAtTime(0.14, t + 0.01);
  gain.gain.exponentialRampToValueAtTime(0.0001, t + dur);
  osc.connect(gain).connect(ctx.destination);
  osc.start(t);
  osc.stop(t + dur + 0.02);
}
document.getElementById('play').addEventListener('click', () => {
  if (navigator.vibrate) { try { navigator.vibrate([60, 90, 60]); } catch (e) {} }
  const ctx = new (window.AudioContext || window.webkitAudioContext)();
  const now = ctx.currentTime;
  thump(ctx, now, 0.08);
  thump(ctx, now + 0.15, 0.08);
  setTimeout(() => ctx.close(), 500);
});

스마트폰의 리니어 액추에이터(iOS의 Taptic Engine 등)는 On/Off 스위치처럼 켜고 끄는 게 아니라, 짧고 강한 "틱" 한 번부터 길고 부드러운 "웅" 한 번까지 다양한 파형으로 진동을 만듭니다. 패턴은 이 펄스들을 시간축에 배열한 것으로, 웹에서는 navigator.vibrate([진동ms, 정지ms, 진동ms, ...]) 처럼 배열 하나로 표현할 수 있습니다.

짧은 펄스 1회는 "가벼운 확인"(탭 피드백), 2회 연속은 "완료·성공", 길게 한 번은 "경고·오류"처럼 의미를 길이·횟수로 구분합니다. 진동은 소리보다 사적이라(주변에 안 들림) 무음 모드에서도 살아남는 유일한 즉각 피드백이지만, 배터리를 많이 쓰고 기기를 손에 안 쥐고 있으면 무용지물이므로 소리·시각 신호와 반드시 함께 설계합니다. 같은 진동을 너무 자주 쓰면(연속 타이핑마다) 손끝이 둔감해지므로 정말 중요한 순간에만 아껴 씁니다.

키보드 타건 햅틱, 알림·메시지 수신, 결제 완료, 게임 컨트롤러의 충돌·발사 피드백에 쓰입니다.

언제 쓰나

무음 모드에서도 살아남아야 하는 중요한 순간에. 같은 패턴을 너무 자주 쓰면 무뎌지니 아껴 쓰세요.