04Icon bồng bềnh trong tile
Icon trong tile tính năng vốn nằm im, đến khi hover thì vòng lặp keyframe translateY kéo nó nhấp nhô lên xuống dìu dịu.
Dùng choTile tóm tắt tính năng, thẻ icon dịch vụ
translateY@keyframes1.4s
<main class="stage">
<div class="if-grid is-demo">
<div class="if-tile if-tile--a if-focal">
<span class="if-icon"></span>
<span class="if-tile__label">Fast</span>
</div>
<div class="if-tile if-tile--b">
<span class="if-tile__tag">Beta</span>
</div>
<div class="if-tile if-tile--c">
<span class="if-tile__tag">v2</span>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: 1.4s;
$easing: $ease-out;
$color: $subject-blue;
@include stage($stage-paper);
.if-grid {
width: min(340px, 90vw);
aspect-ratio: 340 / 190;
display: grid;
grid-template-columns: 1.2fr 1fr;
grid-template-rows: 1fr 1fr;
gap: $sp-2;
}
.if-tile {
border-radius: $r-sm;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
gap: $sp-2;
padding: $sp-3;
box-sizing: border-box;
color: $subject-ink;
font: 600 12px/1 -apple-system, "Segoe UI", sans-serif;
background: rgba(23, 20, 26, .05);
}
.if-tile--a { grid-row: 1 / 3; justify-content: center; align-items: center; }
.if-tile--b, .if-tile--c { justify-content: flex-end; align-items: flex-start; }
.if-tile__tag { opacity: .7; }
.if-tile__label { opacity: .8; }
// 아이콘 — 이모지 대신 CSS 로 그린 둥근 사각형 + 안쪽 점
.if-icon {
position: relative;
width: $sp-8;
height: $sp-8;
border-radius: $r-control;
background: $color;
display: block;
}
.if-icon::after {
content: "";
position: absolute;
inset: $sp-2;
border-radius: $r-pill;
background: $subject-cream;
}
// 실사용: 진짜 마우스 호버 — 평소엔 가만히, 호버해야 떠오른다
.if-focal:hover .if-icon { animation: if-bob $duration $easing infinite; }
@keyframes if-bob {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
// 데모 루프: is-demo 는 같은 동작을 무한 재생 (캡처용)
.if-grid.is-demo .if-focal .if-icon { animation: if-bob $duration $easing infinite; }
@media (prefers-reduced-motion: reduce) {
.if-focal:hover .if-icon,
.if-grid.is-demo .if-focal .if-icon { animation: none !important; transform: none !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.if-grid {
width: min(340px, 90vw);
aspect-ratio: 340/190;
display: grid;
grid-template-columns: 1.2fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 8px;
}
.if-tile {
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
gap: 8px;
padding: 12px;
box-sizing: border-box;
color: #17141a;
font: 600 12px/1 -apple-system, "Segoe UI", sans-serif;
background: rgba(23, 20, 26, 0.05);
}
.if-tile--a {
grid-row: 1/3;
justify-content: center;
align-items: center;
}
.if-tile--b, .if-tile--c {
justify-content: flex-end;
align-items: flex-start;
}
.if-tile__tag {
opacity: 0.7;
}
.if-tile__label {
opacity: 0.8;
}
.if-icon {
position: relative;
width: 32px;
height: 32px;
border-radius: 12px;
background: #2f6df6;
display: block;
}
.if-icon::after {
content: "";
position: absolute;
inset: 8px;
border-radius: 999px;
background: #fff7e6;
}
.if-focal:hover .if-icon {
animation: if-bob 1.4s ease-out infinite;
}
@keyframes if-bob {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-6px);
}
}
.if-grid.is-demo .if-focal .if-icon {
animation: if-bob 1.4s ease-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.if-focal:hover .if-icon,
.if-grid.is-demo .if-focal .if-icon {
animation: none !important;
transform: none !important;
}
}import { CSSProperties } from "react";
import styles from "./IconFloatTile.module.scss";
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--color"?: string;
};
export interface IconFloatTileProps {
duration?: string;
easing?: string;
color?: string;
}
export default function IconFloatTile({
duration = "1.4s",
easing = "ease-out",
color = "#2f6df6",
}: IconFloatTileProps) {
const style: Vars = {
"--duration": duration,
"--easing": easing,
"--color": color,
};
return (
<div className={styles.grid} style={style}>
<div className={`${styles.tile} ${styles.tileA} ${styles.focal}`}>
<span className={styles.icon} />
<span className={styles.label}>Fast</span>
</div>
<div className={`${styles.tile} ${styles.tileB}`}>
<span className={styles.tag}>Beta</span>
</div>
<div className={`${styles.tile} ${styles.tileC}`}>
<span className={styles.tag}>v2</span>
</div>
</div>
);
}04 Icon bồng bềnh trong tile — Diện tích đo được 0.461% · khung hình chuyển động 23/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 bento grid css — hiệu ứng hover dán một dòng
Bài viết chứa demo này
9 bento grid css — hiệu ứng hover dán một dòngBạn từng thấy trang portfolio ghép các ô vuông to nhỏ liền nhau như một tấm collage ảnh chưa? Đây là chín đoạn bento gri Xem tất cả demo trong bài này

