经纬志
首页 / 实战手册 / 样式与渲染

两层半透明叠在一起,颜色比预期深了一倍

先看结论

两个 50% 透明的图层叠上去,重叠区看起来像 75% 不透明,设计稿完全对不上。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

蓝色面图层 fill-opacity: 0.5,红色面图层 fill-opacity: 0.5。 重叠区域显示为暗紫色,整体亮度明显低于任一单层, 与设计稿标注的「两层各 50%,叠加后仍能看清底图」不符。

用取色器量:单层区域 #7fa8d8,重叠区 #6c7ab0, 明度下降超过 20%。

原因

透明度不是相加,而是按 source-over 合成:

α_result = α_top + α_bottom × (1 - α_top)
α = 0.5 + 0.5 × 0.5 = 0.75

两层各 50% 后,重叠区实际不透明度是 75%,不是 50%。 如果图层容器还设置了 CSS opacity,它会在合成之后再乘一次: 0.75 × 0.8 = 0.6。

Leaflet 的 opacity 选项作用于整个 pane(CSS opacity), fillOpacity 作用于单个路径,两者会相乘。

fill-opacitylayer opacity实际不透明度
0.51.00.5
0.50.80.4
0.5 + 0.5 两层1.00.75
0.5 + 0.5 两层0.80.6

排查过程

  1. 在 Elements 面板看面图层所在 pane 的 opacity 样式值,是否小于 1。
  2. 看路径的 fill-opacity 属性,与图层选项是否重复设置。
  3. 用取色器量单层与重叠区的实际色值,反算不透明度。
  4. 检查是否有全局 CSS 对 .leaflet-overlay-pane svg 设置了 opacity。
const pane = map.getPane('overlayPane');
console.log(getComputedStyle(pane).opacity);        // 0.8  <- 额外的乘数
console.log(getComputedStyle(pane.querySelector('path')).fillOpacity);  // 0.5

解决方案

只在一个地方控制透明度。需要看穿底图时降低 fillOpacity, 不要同时设图层 opacity。

// 正确:只设 fillOpacity
L.geoJSON(blue, {
  style: { fillColor: '#1a6fd4', fillOpacity: 0.35, weight: 1, opacity: 0.9 }
}).addTo(map);

// 若必须叠加两层并保持视觉一致,把单层透明度按合成公式反算
// 目标重叠区 0.5,则单层 α 满足 2α - α² = 0.5  -> α ≈ 0.293
L.geoJSON(red, {
  style: { fillColor: '#e8590c', fillOpacity: 0.293, weight: 1 }
}).addTo(map);

Mapbox GL 中 fill-opacity 与图层 opacity 同样是相乘关系, 用 ['*', 0.5, ['get','alpha']] 这类表达式时要注意最终的乘积。

map.setPaintProperty('area-blue', 'fill-opacity', 0.35);
map.setLayoutProperty('area-blue', 'visibility', 'visible');
map.setPaintProperty('area-blue', 'fill-color', '#1a6fd4');
// 不要再用 map.setPaintProperty('area-blue', 'fill-opacity-transition', ...) 叠 opacity

验证

单层区域与重叠区的色差不超过 8%,取色器读出的重叠区色值应接近设计稿; pane 的 computed opacity 为 1。

console.assert(getComputedStyle(map.getPane('overlayPane')).opacity === '1', '仍有额外透明度');
// 色差检查:用 canvas 取两点像素比较
const ctx = document.querySelector('canvas').getContext('2d');
console.assert(Math.abs(ctx.getImageData(320, 240, 1, 1).data[0] -
                     ctx.getImageData(520, 240, 1, 1).data[0]) < 20, '色差过大');

适用版本

库版本说明
Leaflet1.9.xopacity 作用于 pane,fillOpacity 作用于路径
Mapbox GL JS2.15 / 3.xfill-opacity 与 layer opacity 相乘
CSS 合成source-overα = α_top + α_bottom(1 - α_top)
引用本文

经纬志(2026)。《两层半透明叠在一起,颜色比预期深了一倍》。https://www.jingweizhi.com/cookbook/opacity-multiply.html(访问日期 2026-11-03)。