Tile Filter
Runnable Leaflet example: Tile Filter. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.
single HTML file · CDN dependencies onlyinterface may contain Chinese labels
Source
<!--
示例:瓦片滤镜
要点:CSS filter 作用于 tilePane、五种配色、矢量层不受影响、切换类名即可生效
依赖:Leaflet 1.9.4 · jsDelivr CDN + Esri 瓦片
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>瓦片滤镜</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css">
<style>
html,body{height:100%;margin:0}
body{display:flex;flex-direction:column;overflow:hidden;
font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif}
.bar{flex:0 0 auto;padding:8px 12px;background:#f6f7f9;border-bottom:1px solid #e3e6ea;
display:flex;gap:8px;align-items:center;font-size:12px;color:#58606e;flex-wrap:wrap}
.bar button{font:inherit;font-size:12px;padding:4px 11px;border-radius:4px;cursor:pointer;
border:1px solid #ccd1d9;background:#fff;color:#16181d}
.bar button:hover{background:#eef0f3}
.bar button.is-on{background:#1f5fd0;border-color:#1f5fd0;color:#fff}
.bar .spacer{flex:1 1 auto}
#map{flex:1 1 auto;min-height:0}
/* 只作用于瓦片容器,.leaflet-overlay-pane 里的矢量不受影响 */
#map.f-gray .leaflet-tile-pane{filter:grayscale(1) contrast(1.12) brightness(1.02)}
#map.f-night .leaflet-tile-pane{filter:invert(1) hue-rotate(180deg) brightness(.82) contrast(1.15)}
#map.f-warm .leaflet-tile-pane{filter:sepia(.38) saturate(1.35) hue-rotate(-12deg)}
#map.f-contrast .leaflet-tile-pane{filter:contrast(1.55) saturate(1.2)}
</style>
</head>
<body>
<div class="bar">
<span>滤镜</span>
<button class="fx is-on" type="button" data-fx="">原色</button>
<button class="fx" type="button" data-fx="f-gray">灰度</button>
<button class="fx" type="button" data-fx="f-night">夜间</button>
<button class="fx" type="button" data-fx="f-warm">暖色</button>
<button class="fx" type="button" data-fx="f-contrast">高对比</button>
<span class="spacer"></span>
<span id="status"></span>
</div>
<div id="map"></div>
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
(function () {
'use strict';
var map = L.map('map', {
center: [39.907, 116.391],
zoom: 12
});
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 19,
attribution: 'Tiles © Esri'
}).addTo(map);
/* 矢量要素用来验证滤镜的边界:它们不在地图容器上,颜色保持不变 */
L.polygon([
[39.900, 116.370], [39.900, 116.405],
[39.920, 116.405], [39.920, 116.370]
], {
color: '#c2410c',
weight: 3,
fillColor: '#f97316',
fillOpacity: 0.25
}).addTo(map);
L.circle([39.907359, 116.391263], {
radius: 1100,
color: '#1f5fd0',
weight: 2,
fillOpacity: 0.15
}).addTo(map);
L.marker([39.907359, 116.391263], {
icon: L.divIcon({
className: '',
html: '<div style="width:14px;height:14px;border-radius:50%;background:#16a34a;' +
'border:2px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.3)"></div>',
iconSize: [14, 14],
iconAnchor: [7, 7]
})
}).bindPopup('矢量图层不受瓦片滤镜影响').addTo(map);
var mapEl = document.getElementById('map');
var buttons = document.querySelectorAll('.fx');
var status = document.getElementById('status');
var FILTERS = ['f-gray', 'f-night', 'f-warm', 'f-contrast'];
function apply(cls, label) {
/* 只增删滤镜类,保留 Leaflet 自身的容器类 */
FILTERS.forEach(function (name) { mapEl.classList.remove(name); });
if (cls) mapEl.classList.add(cls);
status.textContent = '当前:' + label;
for (var i = 0; i < buttons.length; i++) {
buttons[i].classList.toggle('is-on', buttons[i].getAttribute('data-fx') === cls);
}
}
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function () {
apply(this.getAttribute('data-fx'), this.textContent);
});
}
status.textContent = '当前:原色';
})();
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.