03Caption trượt lên
Thanh caption ẩn dưới ảnh trượt lên qua translateY, để lộ tiêu đề và mô tả.
Dùng choDanh sách card blog, lưới kiểu tạp chí
translateYoverflow:hidden0.25초

<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>
<figcaption class="photo__cap">
<strong class="photo__cap-title">Hướng dẫn pha cà phê</strong>
<span class="photo__cap-desc">Đọc trong 5 phút</span>
</figcaption>
</figure>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-base; // 캡션이 올라오는 시간
$easing: $ease-out;
$color: $subject-cream; // 캡션 배경 색
@include stage($stage-orange);
.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;
outline: none;
cursor: pointer;
}
.photo__pic {
display: block;
position: relative;
aspect-ratio: 4 / 3;
background: linear-gradient(155deg, $subject-blue 0%, $subject-ink 100%);
}
.photo__sun {
position: absolute;
top: 14%;
right: 16%;
width: 18%;
aspect-ratio: 1;
border-radius: 50%;
background: $subject-cream;
}
.photo__hill {
position: absolute;
left: -8%;
right: -8%;
bottom: -10%;
height: 46%;
border-radius: 50% 50% 0 0 / 100% 100% 0 0;
background: $stage-orange;
opacity: .55;
}
.photo__cap {
word-break: keep-all;
position: absolute;
left: 0;
right: 0;
bottom: 0;
display: grid;
gap: 2px;
padding: 12px 14px;
background: $color;
transform: translateY(100%); // 아래로 숨겨 둔다
transition: transform $duration $easing;
}
.photo__cap-title {
word-break: keep-all;
font-weight: 700; font-size: clamp(12px, 3.4vw, 14px); line-height: 1.3; font-family: inherit;
color: $stage-ink;
}
.photo__cap-desc {
word-break: keep-all;
font-weight: 500; font-size: clamp(10px, 2.8vw, 12px); line-height: 1.3; font-family: inherit;
color: $stage-ink;
}
.photo:hover .photo__cap,
.photo:focus-visible .photo__cap,
.photo:active .photo__cap {
word-break: keep-all;
transform: translateY(0);
}
.photo.is-demo .photo__cap {
word-break: keep-all;
animation: slide-loop $dur-loop $easing infinite;
}
@keyframes slide-loop {
0%, 20% { transform: translateY(100%); }
50%, 75% { transform: translateY(0); }
100% { transform: translateY(100%); }
}
// 터치: 캡션은 콘텐츠라 hover 가 없는 기기에선 기본으로 보이게 한다
@media (hover: none) {
.photo__cap {
word-break: keep-all; transform: translateY(0); }
.photo.is-demo .photo__cap {
word-break: keep-all; animation: none; }
}
@media (prefers-reduced-motion: reduce) {
.photo__cap {
word-break: keep-all; transition: none; }
.photo.is-demo .photo__cap {
word-break: keep-all; animation: none; transform: translateY(0); }
}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);
}
.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);
outline: none;
cursor: pointer;
}
.photo__pic {
display: block;
position: relative;
aspect-ratio: 4/3;
background: linear-gradient(155deg, #2f6df6 0%, #17141a 100%);
}
.photo__sun {
position: absolute;
top: 14%;
right: 16%;
width: 18%;
aspect-ratio: 1;
border-radius: 50%;
background: #fff7e6;
}
.photo__hill {
position: absolute;
left: -8%;
right: -8%;
bottom: -10%;
height: 46%;
border-radius: 50% 50% 0 0/100% 100% 0 0;
background: #ff4d1f;
opacity: 0.55;
}
.photo__cap {
word-break: keep-all;
position: absolute;
left: 0;
right: 0;
bottom: 0;
display: grid;
gap: 2px;
padding: 12px 14px;
background: #fff7e6;
transform: translateY(100%);
transition: transform 300ms ease-out;
}
.photo__cap-title {
word-break: keep-all;
font-weight: 700;
font-size: clamp(12px, 3.4vw, 14px);
line-height: 1.3;
font-family: inherit;
color: #17141a;
}
.photo__cap-desc {
word-break: keep-all;
font-weight: 500;
font-size: clamp(10px, 2.8vw, 12px);
line-height: 1.3;
font-family: inherit;
color: #17141a;
}
.photo:hover .photo__cap,
.photo:focus-visible .photo__cap,
.photo:active .photo__cap {
word-break: keep-all;
transform: translateY(0);
}
.photo.is-demo .photo__cap {
word-break: keep-all;
animation: slide-loop 2s ease-out infinite;
}
@keyframes slide-loop {
0%, 20% {
transform: translateY(100%);
}
50%, 75% {
transform: translateY(0);
}
100% {
transform: translateY(100%);
}
}
@media (hover: none) {
.photo__cap {
word-break: keep-all;
transform: translateY(0);
}
.photo.is-demo .photo__cap {
word-break: keep-all;
animation: none;
}
}
@media (prefers-reduced-motion: reduce) {
.photo__cap {
word-break: keep-all;
transition: none;
}
.photo.is-demo .photo__cap {
word-break: keep-all;
animation: none;
transform: translateY(0);
}
}import type { CSSProperties } from "react";
import styles from "./CaptionSlidePhoto.module.scss";
export type CaptionSlidePhotoProps = {
title: string;
desc: string;
/** 캡션이 올라오는 시간 (기본 300ms) */
duration?: number;
easing?: string;
/** 캡션 배경 색 */
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function CaptionSlidePhoto({
title,
desc,
duration = 300,
easing = "ease-out",
color = "#fff7e6",
}: CaptionSlidePhotoProps) {
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>
<figcaption className={styles.cap}>
<strong className={styles.title}>{title}</strong>
<span className={styles.desc}>{desc}</span>
</figcaption>
</figure>
);
}03 Caption trượt lên — Diện tích đo được 6.603% · khung hình chuyển động 13/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

