由于平时是基于react开发,在书写cesium的弹框的时候,经常是组件化开发,但是这导致的就是在常用的字符串式的div较为难以满足需求,随即想到了封装一套基于react的弹框组件,让我们可以更为方便的完成cesium的弹框开发,以上诉的弹框为例
1、代码
话不多说,直接上代码,大家直接cv使用,原理可以参考cesium 自定义动态标记 我也是受他启发。
/**
* @descripion:cesium自定义的div弹框
* @param {Viewer} viewer
* @param {Cartesian2||Cartesian3} position 位置
* @param {number} rotation 旋转角度
* @param {String} positioning default "botten-center" 对其方式
* @param {String} rotationCenter default "botten-center" 旋转中心
* @param {Cartesian2} offset //x、y轴的偏移距离
* @param {Cartesian2} scale //缩放
* @param {String} html html元素
* @param {String} id 指定外维div的id
* @param {number} minViewHeight default 0
* @param {number} maxViewHeight default 5000000
* @param {number} height 当position是三维数组是就不生效
* @return {*}
*/
// //调用
// function addDivLabel() {
// let val = {
// viewer: viewer,
// position: [121.54035, 31.92146],
// height: 500,
// positioning: 'top-right',
// html: `<div id="mydiv" > //或者直接 <ReactDom/>,
// <div>11</div>
// <div>22</div>
// </div>`
// ,
// }
// label = new MapPopup(val)
// //为div绑定事件
// let divv = document.getElementById('mydiv')
// divv?.addEventListener('click', (() => {
// viewer.cesiumWidget.container.removeChild(label.div);
// }));
// }
import * as Cesium from 'cesium'
import React from 'react'
import { createRoot } from 'react-dom/client'
import { flushSync } from 'react-dom'
export default class DivLabel {
[x: string]: any;
constructor(val: {
offset?: number[];
minViewHeight?: number;
maxViewHeight?: number;
viewer: any;
height?: any;
position: any[];
positioning?: string;
rotationCenter?: string;
rotation?: number
scale?: number[];
html: string | React.ReactElement
}) {
this.viewer = val.viewer;
this.height = val.position[2] || val.height;
this.offset = val.offset || [0, 0]
this.position = Cesium.Cartesian3.fromDegrees(
val.position[0],
val.position[1],
val.height
);
this.minViewHeight = val.minViewHeight || 0
this.maxViewHeight = val.maxViewHeight || 50000000
this.positioning = val.positioning || "bottom-center"
this.rotation = val.rotation || 0
this.rotationCener = val.rotationCenter || "bottom-center"
this.div = document.createElement("div");
this.scale = val.scale || [1, 1]
this.addLabel(val);
}
isHtml(str:any) {
const htmlTagPattern = /<[^>]*>/g;
return htmlTagPattern.test(str);
}
addLabel(val: any) {
this.div.id = val.id;
this.div.style.position = "absolute";
if (React.isValidElement(val.html)) {
const root = createRoot(this.div)
flushSync(() => {
root.render(val.html)
})
} else if (this.isHtml(val.html)) {
let divHTML = `<div>${val.html}</div>`
this.div.innerHTML = divHTML;
}
this.viewer.cesiumWidget.container.appendChild(this.div);
this.addPostRender()
}
addPostRender() {
this.viewer.scene.postRender.addEventListener(this.postRender, this);
}
postRender() {
const canvasHeight = this.viewer.scene.canvas.height;
const windowPosition = new Cesium.Cartesian2();
Cesium.SceneTransforms.wgs84ToWindowCoordinates(
this.viewer.scene,
this.position,
windowPosition
);
const elWidth = this.div.offsetWidth;
if (this.positioning == "bottom-left") {
this.div.style.bottom = canvasHeight - windowPosition.y - this.offset[1] + "px";
this.div.style.left = windowPosition.x + this.offset[0] + "px";
} else if (this.positioning == "bottom-right") {
this.div.style.bottom = canvasHeight - windowPosition.y - this.offset[1] + "px";
this.div.style.left = windowPosition.x + this.offset[0] - elWidth + "px";
} else if (this.positioning == "bottom-center") {
this.div.style.bottom = canvasHeight - windowPosition.y - this.offset[1] + "px";
this.div.style.left = windowPosition.x + this.offset[0] - elWidth / 2 + "px";
} else if (this.positioning == "top-center") {
this.div.style.top = windowPosition.y - this.offset[1] + "px";
this.div.style.left = windowPosition.x + this.offset[0] - elWidth / 2 + "px";
} else if (this.positioning == "top-left") {
this.div.style.top = windowPosition.y - this.offset[1] + "px";
this.div.style.left = windowPosition.x + this.offset[0] + "px";
} else if (this.positioning == "top-right") {
this.div.style.top = windowPosition.y - this.offset[1] + "px";
this.div.style.left = windowPosition.x + this.offset[0] - elWidth + "px";
} else {
this.div.style.bottom = canvasHeight - windowPosition.y - this.offset[1] + "px";
this.div.style.left = windowPosition.x + this.offset[0] - elWidth / 2 + "px";
}
this.div.style.transform = "rotate(" + this.rotation + "deg)";
this.div.style.transformOrigin = this.rotationCenter;
this.div.style.transform = "scale(" + this.scale + ")";
const camerPosition = this.viewer.camera.position;
let height = this.viewer.scene.globe.ellipsoid.cartesianToCartographic(camerPosition).height;
height += this.viewer.scene.globe.ellipsoid.maximumRadius;
if ((!(Cesium.Cartesian3.distance(camerPosition, this.position) > height)) && (this.viewer.camera.positionCartographic.height < this.maxViewHeight) && (this.viewer.camera.positionCartographic.height > this.minViewHeight)) {
this.div.style.display = "block"
} else {
this.div.style.display = "none"
}
}
removeDiv() {
this.viewer.cesiumWidget.container.removeChild(this.div);
}
}
2、在react中的使用:
2.1、直接写html元素
...
import DivLabel from './DivLabel'
...
function addDivLabel() {
let val = {
viewer: viewer,
position: [139.767052, 35.681167],
height: 500,
positioning: 'top-right',
html: <div className="divlabel-container">
<div className="animate-maker-border">
<span className="animate-marker__text">这是一个组件的reactDiv</span>
</div>
</div>
}
let label = new DivLabel(val)
}
2.2、引用组件
...
import DivLabel from './DivLabel'
...
const ReactDom = () => {
return (
<div className="divlabel-container" onClick={() => { console.log('点击了div'); }} >
<div className="animate-maker-border">
<span className="animate-marker__text">这是一个组件的reactDiv</span>
</div>
</div>
)
}
function addDivLabel() {
let val = {
viewer: viewer,
position: [139.767052, 35.681167],
height: 500,
positioning: 'top-right',
html: <ReactDom />
}
let label = new DivLabel(val)
}
2.3、附上css的样式,样式是cv的上诉的链接
.divlabel-container , .divlabel-container::before, .divlabel-container::after {
position: absolute;
left: 0;
bottom: 0;
pointer-events: none;
cursor: pointer;
}
.animate-maker-border, .animate-maker-border::before, .animate-maker-border::after {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.animate-maker-border {
width: 150px;
height: 30px;
margin: 0;
color: #69ca62;
box-shadow: inset 0 0 0 1px rgba(105, 202, 98, 0.5);
}
.animate-maker-border::before,.animate-maker-border::after{
content: '';
z-index: 10;
margin: -5%;
box-shadow: inset 0 0 0 2px;
animation: clipMe 8s linear infinite;
}
.animate-maker-border::before {
animation-delay: -4s;
}
@keyframes clipMe {
0%, 100% {
clip: rect(0px, 170.0px, 2px, 0px);
}
25% {
clip: rect(0px, 2px, 47.0px, 0px);
}
50% {
clip: rect(45.0px, 170.0px, 47.0px, 0px);
}
75% {
clip: rect(0px, 170.0px, 47.0px, 45.0px);
}
}
.animate-marker__text {
text-align: center;
color: #fff;
font-size: 14px;
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
font-weight: bolder;
user-select: none;
cursor: pointer;
background: rgba(0, 173, 181, 0.32);
}
2.4、当然利用元素的字符串html也可以
...
` <div style="background:rgba(255,122,0,0.4)">${data.title}</div> `
...