Skip to content

Popup 弹窗

TIP

鼠标单击标注点,弹出 Popup 标注。

代码如下:

点我查看代码
vue
<template>
  <div id="map">
    <div id="popup" class="ol-popup">
      <a
        href="javascript:void(0)"
        id="popup-closer"
        class="ol-popup-closer"
      ></a>
      <div id="popup-content"></div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, onBeforeUnmount } from "vue";
import { Map, View, Feature } from "ol";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
import { defaults, FullScreen } from "ol/control";
import { XYZ, Vector as VectorSource } from "ol/source";
import { Point } from "ol/geom";
import { ATTRIBUTIONS, SHENZHEN, MAPURL } from "../../../constants";
import {
  createLabelStyle,
  createPopup,
  featuerInfo,
  addFeatrueInfo,
} from "./tools";

const raster = new TileLayer({
  source: new XYZ({
    attributions: ATTRIBUTIONS,
    url: MAPURL,
    maxZoom: 20,
  }),
});
let map: Map | null = null;
const initMap = () => {
  map = new Map({
    //初始化map
    target: "map",
    //地图容器中加载的图层
    layers: [
      //加载瓦片图层数据
      raster,
    ],
    view: new View({
      projection: "EPSG:4326", // 坐标系,有EPSG:4326和EPSG:3 857
      center: SHENZHEN, // 深圳坐标
      //地图初始显示级别
      zoom: 5,
    }),
    //加载控件到地图容器中
    controls: defaults().extend([
      new FullScreen(), //加载全屏显示控件(目前支持非IE内核浏览器)
    ]),
  });
  //实例化Vector要素,通过矢量图层添加到地图容器中
  const iconFeature = new Feature({
    geometry: new Point(SHENZHEN),
    //名称属性
    name: "深圳市",
  });

  iconFeature.setStyle(createLabelStyle(iconFeature));
  //矢量标注的数据源
  const vectorSource = new VectorSource({
    features: [iconFeature],
  });
  //矢量标注图层
  const vectorLayer = new VectorLayer({
    source: vectorSource,
  });
  map.addLayer(vectorLayer);

  /**
   * 实现popup的html元素
   */
  const container = document.getElementById("popup");
  const content = document.getElementById("popup-content");
  const closer = document.getElementById("popup-closer");
  if (container && content && closer) {
    /**
     * 在地图容器中创建一个Overlay
     */
    const popup = createPopup(container);
    map.addOverlay(popup);
    /**
     * 添加关闭按钮的单击事件(隐藏popup)
     * @return {boolean} Don't follow the href.
     */
    closer.onclick = () => {
      //未定义popup位置
      popup.setPosition(undefined);
      //失去焦点
      closer.blur();
    };
    /**
     * 为map添加点击事件监听,渲染弹出popup
     */
    map.on("click", (evt) => {
      if (map === null) return;
      //判断当前单击处是否有要素,捕获到要素时弹出popup
      const feature = map.forEachFeatureAtPixel(
        evt.pixel,
        (feature) => feature
      );
      if (feature) {
        //清空popup的内容容器
        content.innerHTML = "";
        //在popup中加载当前要素的具体信息
        addFeatrueInfo({ info: featuerInfo, content });
        if (popup.getPosition() == undefined) {
          //设置popup的位置
          popup.setPosition(featuerInfo.geo);
        }
      }
    });

    /**
     * 为map添加鼠标移动事件监听,当指向标注时改变鼠标光标状态
     */
    map.on("pointermove", (e) => {
      if (map === null) return;
      const pixel = map.getEventPixel(e.originalEvent);
      const hit = map.hasFeatureAtPixel(pixel);
      map.getTargetElement().style.cursor = hit ? "pointer" : "";
    });
  }
};

onMounted(() => {
  initMap();
});

onBeforeUnmount(() => {
  if (map) {
    map.dispose();
    map = null;
  }
});
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#map {
  position: relative;
  height: 650px;
}
.ol-popup {
  position: absolute;
  width: 200px;
  background-color: white;
  -webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
  padding: 15px;
  border-radius: 10px;
  border: 1px solid #cccccc;
  bottom: 45px;
  left: -50px;
  color: #000;
}

.ol-popup:after,
.ol-popup:before {
  top: 100%;
  border: solid transparent;
  content: " ";
  height: 0;
  width: 0;
  position: absolute;
  pointer-events: none;
}

.ol-popup:after {
  border-top-color: white;
  border-width: 10px;
  left: 48px;
  margin-left: -10px;
}

.ol-popup:before {
  border-top-color: #cccccc;
  border-width: 11px;
  left: 48px;
  margin-left: -11px;
}

.ol-popup-closer {
  text-decoration: none;
  position: absolute;
  top: 2px;
  right: 8px;
}

.ol-popup-closer:after {
  content: "✖";
}

#popup-content {
  font-size: 14px;
  font-family: "微软雅黑";
}

#popup-content .markerInfo {
  font-weight: bold;
}
:deep(.markerImg) {
  width: 210px;
}
</style>

tools 代码如下:

点我查看代码
ts
import { Style, Icon, Text, Fill, Stroke } from 'ol/style'
import { Overlay, Feature } from 'ol'
import { SHENZHEN, AUTHOR_INFO } from '../../../constants'

export interface FeatuerInfo {
  geo: number[];
  att: {
    title: string;
    titleURL: string;
    text: string;
    imgURL: string;
  };
}

//示例标注点北京市的信息对象
export const featuerInfo: FeatuerInfo = {
  geo: SHENZHEN,
  att: {
    //标注信息的标题内容
    title: AUTHOR_INFO.NAME,
    //标注详细信息链接
    titleURL: AUTHOR_INFO.JUEJIN,
    //标注内容简介
    text: `
    <br/>
    年龄:${AUTHOR_INFO.AGE}<br/>
    性别:${AUTHOR_INFO.SEX}<br/>
    目前居住地:${AUTHOR_INFO.LOCATION}<br/>
    QQ:${AUTHOR_INFO.QQ}<br/>
    WX:${AUTHOR_INFO.WX}<br/>
    <br/>
    `,
    //标注的图片
    imgURL: AUTHOR_INFO.AVATAR,
  },
}

export const createPopup = (container: HTMLElement): Overlay =>
  new Overlay(
    /** @type {olx.OverlayOptions} */
    ({
      //要转换成overlay的HTML元素
      element: container,
      //当前窗口可见
      autoPan: true,
      //Popup放置的位置
      positioning: 'bottom-center',
      //是否应该停止事件传播到地图窗口
      stopEvent: false,
      autoPanAnimation: {
        //当Popup超出地图边界时,为了Popup全部可见,地图移动的速度
        duration: 250,
      },
    }),
  )

/**
 * 创建矢量标注样式函数,设置image为图标ol.style.Icon
 * @param {ol.Feature} feature 要素
 */
export const createLabelStyle = (feature: Feature): Style =>
  new Style({
    image: new Icon(
      /** @type {olx.style.IconOptions} */
      ({
        //设置图标点
        anchor: [0.5, 60],
        //图标起点
        anchorOrigin: 'top-right',
        //指定x值为图标点的x值
        anchorXUnits: 'fraction',
        //指定Y值为像素的值
        anchorYUnits: 'pixels',
        //偏移
        offsetOrigin: 'top-right',
        // offset:[0,10],
        //图标缩放比例
        scale: 0.4,
        //透明度
        opacity: 0.75,
        //图标的url
        src: '/image/blue.png',
      }),
    ),
    text: new Text({
      //位置
      textAlign: 'center',
      //基准线
      textBaseline: 'middle',
      //文字样式
      font: 'normal 14px 微软雅黑',
      //文本内容
      text: feature.get('name'),
      //文本填充样式(即文字颜色)
      fill: new Fill({ color: '#aa3300' }),
      stroke: new Stroke({ color: '#ffcc33', width: 2 }),
    }),
  })

/**
 * 动态创建popup的具体内容
 * @param {Object} info 弹框信息
 * @param {Element} content 弹框内容节点
 */
export const addFeatrueInfo = ({ info, content }: { info: FeatuerInfo, content: HTMLElement }) => {
  //新增a元素
  const elementA = document.createElement('a')
  elementA.className = 'markerInfo'
  elementA.href = info.att.titleURL
  setInnerText(elementA, info.att.title)
  // 新建的div元素添加a子节点
  content.appendChild(elementA)
  //新增div元素
  const elementDiv = document.createElement('div')
  elementDiv.className = 'markerText'
  elementDiv.innerHTML = info.att.text
  // 为content添加div子节点
  content.appendChild(elementDiv)
  //新增img元素
  const elementImg = document.createElement('img')
  elementImg.className = 'markerImg'
  elementImg.src = info.att.imgURL
  // 为content添加img子节点
  content.appendChild(elementImg)
}

/**
 * 动态设置元素文本内容(兼容)
 */
function setInnerText(element: HTMLElement, text: string) {
  if (typeof element.textContent == 'string') {
    element.textContent = text
  } else {
    element.innerText = text
  }
}

如有转载或 CV 的请标注本站原文地址