09ロックオンボタン・スキャン入力欄
ボタンを押すと4つの角のブラケットが内側にスナップしてロックし、入力欄はフォーカスすると下線がスキャンするように広がる琥珀色のグローに変わります。
使いどころフォーム送信ボタン・メール入力欄
브래킷 스냅스캔 포커스 글로우깜빡이는 캐럿

<main class="stage">
<form class="formhud is-demo">
<button class="formhud__btn" type="button">
<span class="formhud__corner formhud__corner--tl" aria-hidden="true"></span>
<span class="formhud__corner formhud__corner--tr" aria-hidden="true"></span>
<span class="formhud__corner formhud__corner--bl" aria-hidden="true"></span>
<span class="formhud__corner formhud__corner--br" aria-hidden="true"></span>
ロックオン開始
</button>
<label class="formhud__field">
<input class="formhud__input" type="email" placeholder="メールアドレス" readonly>
<span class="formhud__caret" aria-hidden="true"></span>
</label>
</form>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 버튼 스냅 + 입력창 포커스 글로우 한 주기
$easing: $ease-pop; // 스냅이 튕기는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-ink);
.formhud {
display: flex;
flex-direction: column;
gap: $sp-4;
width: clamp(220px, 78vw, 300px);
}
.formhud__btn {
position: relative;
box-sizing: border-box;
border: 1px solid $color;
border-radius: $r-control;
background: rgba($color, .14);
color: $color;
padding: $sp-3 $sp-5;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: .02em;
animation: btnSnap $duration $easing infinite;
}
@keyframes btnSnap {
0%, 55%, 100% { transform: scale(1); }
75% { transform: scale(.97); }
}
.formhud__corner {
position: absolute;
width: 10px;
height: 10px;
pointer-events: none;
border-color: $color;
border-style: solid;
border-width: 0;
}
.formhud__corner--tl { top: $sp-1; left: $sp-1; border-top-width: 2px; border-left-width: 2px; animation: snapTl $duration $easing infinite; }
.formhud__corner--tr { top: $sp-1; right: $sp-1; border-top-width: 2px; border-right-width: 2px; animation: snapTr $duration $easing infinite; }
.formhud__corner--bl { bottom: $sp-1; left: $sp-1; border-bottom-width: 2px; border-left-width: 2px; animation: snapBl $duration $easing infinite; }
.formhud__corner--br { bottom: $sp-1; right: $sp-1; border-bottom-width: 2px; border-right-width: 2px; animation: snapBr $duration $easing infinite; }
@keyframes snapTl { 0%, 55%, 100% { transform: translate(0, 0); } 75% { transform: translate(3px, 3px); } }
@keyframes snapTr { 0%, 55%, 100% { transform: translate(0, 0); } 75% { transform: translate(-3px, 3px); } }
@keyframes snapBl { 0%, 55%, 100% { transform: translate(0, 0); } 75% { transform: translate(3px, -3px); } }
@keyframes snapBr { 0%, 55%, 100% { transform: translate(0, 0); } 75% { transform: translate(-3px, -3px); } }
.formhud__field {
position: relative;
display: block;
border-radius: $r-sm;
background: $subject-ink;
box-shadow: 0 1px 0 0 rgba($color, .4);
animation: focusGlow $duration $easing infinite;
}
@keyframes focusGlow {
0%, 55%, 100% { box-shadow: 0 1px 0 0 rgba($color, .4); }
75% { box-shadow: 0 1px 0 0 rgba($color, .9), 0 0 0 3px rgba($color, .25), 0 0 20px rgba($color, .35); }
}
.formhud__input {
box-sizing: border-box;
width: 100%;
border: 0;
background: transparent;
padding: $sp-3 $sp-4;
color: $subject-cream;
font-weight: 500;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
outline: none;
}
.formhud__input::placeholder {
color: rgba($subject-cream, .45);
}
.formhud__caret {
position: absolute;
left: $sp-4;
top: 50%;
width: 2px;
height: 14px;
transform: translateY(-50%);
background: $color;
animation: blink $dur-loop steps(1) infinite;
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: .2; }
}
@media (prefers-reduced-motion: reduce) {
.formhud__btn, .formhud__corner, .formhud__field, .formhud__caret {
animation: none !important;
}
.formhud__btn { transform: scale(1); }
.formhud__corner { transform: translate(0, 0); }
.formhud__field { box-shadow: 0 1px 0 0 rgba($color, .4); }
.formhud__caret { opacity: 1; }
}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);
}
.formhud {
display: flex;
flex-direction: column;
gap: 16px;
width: clamp(220px, 78vw, 300px);
}
.formhud__btn {
position: relative;
box-sizing: border-box;
border: 1px solid #ffd23f;
border-radius: 12px;
background: rgba(255, 210, 63, 0.14);
color: #ffd23f;
padding: 12px 20px;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.02em;
animation: btnSnap 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes btnSnap {
0%, 55%, 100% {
transform: scale(1);
}
75% {
transform: scale(0.97);
}
}
.formhud__corner {
position: absolute;
width: 10px;
height: 10px;
pointer-events: none;
border-color: #ffd23f;
border-style: solid;
border-width: 0;
}
.formhud__corner--tl {
top: 4px;
left: 4px;
border-top-width: 2px;
border-left-width: 2px;
animation: snapTl 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
.formhud__corner--tr {
top: 4px;
right: 4px;
border-top-width: 2px;
border-right-width: 2px;
animation: snapTr 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
.formhud__corner--bl {
bottom: 4px;
left: 4px;
border-bottom-width: 2px;
border-left-width: 2px;
animation: snapBl 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
.formhud__corner--br {
bottom: 4px;
right: 4px;
border-bottom-width: 2px;
border-right-width: 2px;
animation: snapBr 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes snapTl {
0%, 55%, 100% {
transform: translate(0, 0);
}
75% {
transform: translate(3px, 3px);
}
}
@keyframes snapTr {
0%, 55%, 100% {
transform: translate(0, 0);
}
75% {
transform: translate(-3px, 3px);
}
}
@keyframes snapBl {
0%, 55%, 100% {
transform: translate(0, 0);
}
75% {
transform: translate(3px, -3px);
}
}
@keyframes snapBr {
0%, 55%, 100% {
transform: translate(0, 0);
}
75% {
transform: translate(-3px, -3px);
}
}
.formhud__field {
position: relative;
display: block;
border-radius: 8px;
background: #17141a;
box-shadow: 0 1px 0 0 rgba(255, 210, 63, 0.4);
animation: focusGlow 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes focusGlow {
0%, 55%, 100% {
box-shadow: 0 1px 0 0 rgba(255, 210, 63, 0.4);
}
75% {
box-shadow: 0 1px 0 0 rgba(255, 210, 63, 0.9), 0 0 0 3px rgba(255, 210, 63, 0.25), 0 0 20px rgba(255, 210, 63, 0.35);
}
}
.formhud__input {
box-sizing: border-box;
width: 100%;
border: 0;
background: transparent;
padding: 12px 16px;
color: #fff7e6;
font-weight: 500;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
outline: none;
}
.formhud__input::placeholder {
color: rgba(255, 247, 230, 0.45);
}
.formhud__caret {
position: absolute;
left: 16px;
top: 50%;
width: 2px;
height: 14px;
transform: translateY(-50%);
background: #ffd23f;
animation: blink 2s steps(1) infinite;
}
@keyframes blink {
0%, 49% {
opacity: 1;
}
50%, 100% {
opacity: 0.2;
}
}
@media (prefers-reduced-motion: reduce) {
.formhud__btn, .formhud__corner, .formhud__field, .formhud__caret {
animation: none !important;
}
.formhud__btn {
transform: scale(1);
}
.formhud__corner {
transform: translate(0, 0);
}
.formhud__field {
box-shadow: 0 1px 0 0 rgba(255, 210, 63, 0.4);
}
.formhud__caret {
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./ButtonsInputsHud.module.scss";
export type ButtonsInputsHudProps = {
buttonLabel?: string;
placeholder?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function ButtonsInputsHud({
buttonLabel = "락온 시작",
placeholder = "이메일 주소",
duration = 2000,
easing = "cubic-bezier(.2,1.4,.4,1)",
color = "#ffd23f",
}: ButtonsInputsHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<form className={styles.form} style={vars}>
<button type="button" className={styles.btn}>
<span className={`${styles.corner} ${styles.cornerTl}`} aria-hidden="true" />
<span className={`${styles.corner} ${styles.cornerTr}`} aria-hidden="true" />
<span className={`${styles.corner} ${styles.cornerBl}`} aria-hidden="true" />
<span className={`${styles.corner} ${styles.cornerBr}`} aria-hidden="true" />
{buttonLabel}
</button>
<label className={styles.field}>
<input className={styles.input} type="email" placeholder={placeholder} readOnly />
<span className={styles.caret} aria-hidden="true" />
</label>
</form>
);
}09 ロックオンボタン・スキャン入力欄 — 計測 累積面積 7.12% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · HUD デザインとは?画面に見つめ返される9パーツ
このデモが属する記事
HUD デザインとは?画面に見つめ返される9パーツHUD デザインとは、ゲームやSF映画の照準器やレーダーのように、座標や状態の数値を画面に重ねて表示し、まるで自分が見つめ返されているような印象を与えるインターフェース手法です。この記事では、その監視カメラのような雰囲気をそのまま動くランデ この記事のデモをすべて見る


