純 CSS 的小魔法:做一套會“發(fā)光”的響應式畫廊
我想在應用里展示一組圖片,同時不引入任何笨重腳本。結果發(fā)現(xiàn),只用 CSS 就能優(yōu)雅解決:零 JavaScript 也能實現(xiàn)“點按放大”的動效與無障礙導航。
關鍵思路:結合 tabindex 屬性與 :focus、:focus-within 這兩個 CSS 偽類,在不犧牲性能的前提下,創(chuàng)造順滑而輕量的交互體驗。與此同時,我們還能兼顧鍵盤可達性與布局自適應,這讓組件在移動端與桌面端都更易用、更穩(wěn)定。
結構搭建(Setting Up the Structure)
如果你希望容納彈性數(shù)量的圖片,先準備一份可擴展的 HTML 結構,類似下面這樣。通過給根節(jié)點添加諸如 .image-gallery--4 的修飾類,我們可以按圖片數(shù)量輕松切換布局策略;因此,在后續(xù)樣式中會很方便地定制 1/2/3/4 張圖以及更多張圖的排布。
<div class="image-gallery image-gallery--4">
<div class="image-gallery__item">
<div class="image-gallery__backdrop" tabindex="-1">
<img
class="image-gallery__image"
src="https://picsum.photos/seed/7cb42b45/1500/750"
tabindex="0"
/>
</div>
</div>
<div class="image-gallery__item">
<div class="image-gallery__backdrop" tabindex="-1">
<img
class="image-gallery__image"
src="https://picsum.photos/seed/4dd8300c/1500/750"
tabindex="0"
/>
</div>
</div>
<div class="image-gallery__item">
<div class="image-gallery__backdrop" tabindex="-1">
<img
class="image-gallery__image"
src="https://picsum.photos/seed/b66a4549/1500/750"
tabindex="0"
/>
</div>
</div>
<div class="image-gallery__item">
<div class="image-gallery__backdrop" tabindex="-1">
<img
class="image-gallery__image"
src="https://picsum.photos/seed/5783fa29/1500/750"
tabindex="0"
/>
</div>
</div>
</div>- .image-gallery 根元素帶有“數(shù)量修飾類”(如 .image-gallery--4),因此后續(xù)可以用選擇器精確地調整行列與跨度。
- 每個 .image-gallery__item 內含“背板”元素,并設置 tabindex="-1":它可被點擊聚焦,但在鍵盤 Tab 導航時會被跳過,避免無意義的停留。
- 內層的 <img> 設置 tabindex="0",用戶既可點擊,也能通過 Tab / Shift+Tab 依次聚焦圖片。
基礎樣式(Let’s add some styles)
下面是畫廊的基礎網格、響應式斷點與單元尺寸。通過較為克制的漸變背景,我們還為圖片加載前提供了一個優(yōu)雅的占位態(tài)。
.image-gallery {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1px;
margin: 0 auto;
}
@media (min-width:1001px) {
.image-gallery {
grid-template-columns: repeat(4, 1fr);
}
}
.image-gallery__item {
width: 100%;
height: 100%;
aspect-ratio: 1400 / 650;
}
.image-gallery--1.image-gallery__item {
grid-column: 1 / -1;
}
@media (min-width:1001px) {
.image-gallery:is(.image-gallery--1) {
grid-template-rows: 1fr;
}
.image-gallery:not(.image-gallery--1, .image-gallery--3) {
grid-template-rows: 250px250px;
}
}
.image-gallery__item {
background: linear-gradient(
to bottom right,
#f5f5f5 0%,
#c5c5c5 50%,
#fff 100%
);
}
@media (min-width:1001px) {
.image-gallery:is(.image-gallery--2, .image-gallery--3)
.image-gallery__item:not(:nth-child(1)),
.image-gallery:not(.image-gallery--1, .image-gallery--2, .image-gallery--3)
.image-gallery__item:nth-child(4),
.image-gallery:not(
.image-gallery--1,
.image-gallery--2,
.image-gallery--3,
.image-gallery--4
)
.image-gallery__item:nth-child(n + 5) {
grid-column: span 2;
}
.image-gallery:not(.image-gallery--1).image-gallery__item:nth-child(1),
.image-gallery--2.image-gallery__item:nth-child(2) {
grid-column: span 2;
grid-row: span 2;
}
}
.image-gallery__backdrop {
width: 100%;
height: 100%;
transition: background-color 160ms;
z-index: 1;
}
.image-gallery__image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
cursor: zoom-in;
user-select: none;
outline: none;
}加入交互(Adding Interactivity)
現(xiàn)在讓畫廊“活起來”。思路是:當子元素圖片被聚焦時,通過 :focus-within 讓父級背板切換為 position: fixed 的“遮罩層”,并在其中居中展示被點開的圖片;點擊遮罩區(qū)域即可收起。
.image-gallery__backdrop:not(:focus):focus-within {
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.1);
cursor: zoom-out;
z-index: 2;
user-select: none;
}接著,當圖片本身處于 :focus 時,設置其尺寸為自適應容器,并以 object-fit: contain 居中完整顯示,同時用一個簡短的 @keyframes 提升放大動效的順滑度。
.image-gallery__image:focus {
width: auto;
max-width: 90%;
height: auto;
max-height: 90%;
object-fit: contain;
border-radius: 8px;
pointer-events: none;
z-index: 3;
animation: image-gallery-zoom-in 160ms;
}
@keyframes image-gallery-zoom-in {
0% {
opacity: 0;
transform: scale(0.98);
}
}這里加入了 pointer-events: none,因此用戶在放大態(tài)點擊圖片時,事件會“穿透”至背板,從而一鍵關閉。與此同時,tabindex="0" 讓圖片天然可聚焦——你可以用 Tab 前進、用 Shift+Tab 后退,順暢瀏覽整組圖片。
為了向用戶明確鍵盤用法,我們再加一個輕量指示條:
<div class="image-gallery__keyboard-indicator">
Use <code>Tab</code> or <code>Shift</code>+<code>Tab</code> to navigate between images
</div>.image-gallery__keyboard-indicator {
display: none;
position: fixed;
bottom: 26px;
left: 50%;
padding: 8px12px;
font-size: 14px;
background-color: #f5f5f5;
border-radius: 4px;
transform: translateX(-50%);
z-index: 4;
}
@media (pointer: fine) {
.image-gallery:has(.image-gallery__image:focus)
.image-gallery__keyboard-indicator {
display: block;
}
}
.image-gallery__keyboard-indicator > code {
padding: 2px4px;
font-size: calc(1rem - 2px);
background-color: #fff;
border: 1px solid #c2c2c2;
border-radius: 4px;
}完整示例(Full Demo)
下面是整套實現(xiàn)的可運行示例,包含結構、樣式與交互邏輯。你可以直接復制到本地文件中打開,或集成到你的組件庫里。
圖片
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Three-Column Grid With Subgrid</title>
<style>
.image-gallery {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1px;
margin: 0 auto;
}
@media (min-width:1001px) {
.image-gallery {
grid-template-columns: repeat(4, 1fr);
}
}
.image-gallery__item {
width: 100%;
height: 100%;
aspect-ratio: 1400 / 650;
}
.image-gallery--1.image-gallery__item {
grid-column: 1 / -1;
}
@media (min-width:1001px) {
.image-gallery:is(.image-gallery--1) {
grid-template-rows: 1fr;
}
.image-gallery:not(.image-gallery--1, .image-gallery--3) {
grid-template-rows: 250px250px;
}
}
.image-gallery__item {
background: linear-gradient(
to bottom right,
#f5f5f5 0%,
#c5c5c5 50%,
#fff 100%
);
}
@media (min-width:1001px) {
.image-gallery:is(.image-gallery--2, .image-gallery--3)
.image-gallery__item:not(:nth-child(1)),
.image-gallery:not(
.image-gallery--1,
.image-gallery--2,
.image-gallery--3
)
.image-gallery__item:nth-child(4),
.image-gallery:not(
.image-gallery--1,
.image-gallery--2,
.image-gallery--3,
.image-gallery--4
)
.image-gallery__item:nth-child(n + 5) {
grid-column: span 2;
}
.image-gallery:not(.image-gallery--1).image-gallery__item:nth-child(1),
.image-gallery--2.image-gallery__item:nth-child(2) {
grid-column: span 2;
grid-row: span 2;
}
}
.image-gallery__backdrop {
width: 100%;
height: 100%;
transition: background-color 160ms;
z-index: 1;
}
.image-gallery__backdrop:not(:focus):focus-within {
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.1);
cursor: zoom-out;
z-index: 2;
user-select: none;
}
.image-gallery__image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
cursor: zoom-in;
user-select: none;
outline: none;
}
.image-gallery__image:focus {
width: auto;
max-width: 90%;
height: auto;
max-height: 90%;
object-fit: contain;
border-radius: 8px;
pointer-events: none;
z-index: 3;
animation: image-gallery-zoom-in 160ms;
}
@keyframes image-gallery-zoom-in {
0% {
opacity: 0;
transform: scale(0.98);
}
}
.image-gallery__keyboard-indicator {
display: none;
position: fixed;
bottom: 26px;
left: 50%;
padding: 8px12px;
font-size: 14px;
background-color: #f5f5f5;
border-radius: 4px;
transform: translateX(-50%);
z-index: 4;
}
@media (pointer: fine) {
.image-gallery:has(.image-gallery__image:focus)
.image-gallery__keyboard-indicator {
display: block;
}
}
.image-gallery__keyboard-indicator > code {
padding: 2px4px;
font-size: calc(1rem - 2px);
background-color: #fff;
border: 1px solid #c2c2c2;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="image-gallery image-gallery--4">
<div class="image-gallery__item">
<div class="image-gallery__backdrop" tabindex="-1">
<img
class="image-gallery__image"
src="https://picsum.photos/seed/7cb42b45/1500/750"
tabindex="0"
/>
</div>
</div>
<div class="image-gallery__item">
<div class="image-gallery__backdrop" tabindex="-1">
<img
class="image-gallery__image"
src="https://picsum.photos/seed/4dd8300c/1500/750"
tabindex="0"
/>
</div>
</div>
<div class="image-gallery__item">
<div class="image-gallery__backdrop" tabindex="-1">
<img
class="image-gallery__image"
src="https://picsum.photos/seed/b66a4549/1500/750"
tabindex="0"
/>
</div>
</div>
<div class="image-gallery__item">
<div class="image-gallery__backdrop" tabindex="-1">
<img
class="image-gallery__image"
src="https://picsum.photos/seed/5783fa29/1500/750"
tabindex="0"
/>
</div>
</div>
<div class="image-gallery__keyboard-indicator">
Use <code>Tab</code> or <code>Shift</code>+<code>Tab</code> to navigate
between images
</div>
</div>
</body>
</html>結語
今天我們用純 CSS 完成了一套圖片畫廊:既有響應式布局,又具備“點擊放大 + 鍵盤導航”的交互;既輕量易集成,又不依賴任何 JavaScript。因此你可以把重心放在圖片質量與內容組織上,與此同時保持頁面性能與可訪問性;盡管如此,若你后續(xù)確實需要更復雜的縮略圖預覽、分組輪播或拖拽排序,也可以在這個基礎上再按需加上一點腳本擴展。






























