07Tab kính
Trong nhóm tab chất liệu kính, chọn tab nào thì indicator kính bán trong suốt giữ nguyên độ rộng cố định và trượt xuống dưới tab đó bằng translateX.
Dùng choTab màn hình cài đặt, tab bộ lọc dashboard
backdrop-filtertranslateX220ms

<main class="stage">
<div class="gt is-demo">
<input type="radio" name="gt-tab" id="gt-1" class="gt__radio" checked>
<input type="radio" name="gt-tab" id="gt-2" class="gt__radio">
<input type="radio" name="gt-tab" id="gt-3" class="gt__radio">
<div class="gt__nav">
<span class="gt__pill" aria-hidden="true"></span>
<label class="gt__tab" for="gt-1">Day</label>
<label class="gt__tab" for="gt-2">Week</label>
<label class="gt__tab" for="gt-3">Month</label>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-quick;
$easing: $ease-pop;
$color: $subject-blue;
@include stage($stage-orange);
// 탭 폭을 고정값으로 둔다 — flex:0 0 auto(내용 폭)와 같이 써야 translateX 상수와 어긋나지 않는다 (BD19)
$w: 68px;
.gt { width: min(240px, 82vw); }
.gt__radio { position: absolute; opacity: 0; pointer-events: none; }
.gt__nav {
position: relative;
display: flex;
gap: $sp-2;
padding: $sp-1;
border-radius: $r-pill;
background: rgba(255, 255, 255, .14);
border: 1px solid rgba(255, 255, 255, .3);
backdrop-filter: blur(12px) saturate(160%);
-webkit-backdrop-filter: blur(12px) saturate(160%);
}
.gt__tab {
position: relative;
z-index: 1;
flex: 0 0 auto;
box-sizing: border-box;
width: $w;
text-align: center;
padding: $sp-2 0;
font: 600 clamp(11px, 3.2vw, 13px)/1 -apple-system, "Segoe UI", sans-serif;
color: $subject-ink;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.gt__pill {
position: absolute;
top: $sp-1; left: $sp-1; bottom: $sp-1;
box-sizing: border-box;
width: $w;
border-radius: $r-pill;
background: rgba(255, 255, 255, .6);
backdrop-filter: blur(6px);
box-shadow: $shadow-press;
transform: translateX(0);
transition: transform $duration $easing;
}
// 실사용: :checked 만으로 인디케이터가 이동한다
#gt-1:checked ~ .gt__nav .gt__pill { transform: translateX(0); }
#gt-2:checked ~ .gt__nav .gt__pill { transform: translateX(calc(#{$w} + #{$sp-2})); }
#gt-3:checked ~ .gt__nav .gt__pill { transform: translateX(calc((#{$w} + #{$sp-2}) * 2)); }
// 데모 루프: 클릭 전까지 세 자리를 스스로 오간다
.gt.is-demo .gt__pill { animation: gt-loop $dur-loop $easing infinite; }
@keyframes gt-loop {
0%, 15% { transform: translateX(0); }
33%, 48% { transform: translateX(calc(#{$w} + #{$sp-2})); }
66%, 81% { transform: translateX(calc((#{$w} + #{$sp-2}) * 2)); }
100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
.gt__pill { transition: none; }
.gt.is-demo .gt__pill { 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: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.gt {
width: min(240px, 82vw);
}
.gt__radio {
position: absolute;
opacity: 0;
pointer-events: none;
}
.gt__nav {
position: relative;
display: flex;
gap: 8px;
padding: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(12px) saturate(160%);
-webkit-backdrop-filter: blur(12px) saturate(160%);
}
.gt__tab {
position: relative;
z-index: 1;
flex: 0 0 auto;
box-sizing: border-box;
width: 68px;
text-align: center;
padding: 8px 0;
font: 600 clamp(11px, 3.2vw, 13px)/1 -apple-system, "Segoe UI", sans-serif;
color: #17141a;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.gt__pill {
position: absolute;
top: 4px;
left: 4px;
bottom: 4px;
box-sizing: border-box;
width: 68px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(6px);
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
transform: translateX(0);
transition: transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
#gt-1:checked ~ .gt__nav .gt__pill {
transform: translateX(0);
}
#gt-2:checked ~ .gt__nav .gt__pill {
transform: translateX(calc(68px + 8px));
}
#gt-3:checked ~ .gt__nav .gt__pill {
transform: translateX(calc((68px + 8px) * 2));
}
.gt.is-demo .gt__pill {
animation: gt-loop 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes gt-loop {
0%, 15% {
transform: translateX(0);
}
33%, 48% {
transform: translateX(calc(68px + 8px));
}
66%, 81% {
transform: translateX(calc((68px + 8px) * 2));
}
100% {
transform: translateX(0);
}
}
@media (prefers-reduced-motion: reduce) {
.gt__pill {
transition: none;
}
.gt.is-demo .gt__pill {
animation: none !important;
}
}// 인디케이터는 :checked 만으로 이동한다. 실제 클릭이 오면 데모 루프(.is-demo)만 뗀다.
document.querySelectorAll('.gt__radio').forEach(function (r) {
r.addEventListener('pointerdown', function () { document.querySelector('.gt').classList.remove('is-demo'); });
});import { CSSProperties, useState } from "react";
import styles from "./GlassTab.module.scss";
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--x"?: string;
};
export interface GlassTabProps {
duration?: string;
easing?: string;
}
const TABS = ["Day", "Week", "Month"];
const TAB_WIDTH = 68;
const GAP = 8;
export default function GlassTab({
duration = "220ms",
easing = "cubic-bezier(.2, 1.4, .4, 1)",
}: GlassTabProps) {
const [active, setActive] = useState(0);
const style: Vars = {
"--duration": duration,
"--easing": easing,
"--x": `${active * (TAB_WIDTH + GAP)}px`,
};
return (
<div className={styles.wrap} style={style}>
<div className={styles.nav}>
<span className={styles.pill} aria-hidden="true" />
{TABS.map((label, i) => (
<button
key={label}
type="button"
className={styles.tab}
aria-pressed={active === i}
onClick={() => setActive(i)}
>
{label}
</button>
))}
</div>
</div>
);
}07 Tab kính — Diện tích đo được 6.422% · khung hình chuyển động 14/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 css glassmorphism — dán một file là chạy
Bài viết chứa demo này
9 css glassmorphism — dán một file là chạyGlassmorphism là đặt một tấm kính mờ lên màn hình: nền phía sau hiện lên mờ mềm còn chữ phía trên vẫn rõ nét. Chín hiệu Xem tất cả demo trong bài này


