01Đường cong ease-out-quart
Gắn cubic-bezier(.25, 1, .5, 1) vào translateX, phần tử bắn ra nhanh ngay từ đầu rồi hạ cánh êm quanh điểm đến, không hề vọt quá đích.
Dùng choChuyển cảnh mở modal·drawer, lúc thẻ hiện lên màn hình
cubic-bezier(.25, 1, .5, 1)transform

<main class="stage is-demo">
<div class="track">
<span class="dot dot--start"></span>
<span class="dot dot--end"></span>
<div class="puck"></div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop; // 2s — 왕복 한 주기
$easing: cubic-bezier(.25, 1, .5, 1); // ease-out-quart — 처음부터 빠르게 튀어나가 끝에서 천천히 멈춘다
$color: $subject-blue;
@include stage($stage-ink);
$track-w: 220px;
$puck-w: $sp-6;
$travel: $track-w - $puck-w;
.track {
position: relative;
width: $track-w;
height: $sp-2;
}
.dot {
position: absolute;
top: 50%;
width: $sp-2;
height: $sp-2;
border-radius: $r-pill;
background: rgba(255, 255, 255, .25);
transform: translate(-50%, -50%);
}
.dot--start { left: 0; }
.dot--end { left: $track-w; }
.puck {
position: absolute;
top: 50%;
left: 0;
width: $puck-w;
height: $puck-w;
border-radius: $r-pill;
background: $color;
box-shadow: $shadow-raised;
transform: translate(0, -50%);
}
// transform 만 움직인다 — top/left 는 그대로 두고 puck 만 왕복
.stage.is-demo .puck {
animation: move $duration $easing infinite alternate;
}
@keyframes move {
from { transform: translate(0, -50%); }
to { transform: translate($travel, -50%); }
}
@media (prefers-reduced-motion: reduce) {
.stage.is-demo .puck { animation: none; transform: translate($travel, -50%); }
}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);
}
.track {
position: relative;
width: 220px;
height: 8px;
}
.dot {
position: absolute;
top: 50%;
width: 8px;
height: 8px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.25);
transform: translate(-50%, -50%);
}
.dot--start {
left: 0;
}
.dot--end {
left: 220px;
}
.puck {
position: absolute;
top: 50%;
left: 0;
width: 24px;
height: 24px;
border-radius: 999px;
background: #2f6df6;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
transform: translate(0, -50%);
}
.stage.is-demo .puck {
animation: move 2s cubic-bezier(0.25, 1, 0.5, 1) infinite alternate;
}
@keyframes move {
from {
transform: translate(0, -50%);
}
to {
transform: translate(196px, -50%);
}
}
@media (prefers-reduced-motion: reduce) {
.stage.is-demo .puck {
animation: none;
transform: translate(196px, -50%);
}
}import type { CSSProperties } from "react";
import styles from "./EaseOutQuart.module.scss";
export type EaseOutQuartProps = {
/** 왕복 한 주기 (기본 2000ms) */
duration?: number;
color?: string;
};
export default function EaseOutQuart({ duration = 2000, color = "#2f6df6" }: EaseOutQuartProps) {
const vars: CSSProperties = { "--duration": `${duration}ms`, "--color": color } as CSSProperties;
return (
<div className={styles.stage} style={vars}>
<div className={styles.track}>
<span className={`${styles.dot} ${styles.dotStart}`} />
<span className={`${styles.dot} ${styles.dotEnd}`} />
<div className={styles.puck} />
</div>
</div>
);
}01 Đường cong ease-out-quart — Diện tích đo được 3.2% · khung hình chuyển động 22/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 cubic bezier css — đường cong copy-paste
Bài viết chứa demo này
9 cubic bezier css — đường cong copy-pasteCách ghi thay đổi tốc độ của một chuyển động bằng bốn con số trong CSS chính là giá trị cubic bezier css, còn kiểu nảy t Xem tất cả demo trong bài này

