05입력창 헤드셰이크
비밀번호 입력창에 perspective 를 준 부모 안에서, translateX 와 rotateY 를 함께 쓴 head-shake 키프레임으로 입력창이 고개를 젓듯 살짝 입체적으로 흔들립니다.
쓰임비밀번호 확인 불일치, 인증번호 오류
rotateYperspective900ms

<main class="stage">
<div class="hs is-demo">
<label class="hs__label" for="hs-pw">비밀번호 확인</label>
<input class="hs__input" id="hs-pw" type="password" value="wrongpass" readonly>
<p class="hs__msg">비밀번호가 일치하지 않습니다</p>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: 900ms; // 고개를 젓는 전체 길이
$easing: $ease-out;
$color: $error;
@include stage($stage-ink);
.hs { width: min(260px, 86vw); display: flex; flex-direction: column; gap: $sp-2; perspective: 500px; }
.hs__label { font-weight: 600; font-size: 12px; line-height: 1; font-family: inherit; color: $subject-cream; }
.hs__input {
box-sizing: border-box;
width: 100%;
padding: $sp-2 $sp-3;
border-radius: $r-sm;
border: 2px solid $color;
background: rgba(255, 255, 255, .06);
color: $subject-cream;
font-weight: 400;
font-size: 14px;
line-height: 1;
font-family: inherit;
}
.hs__msg { margin: 0; color: $error-soft; font-weight: 500; font-size: 11px; line-height: 1.3; font-family: inherit; }
@keyframes hs-shake {
0% { transform: translateX(0); }
6.5% { transform: translateX(-6px) rotateY(-9deg); }
18.5% { transform: translateX(5px) rotateY(7deg); }
31.5% { transform: translateX(-3px) rotateY(-5deg); }
43.5% { transform: translateX(2px) rotateY(3deg); }
50%, 100% { transform: translateX(0); }
}
// 실제 동작: 비밀번호 확인이 어긋나면 .hs--shake 가 한 번 붙는다
.hs__input.hs--shake { animation: hs-shake $duration $easing; }
// 데모 루프
.hs.is-demo .hs__input { animation: hs-shake $duration $easing infinite; }
@media (prefers-reduced-motion: reduce) {
.hs__input.hs--shake,
.hs.is-demo .hs__input { animation: none !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.hs {
width: min(260px, 86vw);
display: flex;
flex-direction: column;
gap: 8px;
perspective: 500px;
}
.hs__label {
font-weight: 600;
font-size: 12px;
line-height: 1;
font-family: inherit;
color: #fff7e6;
}
.hs__input {
box-sizing: border-box;
width: 100%;
padding: 8px 12px;
border-radius: 8px;
border: 2px solid #c2361a;
background: rgba(255, 255, 255, 0.06);
color: #fff7e6;
font-weight: 400;
font-size: 14px;
line-height: 1;
font-family: inherit;
}
.hs__msg {
margin: 0;
color: #ffe0d8;
font-weight: 500;
font-size: 11px;
line-height: 1.3;
font-family: inherit;
}
@keyframes hs-shake {
0% {
transform: translateX(0);
}
6.5% {
transform: translateX(-6px) rotateY(-9deg);
}
18.5% {
transform: translateX(5px) rotateY(7deg);
}
31.5% {
transform: translateX(-3px) rotateY(-5deg);
}
43.5% {
transform: translateX(2px) rotateY(3deg);
}
50%, 100% {
transform: translateX(0);
}
}
.hs__input.hs--shake {
animation: hs-shake 900ms ease-out;
}
.hs.is-demo .hs__input {
animation: hs-shake 900ms ease-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.hs__input.hs--shake,
.hs.is-demo .hs__input {
animation: none !important;
}
}import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./HeadShakeInput.module.scss";
export type HeadShakeInputProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function HeadShakeInput({ duration = 900, easing = "ease-out", color = "#c2361a" }: HeadShakeInputProps) {
const [run, setRun] = useState(0);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.hs} style={vars}>
<label className={styles.label} htmlFor="hs-pw">비밀번호 확인</label>
<input
key={run}
id="hs-pw"
className={`${styles.input} ${styles.shake}`}
type="password"
defaultValue="wrongpass"
readOnly
onFocus={() => setRun((n) => n + 1)}
/>
<p className={styles.msg}>비밀번호가 일치하지 않습니다</p>
</div>
);
}05 입력창 헤드셰이크 — 측정 누적면적 2.372% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · CSS 흔들리는 애니메이션 9종 — 폼 에러 표시
이 데모가 속한 글
CSS 흔들리는 애니메이션 9종 — 폼 에러 표시비밀번호를 잘못 입력했을 때 입력창이 좌우로 흔들리는 걸 본 적 있으신가요? 그 동작이 바로 css 흔들리는 애니메이션입니다 — 자바스크립트 없이 CSS 코드 몇 줄만으로 폼이 틀렸다는 걸 눈에 띄게 알려줍니다. 아 이 글의 데모 전부 보기


