Image Overlay
Runnable Leaflet example: Image Overlay. 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
<!--
示例:图像叠加
要点:canvas 生成等经纬度栅格图、L.imageOverlay 按 bounds 铺图、setUrl 换帧不闪烁
依赖:Leaflet 1.9.4 · jsDelivr CDN
-->
<!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 button{font:inherit;font-size:12px;padding:4px 10px;border-radius:4px;cursor:pointer;
border:1px solid #ccd1d9;background:#fff;color:#16181d}
.bar button:hover{background:#eef0f3}
.bar input[type=range]{width:110px;vertical-align:middle}
.bar b{color:#16181d;font-weight:600}
#map{flex:1 1 auto;min-height:0}
</style>
</head>
<body>
<div class="bar">
<button id="frame">换一帧</button>
<button id="toggle">隐藏叠加</button>
<label>透明度 <input id="opacity" type="range" min="0" max="1" step="0.05" value="0.65"></label>
<span id="val">0.65</span>
<b id="range">bounds 116.28–116.52E / 39.86–39.98N</b>
</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.92, 116.40], zoom: 11 });
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 19,
attribution: 'Tiles © Esri'
}).addTo(map);
/* 图片四角对应的地理范围:纬度 0.12°、经度 0.24°,约 3:2,与画布比例一致 */
var bounds = [[39.86, 116.28], [39.98, 116.52]];
var W = 480;
var H = 320;
var canvas = document.createElement('canvas');
canvas.width = W;
canvas.height = H;
function seeded(n) {
var s = n || 1;
return function () {
s = (s * 16807) % 2147483647;
return (s - 1) / 2147483646;
};
}
/* 画一层「栅格降水」:多个径向渐变斑点叠加,透明度即数值 */
function paint(frame) {
var ctx = canvas.getContext('2d');
var rand = seeded(frame * 97 + 11);
ctx.clearRect(0, 0, W, H);
var palette = ['rgba(37,99,235,', 'rgba(14,165,233,', 'rgba(22,163,74,',
'rgba(234,179,8,', 'rgba(220,38,38,'];
for (var i = 0; i < 26; i++) {
var cx = rand() * W;
var cy = rand() * H;
var r = 26 + rand() * 70;
var color = palette[Math.min(palette.length - 1, Math.floor(rand() * palette.length))];
var g = ctx.createRadialGradient(cx, cy, 2, cx, cy, r);
g.addColorStop(0, color + '0.85)');
g.addColorStop(0.55, color + '0.35)');
g.addColorStop(1, color + '0)');
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.fill();
}
ctx.font = '12px sans-serif';
ctx.fillStyle = 'rgba(22,24,29,0.55)';
ctx.fillText('模拟栅格 · 帧 ' + frame, 10, H - 12);
}
paint(1);
var overlay = L.imageOverlay(canvas.toDataURL('image/png'), bounds, {
opacity: 0.65,
zIndex: 2,
interactive: false
}).addTo(map);
var frame = 1;
document.getElementById('frame').addEventListener('click', function () {
frame += 1;
paint(frame);
/* 用 setUrl 换图,DOM 节点复用,不会闪一下白底 */
overlay.setUrl(canvas.toDataURL('image/png'));
});
document.getElementById('opacity').addEventListener('input', function () {
overlay.setOpacity(Number(this.value));
document.getElementById('val').textContent = this.value;
});
var shown = true;
document.getElementById('toggle').addEventListener('click', function () {
shown = !shown;
if (shown) {
overlay.addTo(map);
this.textContent = '隐藏叠加';
} else {
map.removeLayer(overlay);
this.textContent = '显示叠加';
}
});
/* 叠加图不参与交互,点击仍然落到地图上 */
map.on('click', function (e) {
console.log('地图点击', e.latlng.lat.toFixed(5), e.latlng.lng.toFixed(5));
});
})();
</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.