cesium+react实现组件式弹框

本文介绍了如何在React项目中使用组件化方法创建一个可定制的弹框组件,以便更方便地在Cesium中处理弹出内容,包括位置、旋转、尺寸和HTML元素的插入,同时提供了组件的实例代码和使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        由于平时是基于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> `
...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值