09Trượt mượt mà (nurutto)
Khi hover, `.o-nuru` trượt icon mũi tên bằng cubic-bezier gần ease-out-expo, tạo cảm giác mượt tan đối lập với độ đàn hồi của các mục kia.
Dùng choLiên kết 'tìm hiểu thêm', nút bước tiếp theo, mũi tên điều hướng
translateXcubic-bezier(.16, 1, .3, 1)700ms

<main class="stage">
<a class="link o-nuru is-demo" href="#">
Tìm hiểu thêm
<span class="link__arrow" aria-hidden="true">→</span>
</a>
<span class="tag">Mượt mà</span>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: 700ms;
$easing: cubic-bezier(.16, 1, .3, 1); // ease-out-expo 근사 — 매끄럽게 감속한다
$color: $subject-blue;
// ── 데모 무대 ───────────────────────────────
@include stage($stage-ink);
.stage { position: relative; }
.link {
display: inline-flex;
align-items: center;
gap: $sp-2;
padding: $sp-3 $sp-2;
color: $subject-cream;
text-decoration: none;
font-weight: 700;
font-size: clamp(16px, 4.5vw, 19px);
line-height: 1;
font-family: inherit;
border-bottom: 2px solid rgba(255, 247, 230, .3);
}
.link__arrow {
display: inline-block;
color: $color;
font-size: 22px;
}
.tag {
position: absolute;
top: $sp-4;
right: $sp-4;
padding: $sp-1 $sp-3;
border-radius: $r-sm;
background: rgba(255, 255, 255, .12);
color: $subject-cream;
font-weight: 700;
font-size: 12px;
line-height: 1;
font-family: inherit;
letter-spacing: .04em;
}
// ── 실제 쓰임: 마우스를 올리면(또는 포커스) 화살표가 미끄러지듯 이동한다 ─────
.link.o-nuru:hover .link__arrow,
.link.o-nuru:focus-visible .link__arrow { animation: o-nuru $duration $easing 1; }
// ── 데모: 2초마다 스스로 미끄러진다 ─────
.link.o-nuru.is-demo .link__arrow { animation: o-nuru-loop $dur-loop $easing infinite; }
@keyframes o-nuru {
0% { transform: translateX(0) scale(1); }
100% { transform: translateX(28px) scale(1.15); }
}
@keyframes o-nuru-loop {
0%, 40% { transform: translateX(0) scale(1); }
78% { transform: translateX(28px) scale(1.15); }
100% { transform: translateX(0) scale(1); }
}
// ── 접근성 ──────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.link.o-nuru:hover .link__arrow, .link.o-nuru:focus-visible .link__arrow,
.link.o-nuru.is-demo .link__arrow { animation: none; }
}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);
}
.stage {
position: relative;
}
.link {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 8px;
color: #fff7e6;
text-decoration: none;
font-weight: 700;
font-size: clamp(16px, 4.5vw, 19px);
line-height: 1;
font-family: inherit;
border-bottom: 2px solid rgba(255, 247, 230, 0.3);
}
.link__arrow {
display: inline-block;
color: #2f6df6;
font-size: 22px;
}
.tag {
position: absolute;
top: 16px;
right: 16px;
padding: 4px 12px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.12);
color: #fff7e6;
font-weight: 700;
font-size: 12px;
line-height: 1;
font-family: inherit;
letter-spacing: 0.04em;
}
.link.o-nuru:hover .link__arrow,
.link.o-nuru:focus-visible .link__arrow {
animation: o-nuru 700ms cubic-bezier(0.16, 1, 0.3, 1) 1;
}
.link.o-nuru.is-demo .link__arrow {
animation: o-nuru-loop 2s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
@keyframes o-nuru {
0% {
transform: translateX(0) scale(1);
}
100% {
transform: translateX(28px) scale(1.15);
}
}
@keyframes o-nuru-loop {
0%, 40% {
transform: translateX(0) scale(1);
}
78% {
transform: translateX(28px) scale(1.15);
}
100% {
transform: translateX(0) scale(1);
}
}
@media (prefers-reduced-motion: reduce) {
.link.o-nuru:hover .link__arrow, .link.o-nuru:focus-visible .link__arrow,
.link.o-nuru.is-demo .link__arrow {
animation: none;
}
}import type { CSSProperties, ReactNode } from "react";
import styles from "./SmoothLink.module.scss";
export type SmoothLinkProps = {
children: ReactNode;
href?: string;
/** 미끄러지는 데 걸리는 시간 (기본 700ms) */
duration?: number;
/** CSS easing (기본 ease-out-expo 근사) */
easing?: string;
color?: string;
};
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--color"?: string;
};
export default function SmoothLink({
children,
href = "#",
duration = 700,
easing = "cubic-bezier(.16,1,.3,1)",
color = "#2f6df6",
}: SmoothLinkProps) {
const vars: Vars = {
"--duration": `${duration}ms`,
"--easing": easing,
"--color": color,
};
return (
<a className={styles.link} href={href} style={vars}>
{children}
<span className={styles.arrow} aria-hidden="true">
→
</span>
</a>
);
}09 Trượt mượt mà (nurutto) — Diện tích đo được 0.172% · khung hình chuyển động 13/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 css bounce animation — dán một dòng là chạy
Bài viết chứa demo này
9 css bounce animation — dán một dòng là chạycss bounce animation là chuyển động cho phần tử nổi lên, nảy, rung và đung đưa như đang sống. Chín mục gọi theo từ tượng Xem tất cả demo trong bài này


