唯杰地图 logo唯杰地图
VJMAP
EN

Marker2D标记

vjmap3d/references/ui/marker2D.md 14.7 KB414 行
原文

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: `&lt;span style="color: #ffff00; padding-left: 30px">我是标注Marker的右键菜单&lt;/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));

常见坑

  1. DOM 不是 addTo 同步挂载:Marker2D 是 CSS2DObject,元素要等下一渲染帧由 CSS2DRenderer 挂进 DOM——addTo(app) 后立即断言 getElement().isConnected 为 false 是正常的,等一帧再查。
  2. 被遮挡不会隐藏:默认(occlusionOpacity = 1)不做遮挡检测,被实体挡住也照常显示;设置 occlusionOpacity < 1 后,被挡住时只是把透明度设为该值(并派发 occlusion 信号),元素不会 display:none、也不会从 DOM 摘除。display:none 只发生在标记位于视锥外(如相机背后)或 visible=false 时。
  3. getElement() 返回的 div 可直接 addEventListener(如 dblclick),见上文示例。
  4. getBoundingRect() 返回视口像素矩形(来自元素的 getBoundingClientRect(),默认还并入弹窗范围):{x1,y1,x2,y2,cx,cy,rotation,position,zIndex,id,groupName,setVisible}——没有 width/height 字段,尺寸取 x2-x1/y2-y1;透明度低于 disableOpacity 或元素尺寸为 0(未挂载)时返回 null。
  5. signal.elementSizeReady 通常收不到:未设置非零 offset 时,它在构造函数里同步派发,new 返回后再挂的监听永远不会被调用;设置了 offset 时才依赖内部 ResizeObserver 首次量到非零尺寸后异步派发(headless/脱离视口场景可能不派发)。不要把它当"挂载完成"信号,用 signal.onAdd + 下一帧。
  6. setOpacity 同步写 DOM:getOpacity() 读回与 element.style.opacity 一致。setCursor('pointer') 写 element.style.cursor。构造选项 draggable/allowOverlap 立即生效,isDraggable()/isAllowOverlap() 如实读回。