使用ts封装Cesium浮动弹窗效果

前言: 本文使用ts对cesium进行封装,实现了随地图拖拽而移动的浮动弹窗效果。对实体的信息展示、点击实体交互等业务功能友好。

使用API文档:

Popup:返回浮动弹窗实例。

new Popup(options);

参数(options):

NameRequireDefaultDescription

lng:number

true--浮动弹窗定位位置经度。

lat:number

true--浮动弹窗定位位置纬度。

alt:number

true--浮动弹窗定位位置高度。

offset: {

  top:number,

  bottom:number,

  left:number,

  right:number

}

false--

浮动弹窗相对于定位位置,调整的距离。

e.g. top:10, 弹窗向上移动10px。

map:map

true--

浮动弹窗挂载的Cesium地图实例。

html: string | DOM

false''浮动弹窗内部的html片段。
className:stringfalse''添加到浮动弹窗的css类名。

实例方法:

setHtml(html: string | HTMLElement); //修改浮动弹窗内部html
setLngLatAlt(lngLatAlt: LngLatAlt);  //修改浮动弹窗定位的经纬度
remove(); //销毁浮动弹窗实例

Popup实现方法:

import { BaseObject } from "./lib/BaseObject";
import { LngLatAlt } from "./types";
import { HKMap } from "./HKMap";
import { PopupOptions, Offset } from "./types/popup";
import "./style/index.less";

export class Popup extends BaseObject {
  private _map: HKMap;
  private _offset: Offset;
  private _container: HTMLElement;
  private _popupContent: HTMLElement;
  private _lngLatAlt: LngLatAlt;
  private _html: HTMLElement | string;

  constructor(options: PopupOptions) {
    super();
    const { lng, lat, alt, offset, map, html } = options;
    this._lngLatAlt = this._disposePosition(lng, lat, alt);
    this._html = html;
    this._offset = offset;
    this._map = map;

    this._init(options);
  }

  _init(opts: PopupOptions) {
    const { className } = opts;
    if (!this._map) return;

    if (!this._container) {
      const target = this._map.getContainer() as HTMLElement;
      target.style.overflow = "hidden";
      this._container = this._createDOM("div", "hkmap-popup", target);
      this._createDOM("div", "hkmap-popup-tip", this._container);
      this._popupContent = this._createDOM(
        "div",
        "hkmap-popup-content",
        this._container
      );
      if (this._html) this._setHtmlToDom(this._popupContent, this._html);
      if (className) {
        className.split(" ").forEach((v) => this._container.classList.add(v));
      }
    }
    this._map.onCameraChange(() => this._update());

    this._update();
  }

  /**
   * 更新浮窗html
   *
   * @param {(string | HTMLElement)} html
   * @return {*}
   * @memberof Popup
   */
  setHtml(html: string | HTMLElement) {
    if (!html) return;
    this._html = html;
    if (this._popupContent && this._popupContent.parentNode) {
      this._popupContent.parentNode.removeChild(this._popupContent);
    }
    this._popupContent = this._createDOM(
      "div",
      "hkmap-popup-content",
      this._container
    );
    this._setHtmlToDom(this._popupContent, this._html);
    this._update();
  }

  /**
   * 更新浮窗位置
   *
   * @param {LngLatAlt} lngLatAlt
   * @return {*}
   * @memberof Popup
   */
  setLngLatAlt(lngLatAlt: LngLatAlt) {
    if (!lngLatAlt) return;
    this._lngLatAlt = lngLatAlt;
    this._update();
  }

  /**
   * 销毁Popup对象
   *
   * @memberof Popup
   */
  remove() {
    if (this._popupContent && this._popupContent.parentNode) {
      this._popupContent.parentNode.removeChild(this._popupContent);
    }
    if (this._container && this._container.parentNode) {
      this._container.parentNode.removeChild(this._container);
    }
    if (this._map) {
      this._map.offCameraChange(() => this._update());
    }
  }

  /**
   * 更新弹窗
   *
   * @memberof Popup
   */
  _update() {
    const { lng, lat, alt } = this._lngLatAlt;
    const windowPos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
      this._map.viewer.scene,
      Cesium.Cartesian3.fromDegrees(lng, lat, alt)
    );
    const transPos = this._getPopTranslate(windowPos);
    this._setTransformCss(
      this._container,
      `translate(-50%, -100%) translate(${transPos.x}px,${transPos.y}px) translateZ(0)`
    );
  }

  _createDOM(
    tagName: string,
    className?: string,
    container?: Element
  ): HTMLElement {
    const el = document.createElement(tagName);
    if (className !== undefined) el.className = className;
    if (container) container.appendChild(el);
    return el;
  }

  _getPopTranslate({ x, y }) {
    let tansX: number = x;
    let tansY: number = y;
    if (this._offset) {
      const { left, right, bottom, top } = this._offset;
      if (left) tansX = x - left;
      if (right) tansX = x + right;
      if (top) tansY = y - top;
      if (bottom) tansY = y + bottom;
    }
    return { x: Math.round(tansX), y: Math.round(tansY) };
  }

  //dom位置转化
  _setTransformCss(el: HTMLElement, value: string) {
    const transformProp = this._getCssProps(["transform", "WebkitTransform"]);
    el.style[transformProp] = value;
  }

  _setHtmlToDom(target: HTMLElement, child): void {
    if (typeof child === "string") {
      target.innerHTML = child;
    } else {
      target.appendChild(child);
    }
  }

  _getCssProps(props) {
    const docStyle = window.document.documentElement.style;
    if (!docStyle) return props[0];
    for (let i = 0; i < props.length; i++) {
      if (props[i] in docStyle) return props[i];
    }
    return props[0];
  }

  _disposePosition(lng: number, lat: number, alt?: number) {
    const position = { lng, lat, alt };
    if (!(typeof lng === "number") || !(typeof lat === "number")) {
      throw "无效的经纬度";
    }
    if (!alt) position.alt = 0;
    return position;
  }
}

需要引入css样式:

.hkmap-popup {
  position: absolute;
  top: 0;
  left: 0;
  display: -webkit-flex;
  display: flex;
  will-change: transform;
  pointer-events: none;
  -webkit-flex-direction: column-reverse;
  flex-direction: column-reverse;
  z-index: 1;
  zoom: 1.001;

  .hkmap-popup-tip {
    border: 10px solid transparent;
    z-index: 1;
    align-self: center;
    border-bottom: none;
    border-top-color: #fff;
  }
  .hkmap-popup-content {
    min-width: 100px;
    min-height: 40px;
    background-color: #fff;
  }
}

使用示例:

const pop = new Popup({
      lng: 120.12087707148685,
      lat: 30.25188693213902,
      alt: 0,
      map: this.map,
      offset: { top: 10 },
      html: `<div className="ttt-div">测试</div>`,
    });

//修改html:
const newHtml = `<div className="ttt-div">新的html</div>`;
pop.setHtml(newHtml);

//移除popup
pop.remove();

//修改经纬度
pop.setLngLatAlt({
        lng: 120.1308770,
        lat: 30.23888693213902,
        alt: 100,
    })

效果展示:

缺点:

当使用很多弹窗时,需要创建多个popup实例,而每个实例内都对map的cameraChange事件进行了监听,会导致注册过多监听事件,这在设计上是不够完善的。

改进思路=>不单独构建Popup实例,将Popup挂载在map实例的方法内。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值