如何动态修改 HTML 元素的共享样式,统一调整 leaflet 地图标记的 opacity?
可以用 CSS 自定义属性保存各状态的透明度,再通过原生 JavaScript 的 style.setProperty() 修改这些值。引用对应变量的标记会自动更新,不用逐个修改元素,也不需要 jQuery。
建议继续用 class 表示 found、notfound 等状态,用 CSS 自定义属性保存用户的显示设置。
用 CSS 自定义属性控制透明度
假设地图容器的 ID 是 map:
#map {
--found-opacity: 0.3;
}
#map .found {
opacity: var(--found-opacity, 0.3);
}
通过 style.setProperty() 修改透明度:
const mapElement = document.getElementById("map");
mapElement.style.setProperty("--found-opacity", "0.6");
这段代码修改的是地图容器上的自定义属性。普通的 CSS 自定义属性默认由后代元素继承,所以地图内使用 var(--found-opacity) 的元素都会采用新值。之后添加的标记只要带有 found class,也会使用当前设置。
var(--found-opacity, 0.3) 中的 0.3 是变量未定义时的回退值。示例中的数值范围为 0 到 1:0 表示完全透明,1 表示完全不透明,0.3 对应 30% 不透明度。
如果要让设置作用于整个页面,可以把变量定义在 :root 上,再这样修改:
document.documentElement.style.setProperty(
"--found-opacity",
"0.6"
);
变量应在定义它的位置修改。如果 #map 自己定义了同名变量,就会优先使用自己的值。
通过滑块实时调整
在地图容器外放置一个滑块:
<label for="found-opacity">已找到物品的不透明度</label>
<input
id="found-opacity"
type="range"
min="0"
max="1"
step="0.05"
value="0.3"
>
等这些 DOM 元素创建后,执行以下代码:
const mapElement = document.getElementById("map");
const opacityInput = document.getElementById("found-opacity");
function updateFoundOpacity() {
mapElement.style.setProperty(
"--found-opacity",
opacityInput.value
);
}
opacityInput.addEventListener("input", updateFoundOpacity);
updateFoundOpacity();
拖动滑块时,input 事件会持续触发。每次只需更新一个变量,浏览器就会重新计算相关元素的样式。
现有的状态切换代码可以保留,添加、移除 class 的分支可以简化为:
div.classList.toggle("found", setFound);
第二个参数为 true 时添加 class,为 false 时移除。
给互斥状态分别设置样式
增加其他状态时,可以为每个状态设置一个变量:
#map {
--found-opacity: 0.3;
--notfound-opacity: 1;
--notfindable-opacity: 0.1;
}
#map .found {
opacity: var(--found-opacity);
}
#map .notfound {
opacity: var(--notfound-opacity);
}
#map .notfindable {
opacity: var(--notfindable-opacity);
}
各个设置控件修改对应的变量即可:
mapElement.style.setProperty("--found-opacity", "0");
mapElement.style.setProperty("--notfound-opacity", "0.8");
mapElement.style.setProperty("--notfindable-opacity", "0.2");
CSS 不会自动保证这些 class 互斥。切换状态时,应先移除旧状态,再添加新状态。否则,多个规则同时命中,结果就会受 CSS 层叠顺序影响。
对于普通的 Leaflet L.Marker,可以通过 getElement() 获取标记根元素:
const markerStates = ["found", "notfound", "notfindable"];
function applyMarkerState(marker, state) {
if (!markerStates.includes(state)) {
throw new Error(`Unknown marker state: ${state}`);
}
const element = marker.getElement();
// 标记尚未加入地图时,可能还没有对应的 DOM 元素。
if (!element) return;
element.classList.remove(...markerStates);
element.classList.add(state);
}
把 class 加在标记根元素上,PNG 图标和包含 SVG 的 L.DivIcon 就能使用同一套规则。根元素内部的 SVG 也会随整个标记一起改变透明度。
业务状态应保存在物品数据中,再同步到 DOM。假设 marker 已经创建,可以这样处理:
const item = {
state: "notfound"
};
function renderState() {
applyMarkerState(marker, item.state);
}
marker.on("add", renderState);
marker.on("contextmenu", () => {
if (item.state === "notfindable") return;
item.state = item.state === "found" ? "notfound" : "found";
renderState();
});
renderState();
监听 add 可以在标记加入或重新加入地图时恢复状态。如果调用 setIcon() 更换图标后产生了新的 DOM 元素,也需要再执行一次 renderState()。
如果多个标记代表同一物品,建议按物品 ID 保存它们的引用,在状态变化时同步更新。alt 是替代文本,不适合作为业务对象的唯一标识。直接将含有引号等字符的 alt 值拼接到选择器中,还可能导致选择器失效。
留意 Leaflet 写入的内联样式
如果变量已经改变,标记外观却没有变化,可以检查标记元素上的 style.opacity。Leaflet 的 opacity 选项、setOpacity() 或图标插件可能会写入内联透明度,覆盖普通样式表中的 opacity。
如果由 CSS 统一管理状态透明度,可以在标记根元素上覆盖普通内联样式:
#map .leaflet-marker-icon.found {
opacity: var(--found-opacity, 0.3) !important;
}
其他状态也可以按需使用相同写法。这里的 !important 让状态样式优先于普通内联透明度。采用这种方式后,应避免再用 setOpacity() 管理同一元素的状态透明度,以免两套设置相互干扰。
也可以完全使用 Leaflet API:
marker.setOpacity(0.3);
如果已经集中管理标记对象,并且希望连同标记阴影一起调整,这种方式比较合适。用户改变全局设置时,需要遍历相应状态的标记并调用 setOpacity(),新标记也要应用当前设置。只修改图标元素的 CSS,不会自动同步独立的阴影元素。
这些做法适用于 DOM 标记。L.CircleMarker、其他 Path 或 Canvas 绘制对象应使用对应的样式 API,不能直接套用 DOM class 的处理方式。
完全透明与隐藏的区别
opacity: 0 会让图标看不见,但元素仍然存在,也仍可能接收鼠标事件。如果隐藏后不应响应指针操作,还需要处理交互,例如切换一个专用 class:
#map .leaflet-marker-icon.is-hidden {
opacity: 0 !important;
pointer-events: none;
}
pointer-events: none 只处理指针命中。如果还要阻止键盘聚焦,应另行处理焦点,或通过 Leaflet 从地图中移除该标记。这样隐藏的标记也无法再通过右键恢复,因此需要在设置控件等仍可操作的位置提供恢复入口。
如果确实需要直接修改共享 CSS 规则,可以使用 CSSOM 的 CSSStyleRule.style.setProperty()。不过,查找规则和处理跨域样式表的访问限制会增加代码。对于规则固定、数值由用户调整的需求,CSS 自定义属性更容易维护。
备注:内容仅供参考。
