svg-div-overlay
本文回答:
vjmap.SvgOverlay/DivOverlay—— 随地图缩放的 SVG / DIV 覆盖物。
一、定位
这是非 WebGL 的 DOM 覆盖物。适合:
- 小量交互强的 SVG(动画、鼠标交互、组态图)
- 贴图(png / jpg)按四点精确定位
- 框架化组件(Vue/React)作为地图装饰
缺点:
- 性能远低于 WebGL 层
- 数量多会卡(建议单图 <20)
- 倾角大时可能"失真"或"消失"(SVG 有
maxPitch)
二、SvgOverlay 最小示例
const svgOverlay = new vjmap.SvgOverlay({
maxZoom: 5, // 超过 zoom=5 自动隐藏
maxPitch: 60 // pitch ≥ 60 度时隐藏(显示条件 pitch < maxPitch)
});
svgOverlay.addTo(map);
svgOverlay.addElement({
// 坐标 / 长度须用 {{}} 占位(CAD 坐标、CAD 长度),裸数字是会随刷新错位的 SVG 像素
html: `<circle ${vjmap.SvgOverlay.attr_cx_cy_r(pt.x, pt.y, 10)} style="fill:red"/>`,
event: container => {
const ele = container.querySelector("circle");
ele.addEventListener("click", () => alert("点了"));
}
});
三、SvgOverlay 选项
| 选项 | 说明 |
|---|---|
minZoom / maxZoom |
可见 zoom 区间 |
maxPitch |
可见最大倾角 |
updateDivSize |
是否随 zoom 放大 div(SVG 需要 true) |
maxDivSize |
div 放大上限(超过则像素放大) |
noUpdateBoundsWhenMoveend |
moveend 不更新 bounds |
divClassName |
外层 div class |
svgMaxWidth / svgMaxHeight |
SVG 初始画布大小(默认 1000) |
svgOffset |
初始化偏移(默认 100) |
四、增删改 SVG 元素
// 单个
const id = "myCircle";
svgOverlay.addElement({
html: `<circle id="${id}" ${vjmap.SvgOverlay.attr_cx_cy_r(pt.x, pt.y, 10)} fill="red"/>`,
event: parent => {
const el = parent.querySelector(`#${id}`);
el.addEventListener("click", () => console.log("clicked"));
}
});
// 批量
svgOverlay.addElements([
{ html: `<circle ${vjmap.SvgOverlay.attr_cx_cy_r(p1.x, p1.y, 50)}/>`, event: () => {} },
{ html: `<line ${vjmap.SvgOverlay.attr_x1_y1_x2_y2(p1.x, p1.y, p2.x, p2.y)} stroke="red"/>`, event: () => {} }
]);
// 删:按元素的 id 字段或 html 原文匹配(不是 html 里写的 DOM id)
svgOverlay.removeElements([id1, id2]);
// 更新:按 id/html 定位后 Object.assign 合并(不是先删再加)
svgOverlay.updateElements([{ id, html: "<circle ...>", event: () => {} }]);
// 直接操作 SVG container:仅临时生效——每次 moveend 都按 elements[].html 重建 innerHTML,改动会被覆盖
const svg = svgOverlay.getSvgContainer();
svg.querySelector("#myCircle").setAttribute("fill", "blue");
// 持久修改用 updateElements([{ id, html }])
// 获取所有元素
const all = svgOverlay.getElements();
五、SVG 辅助静态函数
attr_* 返回的不是字面属性,而是 {{}} 模板占位符——update() 时把 {{x,y}} 地理坐标换算成像素、{{n}} 长度按比例缩放:
vjmap.SvgOverlay.attr_cx_cy_r(10, 20, 5); // '{{cx=10,cy=20}} r="{{5}}"'
vjmap.SvgOverlay.attr_x1_y1_x2_y2(0, 0, 9, 9); // '{{x1=0,y1=0}} {{x2=9,y2=9}}'
vjmap.SvgOverlay.attr_x_y_w_h(1, 2, 3, 4); // '{{x=1,y=2}} width="{{3}}" height="{{4}}"'
vjmap.SvgOverlay.attr_x_y(5, 6); // '{{x=5,y=6}}'
vjmap.SvgOverlay.attr_fontsize(14); // 'font-size="{{14}}"'
vjmap.SvgOverlay.attr_length(8); // '{{8}}'
vjmap.SvgOverlay.attr_point(geoPt); // '{{d=12.5,-3.25}}'(默认带 `d=` 键名)
vjmap.SvgOverlay.attr_point(geoPt, true); // '{{12.5,-3.25}}'(joinComma 传 true 才是裸 '{{x,y}}'——语义反直觉,见 min.js)
vjmap.SvgOverlay.attr_points([p1, p2]); // 'points="{{10,20}} {{30,40}}"'
vjmap.SvgOverlay.attr_path([p1, p2]); // 'M{{12.5,-3.25}} L{{30,40}}'(内部走 joinComma=true 分支,顶点为裸形态)
用法:html: '<circle ' + attr_cx_cy_r(cx, cy, r) + ' fill="red"/>'。
⚠️ 占位符数字必须 ≥2 位或带小数点:替换正则是 \d+[.]?\d+,{{5}}、{{5,6}}(单个一位数)不匹配会原样进 DOM 报 Expected length, "{{5}}"。写 {{5.0}} 或 {{55}} 才替换。
六、DivOverlay 最小示例(贴图片)
const image = document.createElement("img");
image.style.position = "absolute";
image.style.left = "0";
image.style.top = "0";
image.style.width = "1280px";
image.style.height = "905px";
image.style.opacity = "0.8";
image.src = "/images/park.jpg";
image.style.pointerEvents = "none";
const divOverlay = new vjmap.DivOverlay({
bounds: fourPoints, // 四个 CAD 点(左下 / 左上 / 右上 / 右下)
element: image,
width: 1280,
height: 905,
minZoom: 5,
maxZoom: 15
});
divOverlay.addTo(map);
七、DivOverlay 选项
| 选项 | 说明 |
|---|---|
bounds |
四个点坐标数组 [[x,y],[x,y],[x,y],[x,y]] 或 GeoBounds |
element |
HTML 元素 |
width / height |
元素原生大小(像素) |
minZoom / maxZoom |
可见范围 |
maxPitch |
可见最大倾角 |
updateDivSize |
随 zoom 放大 div |
maxDivSize |
div 放大上限 |
八、DivOverlay 动态更新
divOverlay.updateBounds([pt1, pt2, pt3, pt4]); // 拖动四角调整
divOverlay.updateSize(w, h); // 改原生大小
divOverlay.setVisible(false); // 隐藏:从文档摘除 DOM
divOverlay.setVisible(true); // 显示:插回 DOM
divOverlay.setVisible(false, true); // 仅切 display:none(DOM 保留)
divOverlay.setVisible(true, true); // 仅切 display:""
// 两种方式须成对使用:setVisible(false) 摘除后再 setVisible(true, true) 只改 display,元素仍不在文档里
divOverlay.remove(); // 彻底移除
九、组态图场景(DIV 内装 SVG)
const svgStr = `
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 800 600"
preserveAspectRatio="xMinYMin meet">
<!-- 组态内容 -->
</svg>`;
const el = document.createElement("div");
el.style.position = "absolute";
el.style.width = "800px";
el.style.height = "600px";
el.innerHTML = svgStr;
new vjmap.DivOverlay({
bounds: mapBounds, // GeoBounds 直接传(也接受四点数组)
element: el,
width: 800,
height: 600,
updateDivSize: true, // SVG 随缩放放大
maxDivSize: 6000
}).addTo(map);
十、Vue/React 组件作为 DivOverlay
// Vue3
const host = document.createElement("div");
host.style.position = "absolute";
const app = Vue.createApp(MyComp);
app.mount(host);
new vjmap.DivOverlay({
bounds, element: host, width: w, height: h
}).addTo(map);
// 卸载时
app.unmount();
divOverlay.remove();
十一、对比
| 需求 | 首选 |
|---|---|
| 几千个标志 | WebGL Symbol |
| 一张背景贴图按四角对齐 | DivOverlay |
| 组态图 / 设备示意 | DivOverlay (内嵌 SVG) |
| 简单可交互几何(< 50 个) | SvgOverlay |
| 大量可交互几何 | 用 WebGL Polygon / Polyline + hover |
| Vue/React 卡片 | Marker |
| 气泡弹窗 | Popup |
十二、常见坑
- 倾斜时看不见 →
maxPitch默认 90(显示条件pitch < maxPitch),引擎最大倾角 85,默认不会因倾角隐藏;设maxPitch: 85反而会在 85° 时隐藏。大倾角下变形 / 消失是 CSS 3D 矩阵变换的局限,用map.setMaxPitch(...)限制倾角或改用 WebGL 图层。 - 多个 DivOverlay 错位 → 每个子 element 记得
position: absolute; left:0; top:0;。 - SVG 超大 zoom 失真 → 设
maxDivSize控制;或切成切片贴图。 - DivOverlay 挡鼠标 →
pointerEvents: "none",或只对需要响应的子元素保留 pointer。 - Vue unmount 前 divOverlay 没删 → 容易内存泄漏;
onBeforeUnmount做清理。 addElement必须在addTo(map)之后:先于挂载调用会因内部update()访问this._map.getZoom抛TypeError(_map还是 undefined)。- DivOverlay 的
bounds选项类型严格:只接受GeoBounds或[4×GeoPointLike]四角点数组 +width/height必填;传"[x,y,x,y]"字符串抛Invalid input coordinate: [(文本格式要走vjmap.GeoBounds.fromString)。 - 抛错的 overlay 必须
remove():构造/addTo 抛异常后若残留,该对象仍挂在地图 zoom/move 事件链上持续抛错,阻断后续监听器(残留坏 DivOverlay 导致zoomend不再派发)。 map.removeMarkers()事件载荷不是数组:removeMarkers事件回调拿到的是 Eventedfire出来的事件对象(e.type === "removeMarkers"),markers 数组被extend展开成数字键e[0]、e[1]……——取e.markers/e.data都是undefined,遍历数字键才是移除的 Marker 列表。noIncludeDivOverlay/noIncludeSvgOverlay为双否定参数:传true才排除对应覆盖物不被清理。