04Overlay mờ dần
Lớp màu trong suốt phủ lên ảnh chuyển opacity từ 0 sang 1, rời chuột thì mờ dần trở lại.
Dùng choẢnh nền hero, ảnh cover danh mục
opacity:hover/:focus-visible0.3초

<main class="stage">
<figure class="photo is-demo" tabindex="0">
<span class="photo__pic" aria-hidden="true">
<span class="photo__sun"></span>
<span class="photo__hill"></span>
</span>
<span class="photo__overlay">
<span class="photo__overlay-text">Xem chi tiết</span>
</span>
</figure>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-base; // 오버레이가 덮이는 시간
$easing: $ease-out;
$color: $subject-blue; // 오버레이 색
@include stage($stage-paper);
.stage { padding: clamp(6px, 2.4vw, 16px); } // 320px 대응: 데모 카드가 작아 여백도 줄인다
.photo {
margin: 0;
position: relative;
width: min(160px, 50vw);
border-radius: $r-card;
overflow: hidden;
box-shadow: $shadow-raised;
border: 3px solid $subject-ink;
outline: none;
cursor: pointer;
}
.photo__pic {
display: block;
position: relative;
aspect-ratio: 4 / 3;
background: linear-gradient(155deg, $subject-cream 0%, $subject-blue 100%);
}
.photo__sun {
position: absolute;
top: 16%;
right: 18%;
width: 20%;
aspect-ratio: 1;
border-radius: 50%;
background: $stage-yellow;
}
.photo__hill {
position: absolute;
left: -8%;
right: -8%;
bottom: -10%;
height: 48%;
border-radius: 50% 50% 0 0 / 100% 100% 0 0;
background: $subject-ink;
opacity: .85;
}
.photo__overlay {
position: absolute;
inset: 0;
display: grid;
place-items: center;
background: rgba($color, .82);
opacity: 0; // 평소엔 투명
transition: opacity $duration $easing;
}
.photo__overlay-text {
word-break: keep-all;
padding: 8px 18px;
border-radius: $r-pill;
font-weight: 700; font-size: clamp(11px, 3vw, 13px); line-height: 1; font-family: inherit;
color: $subject-cream;
border: 1px solid $subject-cream;
}
.photo:hover .photo__overlay,
.photo:focus-visible .photo__overlay,
.photo:active .photo__overlay {
opacity: 1;
}
.photo.is-demo .photo__overlay {
animation: fade-loop $dur-loop $easing infinite;
}
@keyframes fade-loop {
0%, 20% { opacity: 0; }
50%, 75% { opacity: 1; }
100% { opacity: 0; }
}
@media (hover: none) {
.photo:active .photo__overlay { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.photo__overlay { transition: none; }
.photo.is-demo .photo__overlay { animation: none; }
}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);
}
.stage {
padding: clamp(6px, 2.4vw, 16px);
}
.photo {
margin: 0;
position: relative;
width: min(160px, 50vw);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
border: 3px solid #17141a;
outline: none;
cursor: pointer;
}
.photo__pic {
display: block;
position: relative;
aspect-ratio: 4/3;
background: linear-gradient(155deg, #fff7e6 0%, #2f6df6 100%);
}
.photo__sun {
position: absolute;
top: 16%;
right: 18%;
width: 20%;
aspect-ratio: 1;
border-radius: 50%;
background: #ffd23f;
}
.photo__hill {
position: absolute;
left: -8%;
right: -8%;
bottom: -10%;
height: 48%;
border-radius: 50% 50% 0 0/100% 100% 0 0;
background: #17141a;
opacity: 0.85;
}
.photo__overlay {
position: absolute;
inset: 0;
display: grid;
place-items: center;
background: rgba(47, 109, 246, 0.82);
opacity: 0;
transition: opacity 300ms ease-out;
}
.photo__overlay-text {
word-break: keep-all;
padding: 8px 18px;
border-radius: 999px;
font-weight: 700;
font-size: clamp(11px, 3vw, 13px);
line-height: 1;
font-family: inherit;
color: #fff7e6;
border: 1px solid #fff7e6;
}
.photo:hover .photo__overlay,
.photo:focus-visible .photo__overlay,
.photo:active .photo__overlay {
opacity: 1;
}
.photo.is-demo .photo__overlay {
animation: fade-loop 2s ease-out infinite;
}
@keyframes fade-loop {
0%, 20% {
opacity: 0;
}
50%, 75% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@media (hover: none) {
.photo:active .photo__overlay {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.photo__overlay {
transition: none;
}
.photo.is-demo .photo__overlay {
animation: none;
}
}import type { CSSProperties } from "react";
import styles from "./OverlayFadePhoto.module.scss";
export type OverlayFadePhotoProps = {
overlayText: string;
/** 오버레이가 덮이는 시간 (기본 300ms) */
duration?: number;
easing?: string;
/** 오버레이 색 */
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function OverlayFadePhoto({
overlayText,
duration = 300,
easing = "ease-out",
color = "#2f6df6",
}: OverlayFadePhotoProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<figure className={styles.photo} style={vars} tabIndex={0}>
<span className={styles.pic} aria-hidden="true">
<span className={styles.sun} />
<span className={styles.hill} />
</span>
<span className={styles.overlay}>
<span className={styles.overlayText}>{overlayText}</span>
</span>
</figure>
);
}04 Overlay mờ dần — Diện tích đo được 13.208% · khung hình chuyển động 11/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 CSS Image Hover Effects — dán 1 dòng là chạy
Bài viết chứa demo này
9 CSS Image Hover Effects — dán 1 dòng là chạyCSS image hover effects là các hiệu ứng chỉ chạy bằng CSS khi con trỏ dừng trên một ảnh, không cần JavaScript. Chín kiểu Xem tất cả demo trong bài này


