使用場景
在生產環境中,遇到一個需求,需要在一個深色風格的大屏頁面中,嵌入 Google Maps。為了減少違和感,希望地圖四邊能夠淡出過渡。
這裡的“淡出過渡”,關鍵是淡出,而非降低透明度。
基於 Google Maps 的深色示例中,附加上述需求,效果如下:
簡單的說,就是中間放地圖,四周放標題和其它展板內容。
CSS mask-image + SVG
簡化一下,把地圖換成圖片,實現一個示例。
示例中,註釋掉“mask”標記的內容,恢復“svg test”標記的內容,可以檢視 svg 。
準備工作,定義一個“容器”和“目標”層:
<div id="container">
<img id="target" src="https://cdn.pixabay.com/photo/2024/07/28/09/04/mountain-8927018_1280.jpg">
<!-- svg test -->
<!-- <div id="target" style="width:1920px;height:1080px;"></div> -->
</div>
基礎樣式:
body {
margin: 0;
background-color: black;
}
#container {
position: absolute;
width: 100%;
height: 100%;
background-repeat: repeat;
display: flex;
align-items: center;
justify-content: center;
}
#target {
max-width: 80%;
max-height: 80%;
/* mask */
-webkit-mask-mode: alpha;
mask-mode: alpha;
mask-repeat: no-repeat;
mask-size: 100% 100%;
/* svg test */
/* background-repeat: no-repeat;
background-size: 100% 100%; */
}
給“容器”新增一個波點背景,為了驗證淡出過渡區域可以透視背景,這裡直接用 svg 實現:
(function() {
const container = document.querySelector('#container');
const containerBg = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30"><circle fill="rgba(255,255,255,0.1)" cx="15" cy="15" r="10" /></svg>`;
container.style.backgroundImage = `url('data:image/svg+xml;utf8,${encodeURIComponent(containerBg)}')`;
// 略
})();
接著給“目標”準備一個處理方法,如果目標是一個圖片,為了獲得圖片大小,將在圖片的 onload 中執行:
(function() {
// 略
const target = document.querySelector('#target');
function setTargetBg() {
// 略
}
target.onload = setTargetBg
setTargetBg()
})();
為了實現淡出過渡效果,需要準備一個 svg:
分為 4+1 塊,上下左右 4 個梯形 path,中間 1 個矩形 rect。
4 個梯形分別設定了 4 個方向的 linearGradient 漸變。
這裡用程式碼繪製上面的 svg:
svg 的寬高是基於“目標”的寬高,淡入過渡區域大小 padding 基於“目標”短邊的 20%。
特別地,patch 和 rect 中的加減“1”,目的是為了消除 path 之間的縫隙。
function setTargetBg() {
const svgWidth = target.offsetWidth,
svgHeight = target.offsetHeight,
padding = Math.floor(Math.min(target.offsetWidth, target.offsetHeight) * 0.2),
fill = 'white',
patch = 0.2;
const targetMask = `
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
width="${svgWidth}"
height="${svgHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}">
<defs>
<linearGradient id="mask-bottom-to-top" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="transparent" />
<stop offset="100%" stop-color="${fill}" />
</linearGradient>
<linearGradient id="mask-top-to-bottom" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="${fill}" />
<stop offset="100%" stop-color="transparent" />
</linearGradient>
<linearGradient id="mask-rigth-to-left" x1="0" x2="1" y1="0" y2="0">
<stop offset="0%" stop-color="transparent" />
<stop offset="100%" stop-color="${fill}" />
</linearGradient>
<linearGradient id="mask-left-to-right" x1="0" x2="1" y1="0" y2="0">
<stop offset="0%" stop-color="${fill}" />
<stop offset="100%" stop-color="transparent" />
</linearGradient>
</defs>
<path fill="url(#mask-bottom-to-top)" d="M0,0 L${svgWidth},0 L${svgWidth - padding + patch},${padding + patch} L${padding - patch},${padding + patch} Z"></path>
<path fill="url(#mask-top-to-bottom)" d="M0,${svgHeight} L${padding - patch},${svgHeight - padding - patch} L${svgWidth - padding + patch},${svgHeight - padding - patch} L${svgWidth},${svgHeight} Z"></path>
<path fill="url(#mask-rigth-to-left)" d="M0,0 L${padding + patch},${padding} L${padding + patch},${svgHeight - padding} L0,${svgHeight} Z"></path>
<path fill="url(#mask-left-to-right)" d="M${svgWidth},0 L${svgWidth - padding - patch},${padding} L${svgWidth - padding - patch},${svgHeight - padding} L${svgWidth},${svgHeight} Z"></path>
<rect x="${padding - 1}" y="${padding - 1}" width="${svgWidth - padding * 2 + 1 * 2}" height="${svgHeight - padding * 2 + 1 * 2}" fill="${fill}"></rect>
</svg>
`;
// mask
target.style.maskImage = `url('data:image/svg+xml;utf8,${encodeURIComponent(targetMask.replace(/\n/g, ''))}')`;
// svg test
// target.style.backgroundImage = `url('data:image/svg+xml;utf8,${encodeURIComponent(targetMask.replace(/\n/g, ''))}')`;
}
最終效果:
線上Demo