Layer Panel
Runnable Leaflet example: Layer Panel. 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
<!--
示例:图层控制面板
要点:四类覆盖物分组、勾选显隐、全选与全不选、面板与地图事件隔离
依赖: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:10px;align-items:center;font-size:12px;color:#58606e}
.bar .spacer{flex:1 1 auto}
#wrap{flex:1 1 auto;position:relative;min-height:0}
#map{position:absolute;inset:0}
.panel{position:absolute;top:12px;right:12px;z-index:800;width:190px;
background:#fff;border-radius:6px;box-shadow:0 2px 10px rgba(0,0,0,.18);
padding:10px 12px;font-size:12px;color:#16181d}
.panel h3{margin:0 0 8px;font-size:12px;font-weight:600;color:#58606e;
letter-spacing:.04em;text-transform:uppercase}
.panel label{display:flex;align-items:center;gap:8px;padding:4px 0;cursor:pointer}
.panel label input{margin:0}
.panel label .n{margin-left:auto;color:#8a919e;font-variant-numeric:tabular-nums}
.panel .foot{display:flex;gap:6px;margin-top:8px;padding-top:8px;border-top:1px solid #eef0f3}
.panel .foot button{flex:1;font:inherit;font-size:12px;padding:3px 0;border-radius:4px;
cursor:pointer;border:1px solid #ccd1d9;background:#f8f9fb;color:#16181d}
.panel .foot button:hover{background:#eef0f3}
.pin{width:12px;height:12px;border-radius:50%;border:2px solid #fff;
box-shadow:0 0 0 1px rgba(0,0,0,.22)}
</style>
</head>
<body>
<div class="bar">
<span>覆盖物</span>
<span class="spacer"></span>
<span id="status"></span>
</div>
<div id="wrap">
<div id="map"></div>
<div class="panel">
<h3>图层</h3>
<label><input type="checkbox" data-layer="markers" checked> 标记 <span class="n">6</span></label>
<label><input type="checkbox" data-layer="circles" checked> 圆 <span class="n">2</span></label>
<label><input type="checkbox" data-layer="polygons" checked> 多边形 <span class="n">2</span></label>
<label><input type="checkbox" data-layer="lines" checked> 折线 <span class="n">2</span></label>
<div class="foot">
<button id="all" type="button">全选</button>
<button id="none" type="button">全不选</button>
</div>
</div>
</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.912, 116.400],
zoom: 13
});
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 19,
attribution: 'Tiles © Esri'
}).addTo(map);
var groups = {
markers: L.layerGroup(),
circles: L.layerGroup(),
polygons: L.layerGroup(),
lines: L.layerGroup()
};
var places = [
['天安门', 39.907359, 116.391263, '#1f5fd0'],
['王府井', 39.914700, 116.410600, '#c2410c'],
['前门', 39.899400, 116.397500, '#17694a'],
['西单', 39.909700, 116.374200, '#7c3aed'],
['东单', 39.908800, 116.418300, '#0e7490'],
['崇文门', 39.899900, 116.417200, '#a16207']
];
places.forEach(function (p) {
L.marker([p[1], p[2]], {
icon: L.divIcon({
className: '',
html: '<div class="pin" style="background:' + p[3] + '"></div>',
iconSize: [12, 12],
iconAnchor: [6, 6]
})
}).bindPopup('<b>' + p[0] + '</b>').addTo(groups.markers);
});
L.circle([39.907359, 116.391263], {
radius: 1200, color: '#1f5fd0', weight: 1, fillOpacity: 0.08
}).addTo(groups.circles);
L.circle([39.914700, 116.410600], {
radius: 800, color: '#c2410c', weight: 1, fillOpacity: 0.08
}).addTo(groups.circles);
L.polygon([
[39.920, 116.380], [39.920, 116.410], [39.935, 116.410], [39.935, 116.380]
], { color: '#7c3aed', weight: 2, fillOpacity: 0.12 }).addTo(groups.polygons);
L.polygon([
[39.885, 116.400], [39.885, 116.430], [39.898, 116.430], [39.898, 116.400]
], { color: '#17694a', weight: 2, fillOpacity: 0.12 }).addTo(groups.polygons);
L.polyline([
[39.885, 116.360], [39.920, 116.375], [39.945, 116.420]
], { color: '#0e7490', weight: 3, dashArray: '8 6' }).addTo(groups.lines);
L.polyline([
[39.890, 116.440], [39.910, 116.425], [39.935, 116.445]
], { color: '#a16207', weight: 3 }).addTo(groups.lines);
var order = ['markers', 'circles', 'polygons', 'lines'];
order.forEach(function (key) {
groups[key].addTo(map);
});
var boxes = document.querySelectorAll('.panel input[data-layer]');
var status = document.getElementById('status');
function refresh() {
var visible = 0;
order.forEach(function (key) {
if (map.hasLayer(groups[key])) visible++;
});
status.textContent = visible + ' / ' + order.length + ' 个覆盖物分组显示中';
}
function apply(key, on) {
if (on) {
if (!map.hasLayer(groups[key])) groups[key].addTo(map);
} else {
if (map.hasLayer(groups[key])) map.removeLayer(groups[key]);
}
}
for (var i = 0; i < boxes.length; i++) {
boxes[i].addEventListener('change', function () {
apply(this.getAttribute('data-layer'), this.checked);
refresh();
});
}
document.getElementById('all').addEventListener('click', function () {
for (var i = 0; i < boxes.length; i++) boxes[i].checked = true;
order.forEach(function (key) { apply(key, true); });
refresh();
});
document.getElementById('none').addEventListener('click', function () {
for (var i = 0; i < boxes.length; i++) boxes[i].checked = false;
order.forEach(function (key) { apply(key, false); });
refresh();
});
refresh();
})();
</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.