09Làm mờ và tối nền
Menu mở ra thì nội dung phía sau mờ và tối đi để tập trung vào menu.
Dùng choMenu overlay trên trang có nhiều nội dung
6px어둡게 40%10px

<main class="stage is-demo">
<div class="page">
<span class="page__line page__line--1"></span>
<span class="page__line page__line--2"></span>
<span class="page__line page__line--3"></span>
</div>
<button class="burger" type="button" aria-label="Mở menu">
<span class="bar bar1"></span>
<span class="bar bar2"></span>
<span class="bar bar3"></span>
</button>
<nav class="menu" aria-hidden="true">
<a class="menu__link" href="#">Trang chủ</a>
<a class="menu__link" href="#">Giới thiệu</a>
<a class="menu__link" href="#">Sản phẩm</a>
<a class="menu__link" href="#">Liên hệ</a>
</nav>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 (값은 토큰에서 받는다) ──────────────────────
$blur: 6px; // 배경이 흐려지는 정도. 진하게 10px
$dim: .4; // 배경이 어두워지는 정도(0~1)
$color: $subject-cream; // 줄·메뉴 글자 색
@include stage($stage-ink);
.stage { position: relative; overflow: hidden; }
.page {
position: absolute; inset: 0;
display: grid; align-content: center; gap: clamp(10px, 4vmin, 16px);
padding: clamp(20px, 8vmin, 32px);
transition: filter $dur-base $ease-spring;
}
.page__line { display: block; height: clamp(8px, 2.6vmin, 12px); border-radius: 6px; background: rgba($color, .5); }
.page__line--1 { width: 70%; }
.page__line--2 { width: 92%; }
.page__line--3 { width: 55%; }
.burger {
position: absolute; top: clamp(14px, 5vmin, 20px); left: clamp(14px, 5vmin, 20px);
z-index: 3; width: clamp(34px, 10vmin, 42px); height: clamp(34px, 10vmin, 42px);
border: 0; border-radius: $r-control; background: transparent;
padding: 0; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.bar {
position: absolute; left: 50%; top: 50%;
width: clamp(20px, 6.4vmin, 26px); height: 3px; border-radius: 2px;
background: $color;
transition: transform $dur-quick $ease-spring, opacity $dur-quick $ease-spring;
}
.bar1 { transform: translate(-50%, -50%) translateY(-7px) rotate(0deg); }
.bar2 { transform: translate(-50%, -50%) scaleX(1); opacity: 1; }
.bar3 { transform: translate(-50%, -50%) translateY(7px) rotate(0deg); }
.menu {
position: absolute; top: clamp(52px, 16vmin, 64px); left: clamp(14px, 5vmin, 20px);
z-index: 2;
display: grid; gap: clamp(6px, 2.2vmin, 10px);
width: clamp(120px, 40vmin, 160px);
padding: clamp(12px, 4vmin, 16px);
border-radius: $r-card; background: $stage-paper; box-shadow: $shadow-raised;
opacity: 0; transform: translateY(-8px) scale(.96); transform-origin: top left; // 규칙8: 버튼 모서리에서 펼침
pointer-events: none;
transition: opacity $dur-base $ease-spring, transform $dur-base $ease-spring;
}
.menu__link { font-weight: 600; font-size: clamp(11px, 3.6vmin, 13px); line-height: 1; font-family: inherit; color: $stage-ink; text-decoration: none; }
.stage.is-open .page { filter: blur($blur) brightness(1 - $dim); }
.stage.is-open .bar1 { transform: translate(-50%, -50%) translateY(0) rotate(45deg); }
.stage.is-open .bar2 { transform: translate(-50%, -50%) scaleX(0); opacity: 0; }
.stage.is-open .bar3 { transform: translate(-50%, -50%) translateY(0) rotate(-45deg); }
.stage.is-open .menu { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.stage.is-demo .page { animation: page-loop $dur-loop $ease-spring infinite; }
.stage.is-demo .bar1 { animation: bar1-loop $dur-loop $ease-spring infinite; }
.stage.is-demo .bar2 { animation: bar2-loop $dur-loop $ease-spring infinite; }
.stage.is-demo .bar3 { animation: bar3-loop $dur-loop $ease-spring infinite; }
.stage.is-demo .menu { animation: menu-loop $dur-loop $ease-spring infinite; }
@keyframes page-loop {
0% { filter: blur(0) brightness(1); }
15% { filter: blur($blur) brightness(1 - $dim); }
70% { filter: blur($blur) brightness(1 - $dim); }
85% { filter: blur(0) brightness(1); }
100% { filter: blur(0) brightness(1); }
}
@keyframes bar1-loop {
0% { transform: translate(-50%, -50%) translateY(-7px) rotate(0deg); }
15% { transform: translate(-50%, -50%) translateY(0) rotate(45deg); }
70% { transform: translate(-50%, -50%) translateY(0) rotate(45deg); }
85% { transform: translate(-50%, -50%) translateY(-7px) rotate(0deg); }
100% { transform: translate(-50%, -50%) translateY(-7px) rotate(0deg); }
}
@keyframes bar2-loop {
0% { transform: translate(-50%, -50%) scaleX(1); opacity: 1; }
15% { transform: translate(-50%, -50%) scaleX(0); opacity: 0; }
70% { transform: translate(-50%, -50%) scaleX(0); opacity: 0; }
85% { transform: translate(-50%, -50%) scaleX(1); opacity: 1; }
100% { transform: translate(-50%, -50%) scaleX(1); opacity: 1; }
}
@keyframes bar3-loop {
0% { transform: translate(-50%, -50%) translateY(7px) rotate(0deg); }
15% { transform: translate(-50%, -50%) translateY(0) rotate(-45deg); }
70% { transform: translate(-50%, -50%) translateY(0) rotate(-45deg); }
85% { transform: translate(-50%, -50%) translateY(7px) rotate(0deg); }
100% { transform: translate(-50%, -50%) translateY(7px) rotate(0deg); }
}
@keyframes menu-loop {
0% { opacity: 0; transform: translateY(-8px) scale(.96); }
15% { opacity: 1; transform: translateY(0) scale(1); }
70% { opacity: 1; transform: translateY(0) scale(1); }
85% { opacity: 0; transform: translateY(-8px) scale(.96); }
100% { opacity: 0; transform: translateY(-8px) scale(.96); }
}
// ── 접근성 ─────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.stage.is-demo .page, .stage.is-demo .bar1, .stage.is-demo .bar2, .stage.is-demo .bar3, .stage.is-demo .menu { animation: none; }
.page, .bar, .menu { transition-duration: 1ms; }
}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;
overflow: hidden;
}
.page {
position: absolute;
inset: 0;
display: grid;
align-content: center;
gap: clamp(10px, 4vmin, 16px);
padding: clamp(20px, 8vmin, 32px);
transition: filter 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.page__line {
display: block;
height: clamp(8px, 2.6vmin, 12px);
border-radius: 6px;
background: rgba(255, 247, 230, 0.5);
}
.page__line--1 {
width: 70%;
}
.page__line--2 {
width: 92%;
}
.page__line--3 {
width: 55%;
}
.burger {
position: absolute;
top: clamp(14px, 5vmin, 20px);
left: clamp(14px, 5vmin, 20px);
z-index: 3;
width: clamp(34px, 10vmin, 42px);
height: clamp(34px, 10vmin, 42px);
border: 0;
border-radius: 12px;
background: transparent;
padding: 0;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.bar {
position: absolute;
left: 50%;
top: 50%;
width: clamp(20px, 6.4vmin, 26px);
height: 3px;
border-radius: 2px;
background: #fff7e6;
transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.bar1 {
transform: translate(-50%, -50%) translateY(-7px) rotate(0deg);
}
.bar2 {
transform: translate(-50%, -50%) scaleX(1);
opacity: 1;
}
.bar3 {
transform: translate(-50%, -50%) translateY(7px) rotate(0deg);
}
.menu {
position: absolute;
top: clamp(52px, 16vmin, 64px);
left: clamp(14px, 5vmin, 20px);
z-index: 2;
display: grid;
gap: clamp(6px, 2.2vmin, 10px);
width: clamp(120px, 40vmin, 160px);
padding: clamp(12px, 4vmin, 16px);
border-radius: 16px;
background: #ffffff;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
opacity: 0;
transform: translateY(-8px) scale(0.96);
transform-origin: top left;
pointer-events: none;
transition: opacity 300ms cubic-bezier(0.2, 0.8, 0.2, 1), transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.menu__link {
font-weight: 600;
font-size: clamp(11px, 3.6vmin, 13px);
line-height: 1;
font-family: inherit;
color: #17141a;
text-decoration: none;
}
.stage.is-open .page {
filter: blur(6px) brightness(0.6);
}
.stage.is-open .bar1 {
transform: translate(-50%, -50%) translateY(0) rotate(45deg);
}
.stage.is-open .bar2 {
transform: translate(-50%, -50%) scaleX(0);
opacity: 0;
}
.stage.is-open .bar3 {
transform: translate(-50%, -50%) translateY(0) rotate(-45deg);
}
.stage.is-open .menu {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.stage.is-demo .page {
animation: page-loop 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.stage.is-demo .bar1 {
animation: bar1-loop 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.stage.is-demo .bar2 {
animation: bar2-loop 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.stage.is-demo .bar3 {
animation: bar3-loop 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.stage.is-demo .menu {
animation: menu-loop 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes page-loop {
0% {
filter: blur(0) brightness(1);
}
15% {
filter: blur(6px) brightness(0.6);
}
70% {
filter: blur(6px) brightness(0.6);
}
85% {
filter: blur(0) brightness(1);
}
100% {
filter: blur(0) brightness(1);
}
}
@keyframes bar1-loop {
0% {
transform: translate(-50%, -50%) translateY(-7px) rotate(0deg);
}
15% {
transform: translate(-50%, -50%) translateY(0) rotate(45deg);
}
70% {
transform: translate(-50%, -50%) translateY(0) rotate(45deg);
}
85% {
transform: translate(-50%, -50%) translateY(-7px) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) translateY(-7px) rotate(0deg);
}
}
@keyframes bar2-loop {
0% {
transform: translate(-50%, -50%) scaleX(1);
opacity: 1;
}
15% {
transform: translate(-50%, -50%) scaleX(0);
opacity: 0;
}
70% {
transform: translate(-50%, -50%) scaleX(0);
opacity: 0;
}
85% {
transform: translate(-50%, -50%) scaleX(1);
opacity: 1;
}
100% {
transform: translate(-50%, -50%) scaleX(1);
opacity: 1;
}
}
@keyframes bar3-loop {
0% {
transform: translate(-50%, -50%) translateY(7px) rotate(0deg);
}
15% {
transform: translate(-50%, -50%) translateY(0) rotate(-45deg);
}
70% {
transform: translate(-50%, -50%) translateY(0) rotate(-45deg);
}
85% {
transform: translate(-50%, -50%) translateY(7px) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) translateY(7px) rotate(0deg);
}
}
@keyframes menu-loop {
0% {
opacity: 0;
transform: translateY(-8px) scale(0.96);
}
15% {
opacity: 1;
transform: translateY(0) scale(1);
}
70% {
opacity: 1;
transform: translateY(0) scale(1);
}
85% {
opacity: 0;
transform: translateY(-8px) scale(0.96);
}
100% {
opacity: 0;
transform: translateY(-8px) scale(0.96);
}
}
@media (prefers-reduced-motion: reduce) {
.stage.is-demo .page, .stage.is-demo .bar1, .stage.is-demo .bar2, .stage.is-demo .bar3, .stage.is-demo .menu {
animation: none;
}
.page, .bar, .menu {
transition-duration: 1ms;
}
}(function () {
var stage = document.querySelector('.stage');
var burger = document.querySelector('.burger');
var open = false;
burger.addEventListener('click', function () {
stage.classList.remove('is-demo');
open = !open;
stage.classList.toggle('is-open', open);
burger.setAttribute('aria-label', open ? 'Đóng menu' : 'Mở menu');
});
})();import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./BackdropBlurDim.module.scss";
export type BackdropBlurDimProps = {
links: string[];
openLabel: string;
closeLabel: string;
/** 배경이 흐려지는 정도 (기본 6px, 진하게 10px) */
blur?: number;
/** 배경이 어두워지는 정도 0~1 (기본 .4) */
dim?: number;
color?: string;
};
type Vars = CSSProperties & { "--color"?: string; "--blur"?: string; "--brightness"?: string };
export default function BackdropBlurDim({
links,
openLabel,
closeLabel,
blur = 6,
dim = 0.4,
color = "#fff7e6",
}: BackdropBlurDimProps) {
const [open, setOpen] = useState(false);
const vars: Vars = { "--color": color, "--blur": `${blur}px`, "--brightness": `${1 - dim}` };
const cls = (base: string) => (open ? `${base} ${styles.isOpen}` : base);
return (
<div className={styles.stage} style={vars}>
<div className={cls(styles.page)}>
<span className={`${styles.pageLine} ${styles.pageLine1}`} />
<span className={`${styles.pageLine} ${styles.pageLine2}`} />
<span className={`${styles.pageLine} ${styles.pageLine3}`} />
</div>
<button
type="button"
className={cls(styles.burger)}
aria-label={open ? closeLabel : openLabel}
onClick={() => setOpen((v) => !v)}
>
<span className={`${styles.bar} ${styles.bar1}`} />
<span className={`${styles.bar} ${styles.bar2}`} />
<span className={`${styles.bar} ${styles.bar3}`} />
</button>
<nav className={cls(styles.menu)} aria-hidden={!open}>
{links.map((label) => (
<a key={label} className={styles.menuLink} href="#">
{label}
</a>
))}
</nav>
</div>
);
}09 Làm mờ và tối nền — Diện tích đo được 20.247% · khung hình chuyển động 8/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 hiệu ứng css hamburger menu animation không JS
Bài viết chứa demo này
9 hiệu ứng css hamburger menu animation không JSCss hamburger menu animation là chuyển động của nút ba vạch khi menu mở và đóng, dựng bằng transition và transform của C Xem tất cả demo trong bài này


