Marker2D标记
Marker2D是基于threejs中 CSS2DObject 实现的div标记。
常用方法
| 方法 | 描述 |
|---|---|
| isShow | 是否显示 |
| show | 显示 |
| hide | 隐藏 |
| getPosition | 获取位置 |
| setPosition | 设置位置 |
| togglePopup | 切换弹出信息框显示或隐藏 |
| setPopup | 设置弹出信息框 |
| addTo | 增加至应用或实体中 |
| getElement | 获取元素对象 |
| getOpacity | 获取透明度(读元素 style.opacity,未设置时为 1) |
| setOpacity | 设置透明度(写元素 style.opacity,有弹窗时一并设置) |
| setAllowOverlap | 设置是否允许重叠 |
| getBoundingRect | 获取边界范围(视口屏幕像素矩形,供碰撞检测用;不可见/尺寸为 0 时返回 null) |
| isAllowOverlap | 是否允许重叠 |
| setDraggable | 设置是否可拖动 |
| isDraggable | 是否可拖动 |
| remove | 移除 |
| setAnimation | 设置动画 |
| setCursor | 设置光标 |
信号名
| 方法 | 描述 |
|---|---|
| dragstart | 开始拖动 |
| drag | 拖动 |
| dragend | 结束拖动 |
| click | 点击 |
| occlusion | 遮挡状态变化,回调参数 {object, occlusion}。⚠️ 只有构造选项 occlusionOpacity < 1(默认 1)时才做遮挡检测,否则永不触发 |
| elementSizeReady | 元素大小已就绪。⚠️ 未设置 offset(或为 [0,0])时在构造函数内同步派发,new 之后再 .add 的监听永远收不到;只有设置了非零 offset 才会在挂载后量到尺寸时异步派发 |
| onAdd | 增加 |
| onRemove | 移除 |
对于div的事件,可以通过getElement获取div元素,对此div增加事件。 如
marker2d.getElement().addEventListener("dblclick", event => {
console.log("双击")
});
用法示例
创建一个marker2D
let marker2d = new vjmap3d.Marker2D({
color: vjmap3d.randHtmlColor(),
draggable: true,
allowOverlap: false
});
marker2d.setPosition([2, 0, 1]);
marker2d.addTo(app);
事件
marker2d.signal.dragstart.add(() => {
app.logInfo("dragstart");
});
marker2d.signal.drag.add(() => {
app.logInfo("drag");
});
marker2d.signal.dragend.add(() => {
app.logInfo("dragend");
});
marker2d.signal.click.add(() => {
app.logInfo("click");
});
marker2d.signal.occlusion.add(() => { // 需构造时设 occlusionOpacity < 1 才会触发
app.logInfo("occlusion");
});
marker2d.signal.elementSizeReady.add(() => { // ⚠️ 本例未设 offset,该信号已在构造时同步派发,这里收不到
app.logInfo("elementSizeReady");
});
marker2d.signal.onAdd.add(() => {
app.logInfo("onAdd");
});
marker2d.signal.onRemove.add(() => {
app.logInfo("onRemove");
});
marker2d.getElement().addEventListener("contextmenu", event => {
// @ts-ignore
if (event.button != 2) return;//不是右键
// 阻止地图默认右键菜单触发
event.preventDefault();
event.stopPropagation();
new vjmap3d.ContextMenu({
event: event,
theme: "dark", //light
width: "250px",
items: [
{type: 'custom', markup: `<span style="color: #ffff00; padding-left: 30px">我是标注Marker的右键菜单</span>`},
{
label: '删除Marker',
onClick: () => {
marker2d.remove()
}
}
]
});
})
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
container: "map", // 容器id
stat: { show: true, left: "0" },
scene: {
gridHelper: { visible: true } // 是否显示坐标网格
},
camera: { // 相机设置
viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
}
})
let marker2d = new vjmap3d.Marker2D({
color: vjmap3d.randHtmlColor(),
draggable: true,
allowOverlap: false
});
marker2d.setPosition([2, 0, 1]);
marker2d.addTo(app);
marker2d.signal.dragstart.add(() => {
app.logInfo("dragstart");
});
marker2d.signal.drag.add(() => {
app.logInfo("drag");
});
marker2d.signal.dragend.add(() => {
app.logInfo("dragend");
});
marker2d.signal.click.add(() => {
app.logInfo("click");
});
marker2d.signal.occlusion.add(() => { // 需构造时设 occlusionOpacity < 1 才会触发
app.logInfo("occlusion");
});
marker2d.signal.elementSizeReady.add(() => { // ⚠️ 本例未设 offset,该信号已在构造时同步派发,这里收不到
app.logInfo("elementSizeReady");
});
marker2d.signal.onAdd.add(() => {
app.logInfo("onAdd");
});
marker2d.signal.onRemove.add(() => {
app.logInfo("onRemove");
});
marker2d.getElement().addEventListener("contextmenu", event => {
// @ts-ignore
if (event.button != 2) return;//不是右键
// 阻止地图默认右键菜单触发
event.preventDefault();
event.stopPropagation();
new vjmap3d.ContextMenu({
event: event,
theme: "dark", //light
width: "250px",
items: [
{type: 'custom', markup: `<span style="color: #ffff00; padding-left: 30px">我是标注Marker的右键菜单</span>`},
{
label: '删除Marker',
onClick: () => {
marker2d.remove()
}
}
]
});
})
关联信息提示框
for (let i = 0; i < 100; i++) {
let marker2d = new vjmap3d.Marker2D({
color: vjmap3d.randHtmlColor(),
allowOverlap: true,
fadeDistance: 200,
occlusionOpacity: 0.2
});
marker2d.setPosition(vjmap3d.toVector3(vjmap3d.randPoint3D()));
marker2d.addTo(app);
marker2d.setPopup(
new vjmap3d.Popup2D({
backgroundColor: "#A0FFA0",
buttonColor: "#f00",
closeOnClick: true,
closeButton: true
}).setHTML(`index${i + 1}`)
);
// marker2d.togglePopup();
}
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
container: "map", // 容器id
stat: { show: true, left: "0" },
scene: {
gridHelper: { visible: true } // 是否显示坐标网格
},
camera: { // 相机设置
viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
}
})
for (let i = 0; i < 100; i++) {
let marker2d = new vjmap3d.Marker2D({
color: vjmap3d.randHtmlColor(),
allowOverlap: true,
fadeDistance: 200,
occlusionOpacity: 0.2
});
marker2d.setPosition(vjmap3d.toVector3(vjmap3d.randPoint3D()));
marker2d.addTo(app);
marker2d.setPopup(
new vjmap3d.Popup2D({
backgroundColor: "#A0FFA0",
buttonColor: "#f00",
closeOnClick: true,
closeButton: true
}).setHTML(`index${i + 1}`)
);
// marker2d.togglePopup();
}
遮挡时设置透明度
let box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
color: '#0ff'
});
let mesh = new THREE.Mesh(box, material);
mesh.position.set(0, 0, 0)
app.scene.add(mesh);
//
let marker2d = new vjmap3d.Marker2D({
color: "#f00",
occlusionOpacity: 0.2 // 遮挡时透明度
});
marker2d.setPosition([0, 0, -1.5]);
marker2d.addTo(app);
marker2d.signal.occlusion.add(e => {
if (e.occlusion) {
app.logInfo("被遮挡", "warn");
} else {
app.logInfo("没被遮挡", "success");
}
});
app.signal.onAppAfterRender.add(t => {
app.cameraControl.azimuthAngle += 20 * t.deltaTime * THREE.MathUtils.DEG2RAD;
})
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
container: "map", // 容器id
stat: { show: true, left: "0" },
scene: {
gridHelper: { visible: true } // 是否显示坐标网格
},
camera: { // 相机设置
viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
}
})
let box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
color: '#0ff'
});
let mesh = new THREE.Mesh(box, material);
mesh.position.set(0, 0, 0)
app.scene.add(mesh);
//
let marker2d = new vjmap3d.Marker2D({
color: "#f00",
occlusionOpacity:0.2 // 遮挡时透明度
});
marker2d.setPosition([0, 0, -1.5]);
marker2d.addTo(app);
marker2d.signal.occlusion.add(e => {
if (e.occlusion) {
app.logInfo("被遮挡", "warn");
} else {
app.logInfo("没被遮挡", "success");
}
});
app.signal.onAppAfterRender.add(t => {
app.cameraControl.azimuthAngle += 20 * t.deltaTime * THREE.MathUtils.DEG2RAD;
})
动画Marker
let marker2d = new vjmap3d.Marker2D({
color: vjmap3d.randHtmlColor()
});
marker2d.setPosition(vjmap3d.toVector3(vjmap3d.toVector3(vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}))))
marker2d.addTo(app);
marker2d.setAnimation("MAP_ANIMATION_BOUNCE");
RotatingTextBorderMarker2d
// 创建一个缺省的
let markerEle = new vjmap3d.RotatingTextBorderMarker(
{
position: vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}),
text: "3D地图"
},
{}
);
let marker2d = markerEle.createMarker2d({
draggable: true
});
marker2d.addTo(app);
BreathingApertureMarker
// 创建一个缺省的
let markerEle = new vjmap3d.BreathingApertureMarker(
{
position: vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}),
text: "3D地图"
},
{
textColor: vjmap3d.randHtmlColor()
}
);
let marker2d = markerEle.createMarker2d({
draggable: true
});
marker2d.addTo(app);
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
container: "map", // 容器id
stat: { show: true, left: "0" },
scene: {
gridHelper: { visible: true } // 是否显示坐标网格
},
camera: { // 相机设置
viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
}
})
const ui = await app.getConfigPane({ title: "Marker", style: { width: "250px"}});
let gui = new vjmap3d.UiPanelJsonConfig();
gui.addButton("动画Marker", async () => {
let marker2d = new vjmap3d.Marker2D({
color: vjmap3d.randHtmlColor()
});
marker2d.setPosition(vjmap3d.toVector3(vjmap3d.toVector3(vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}))))
marker2d.addTo(app);
marker2d.setAnimation("MAP_ANIMATION_BOUNCE");
});
gui.addButton("RotatingTextBorderMarker2d", async () => {
// 创建一个缺省的
let markerEle = new vjmap3d.RotatingTextBorderMarker(
{
position: vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}),
text: "3D地图"
},
{}
);
let marker2d = markerEle.createMarker2d({
draggable: true
});
marker2d.addTo(app);
});
gui.addButton("BreathingApertureMarker", async () => {
// 创建一个缺省的
let markerEle = new vjmap3d.BreathingApertureMarker(
{
position: vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5}),
text: "3D地图"
},
{
textColor: vjmap3d.randHtmlColor()
}
);
let marker2d = markerEle.createMarker2d({
draggable: true
});
marker2d.addTo(app);
});
gui.toJson().forEach(c => ui.appendChild(c));
常见坑
- DOM 不是 addTo 同步挂载:Marker2D 是 CSS2DObject,元素要等下一渲染帧由 CSS2DRenderer 挂进 DOM——
addTo(app)后立即断言getElement().isConnected为 false 是正常的,等一帧再查。 - 被遮挡不会隐藏:默认(
occlusionOpacity= 1)不做遮挡检测,被实体挡住也照常显示;设置occlusionOpacity < 1后,被挡住时只是把透明度设为该值(并派发occlusion信号),元素不会display:none、也不会从 DOM 摘除。display:none只发生在标记位于视锥外(如相机背后)或visible=false时。 getElement()返回的 div 可直接 addEventListener(如 dblclick),见上文示例。getBoundingRect()返回视口像素矩形(来自元素的getBoundingClientRect(),默认还并入弹窗范围):{x1,y1,x2,y2,cx,cy,rotation,position,zIndex,id,groupName,setVisible}——没有width/height字段,尺寸取x2-x1/y2-y1;透明度低于disableOpacity或元素尺寸为 0(未挂载)时返回null。signal.elementSizeReady通常收不到:未设置非零offset时,它在构造函数里同步派发,new返回后再挂的监听永远不会被调用;设置了offset时才依赖内部 ResizeObserver 首次量到非零尺寸后异步派发(headless/脱离视口场景可能不派发)。不要把它当"挂载完成"信号,用signal.onAdd+ 下一帧。setOpacity同步写 DOM:getOpacity()读回与element.style.opacity一致。setCursor('pointer')写element.style.cursor。构造选项draggable/allowOverlap立即生效,isDraggable()/isAllowOverlap()如实读回。