HTML + CSS 創建圖片倒影的 5 種方法
目標:掌握多種生成“圖片倒影 / Reflection”效果的實現思路,理解兼容性、性能差異與最佳實踐,方便在真實業務(商品展示、相冊、登陸頁面視覺強化)中安全使用。
總覽對比
方法 | 核心技術 | 代碼量 | 兼容性 | 動態內容適配 | 可控性 | 適用場景 |
---|---|---|---|---|---|---|
1. -webkit-box-reflect | 私有 CSS 屬性 | 最少 | 僅 WebKit (Chrome / Safari / Edge) | 自動 | 中 | 快速 Demo / 營銷頁 |
2. 偽元素 + transform: scaleY(-1) | 標準 CSS | 少 | 所有現代瀏覽器 | 良好 | 高(可自定義遮罩) | 通用首選 |
3. 偽元素 + mask-image / -webkit-mask | CSS Mask | 中 | Safari / Chromium (Firefox 部分實驗) | 良好 | 很高(漸隱更自然) | 高端展示 |
4. SVG 復制 + 漸變遮罩 | 內聯 SVG | 中 | 全面 (IE 除外) | 良好 | 很高(濾鏡/形變) | 復雜視覺 / 批量渲染 |
5. Canvas 二次繪制 | <canvas> | 高 | 全面 | 需手動重繪 | 最高(像素級) | 動態生成 / 后處理 |
選擇建議:
- 追求最少代碼 & 不顧部分瀏覽器:用 1。
- 需要兼容性 + 易維護:用 2。
- 想要柔和漸隱過渡、無多余 DOM:用 3。
- 大型可視化 / 復雜濾鏡鏈:用 4。
- 需要最終導出合成圖 / 動態內容(如生成分享海報):用 5。
方法一:-webkit-box-reflect
(最簡單 / 兼容性受限)
<style>.reflect-webkit {width: 240px;-webkit-box-reflect: below 6px linear-gradient(to bottom, rgba(0, 0, 0, 0.25), transparent70%);}
</style>
<img class="reflect-webkit" src="demo.jpg" alt="Product" />
說明:
- 語法:
-webkit-box-reflect: <direction> <offset> <mask>
linear-gradient
充當倒影的漸隱遮罩。
優點:單行 + 自動跟隨寬高。
缺點:僅 WebKit 內核(Firefox 不支持)。
適合:臨時視覺增強、非核心信息。
方法二:偽元素復制 + 反轉(推薦通用方案)
思路:利用容器包裹圖片,偽元素 ::after
再繪制同一張圖像,垂直翻轉并添加漸變。
<div class="reflection-box"><img src="demo.jpg" alt="Phone" />
</div><style>.reflection-box {position: relative;width: 240px;}.reflection-box img {display: block;width: 100%;}.reflection-box::after {content: '';position: absolute;left: 0;right: 0;top: 100%;height: 100%;background: url('demo.jpg') center/cover no-repeat;transform: scaleY(-1);transform-origin: top;opacity: 0.6;/* 漸隱疊加 */mask-image: linear-gradient(to bottom,rgba(0, 0, 0, 0.8),rgba(0, 0, 0, 0));-webkit-mask-image: linear-gradient(to bottom,rgba(0, 0, 0, 0.8),rgba(0, 0, 0, 0));}
</style>
處理動態 src:
- 如果圖片 URL 需動態綁定,可用行內
style="--src:url('xxx')"
+background: var(--src)
或用 JS 設置偽元素。 - 亦可直接復制
<img>
節點再scaleY(-1)
,如下:
<div class="reflect-wrap"><img src="demo.jpg" alt="Phone" class="origin" /><img src="demo.jpg" alt="Phone reflection" class="mirror" />
</div><style>.reflect-wrap {width: 240px;position: relative;}.reflect-wrap .origin {display: block;width: 100%;}.reflect-wrap .mirror {display: block;width: 100%;transform: scaleY(-1);transform-origin: top;margin-top: 6px;mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.6), transparent);-webkit-mask-image: linear-gradient(to bottom,rgba(0, 0, 0, 0.6),transparent);opacity: 0.8;}
</style>
優點:標準、兼容好、可擴展。
注意:雙份圖片會觸發兩次解碼,可用 <img decoding="async" loading="lazy">
或復用繪制(JS 畫到 canvas 再生成 DataURL)。
方法三:偽元素 + Mask(強化漸隱 & 靈活形狀)
核心區別:不直接復制圖片,而是使用 CSS 變量引用同一來源,搭配 mask-image
形成更可控的透明衰減(可變換曲線)。
<figure class="mask-reflect" style="--src:url('demo.jpg')"><img src="demo.jpg" alt="Laptop" />
</figure><style>.mask-reflect {position: relative;width: 260px;}.mask-reflect img {width: 100%;display: block;}.mask-reflect::after {content: '';position: absolute;inset: 0;top: 100%;height: 100%;background: var(--src) center/cover no-repeat;transform: scaleY(-1);transform-origin: top;/* 自定義遮罩——使用非線性漸變模擬更柔的衰減 */mask-image: linear-gradient(to bottom,rgba(0, 0, 0, 0.9) 0%,rgba(0, 0, 0, 0.4) 35%,rgba(0, 0, 0, 0.15) 60%,rgba(0, 0, 0, 0) 100%);-webkit-mask-image: linear-gradient(to bottom,rgba(0, 0, 0, 0.9) 0%,rgba(0, 0, 0, 0.4) 35%,rgba(0, 0, 0, 0.15) 60%,rgba(0, 0, 0, 0) 100%);opacity: 0.85;filter: blur(0.4px) saturate(0.95);}
</style>
拓展:
- 橫向拉伸 / 傾斜效果:附加
transform: scaleY(-1) skewX(3deg);
- 波紋倒影:疊加
filter: url(#turbulence)
(需 SVG filter)。
方法四:SVG 復制 + 漸變遮罩
適合批量渲染(一個 SVG 中包含多個倒影)或需要濾鏡(模糊、色偏、波浪)。
<svg width="260" viewBox="0 0 260 360" class="svg-reflect"><defs><linearGradient id="fade" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="black" stop-opacity="0.8" /><stop offset="70%" stop-color="black" stop-opacity="0" /></linearGradient><mask id="mask-fade" maskUnits="userSpaceOnUse"><rect x="0" y="180" width="260" height="180" fill="url(#fade)" /></mask></defs><!-- 原圖 --><image href="demo.jpg" x="0" y="0" width="260" height="180" /><!-- 倒影 --><g transform="translate(0,180) scale(1,-1)" mask="url(#mask-fade)"><image href="demo.jpg" x="0" y="0" width="260" height="180" opacity="0.9" /></g>
</svg>
優點:
- 所有可視操作可在 SVG 內完成(模糊、波紋、顏色矩陣)。
- 單文件可復制多份資源。
缺點:語法冗長;與普通 DOM 混排需處理層級。
擴展濾鏡(波紋):
<filter id="ripple" x="-20%" y="-20%" width="140%" height="140%"><feTurbulence baseFrequency="0.01 0.15" numOctaves="2" result="turb"/><feDisplacementMap in2="turb" in="SourceGraphic" scale="6" xChannelSelector="R" yChannelSelector="G"/>
</filter>
然后在倒影 <g>
里加 filter="url(#ripple)"
。
方法五:Canvas 動態繪制(可導出 / 像素級)
適合:
- 需要合成單張最終圖(下載 / 分享)。
- 倒影需要與原圖進行額外像素操作(模糊、曲面映射、噪聲)。
<canvas id="c" width="260" height="360"></canvas>
<script>const img = new Image();img.src = 'demo.jpg';img.onload = () => {const h = 180;const w = 260;const canvas = document.getElementById('c');const ctx = canvas.getContext('2d');// 原圖ctx.drawImage(img, 0, 0, w, h);// 倒影:縮放坐標系ctx.save();ctx.translate(0, h * 2); // 移到下面ctx.scale(1, -1);ctx.drawImage(img, 0, 0, w, h);ctx.restore();// 漸隱:創建漸變遮罩const gradient = ctx.createLinearGradient(0, h, 0, h * 2);gradient.addColorStop(0, 'rgba(0,0,0,0.6)');gradient.addColorStop(0.7, 'rgba(0,0,0,0)');ctx.globalCompositeOperation = 'destination-in';ctx.fillStyle = gradient;ctx.fillRect(0, h, w, h);ctx.globalCompositeOperation = 'source-over';};
</script>
延伸:
- 添加 Blur:使用離屏 canvas 再
ctx.filter = 'blur(2px)'
。 - 曲面:對每一行像素裁剪后重新繪制(實現較復雜)。
- 封裝為函數供多個圖片批量處理。
性能與內存考量
關注點 | 說明 | 建議 |
---|---|---|
重復解碼 | 偽元素背景與 <img> 均觸發 | 使用緩存(同 src 瀏覽器已緩存),或使用單 <img> +CSS 復制(方法二第二種 DOM 方式) |
Reflow | 倒影高度改變影響布局 | 給容器固定高度 / 使用絕對定位避免抖動 |
重繪成本 | CSS 濾鏡 / SVG 濾鏡會增加渲染成本 | 降低濾鏡強度;按需加載(IntersectionObserver) |
Canvas 占用 | 多張大圖 + 離屏操作 | 控制尺寸,復用 canvas |
動態數據 | src 頻繁變化 | Debounce 更新;避免在視口外構建倒影 |
常見坑 & 解決方案
問題 | 場景 | 解決 |
---|---|---|
圖片加載時閃爍 | 背景方式倒影先出現空白 | 用 onload 后再添加反射 class |
高 DPI 模糊 | Canvas 導出在 Retina 模糊 | canvas.width = cssWidth * devicePixelRatio 再縮放繪制 |
Mask 失效 | Firefox 對 mask-image 支持差異 | 降級:在 Firefox 檢測后改用透明漸變 PNG 覆蓋 |
濾鏡太重卡頓 | 多個 blur(8px) | 降級為較小 blur + opacity |
SEO 影響 | 額外 <img> 計入圖片索引 | 第二種 DOM 復制時給倒影圖 aria-hidden="true" + alt="" |
可復用 CSS 片段(方法二封裝)
.reflect {position: relative;display: inline-block;
}
.reflect > img {display: block;
}
.reflect::after {content: '';position: absolute;left: 0;right: 0;top: 100%;height: var(--reflect-height, 100%);background: var(--reflect-src) center/contain no-repeat;transform: scaleY(-1);transform-origin: top;opacity: var(--reflect-opacity, 0.7);mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.9), transparent);-webkit-mask-image: linear-gradient(to bottom,rgba(0, 0, 0, 0.9),transparent);
}
使用:
<divclass="reflect"style="--reflect-src:url('demo.jpg'); --reflect-height:100%; --reflect-opacity:.6"
><img src="demo.jpg" alt="Phone" />
</div>
JS 動態設置:
const wrap = document.querySelector('.reflect');
const img = wrap.querySelector('img');
img.addEventListener('load', () => {wrap.style.setProperty('--reflect-src',`url('${img.currentSrc || img.src}')`);
});
進階創意玩法
創意 | 說明 | 關鍵點 |
---|---|---|
波浪倒影 | 倒影加 feTurbulence 位移 | SVG 過濾或 Canvas 位移 |
動態閃光 | 倒影上加漸變動畫 | keyframes 改變 mask 的角度 |
顏色偏移 | 倒影輕微冷色 | filter: hue-rotate(5deg) saturate(.9) |
模糊加速 | 滾動遠離時加大模糊 | IntersectionObserver + class 切換 |
多層折射 | 多個偽元素多次縮放 | 性能注意:限制層數 |
選擇決策樹(簡化)
- 是否需要導出最終合成圖?→ 是:Canvas。
- 是否需要復雜濾鏡 / 波紋?→ 是:SVG / Canvas。
- 是否關鍵頁面全平臺必須統一?→ 是:偽元素方案。
- 是否只是臨時視覺點綴且不在意 Firefox?→
-webkit-box-reflect
。 - 想要更絲滑衰減邊緣?→ 增強 Mask(方法三)。
快速小抄(Cheat Sheet)
目標 | 推薦代碼片段 |
---|---|
最快 Demo | -webkit-box-reflect: below 4px linear-gradient(...) |
通用實現 | 偽元素 + scaleY(-1) + mask-image |
柔和漸隱 | 多段 stop 的 linear-gradient 遮罩 |
波紋特效 | SVG feTurbulence + feDisplacementMap |
導出圖片 | Canvas 繪制 + toDataURL() |
性能優化 | 只為首屏可見元素添加倒影 / 復用 src |
總結
圖片倒影本質是:復制 → 翻轉 → 衰減透明度 +(可選)附加濾鏡。選型關鍵在 兼容性、性能、是否需后處理 / 導出、視覺精細度 之間的平衡。實際項目中建議封裝成組件(接收:圖片源、倒影高度、衰減曲線、濾鏡強度),統一管理,避免隨處散落臨時寫法。
思路優先,技巧其次。掌握“復制 + 翻轉 + 遮罩”內核,再延伸任意創意。
(完)