Openlayers:为Overlay创建element的四种方式

element属性是Overlay的核心,它定义了Overlay的内容。因此在创建Overlay的时候,如何创建它的element就成了一个至关重要的问题。

如果Overlay的结构非常简单,那么利用DOM API 就可以创建一个HTMLElement作为element。但是如果Overlay的结构非常复杂,比如说我希望Overlay当中展示一个echarts绘制的饼图,或者在Overlay中使用Element UI的轮播图组件,又或者我的Overlay内部的结构非常复杂,在这些情况下我们就应该考虑其它的创建element的方式了。在这篇文章中我就将会介绍四种给创建element的方法。

一、通过JS DOM API 创建element

JS本身有一整套完整的DOM API,让我们可以函数式的方式创建DOM树。这是最简单的一种创建element的方式,如果Overlay中的内容特别简单,比如层级特别浅就只有一两个元素,那么可以使用这种方法。

JavaScript 复制代码
// DOM API
const el_blue = document.createElement("div");
el_blue.className = "test-overlay blue";
el_blue.innerText = "蓝色overlay";

addOverlay(window.map, {
  position: e.coordinate,
  element: el_blue.cloneNode(true),
});

需要注意的是一个元素只能绑定一个Overlay,如果你希望多个Overlay都绑定同样的元素,那么可以使用cloneNode方法对元素进行复制,或者通过一个函数来获取元素。

JavaScript 复制代码
element.cloneNode(true)

JavaScript 复制代码
function getOverlayElement(){
  return document.createElement("div");
}

二、将DOMString 作为element

如果element内部的层级较深,那么就不再适合使用DOM API去创建了,那样很麻烦。此时建议可以使用DOMString来创建element。

JavaScript 复制代码
 // DOMString
  let el_blue = `<div class="test-overlay blue">
                      <div>
                          <div>
                              <div>
                                  蓝色overlay
                              </div>
                          </div>            
                      </div>
                  </div>`;

但是请注意Overlay的element属性只接收HTMLElement不接收DOMString,所以需要创建一个元素包装一下。

JavaScript 复制代码
  // DOMString
  let el_blue = `<div class="test-overlay blue">
                      <div>
                          <div>
                              <div>
                                  蓝色overlay
                              </div>
                          </div>            
                      </div>
                  </div>`;


  //创建一个包装元素,将DOMString转换为HMTLElement
  const container = document.createElement("div");
  container.innerHTML = el_blue;
  el_blue = container.firstChild;

  addOverlay(window.map, {
    position: e.coordinate,
    element: el_blue,
  });

三、引用Vue单文件组件中的部分元素作为element

前两种方法简单方便,但是无法应付一些复杂的情况。曾经我为此很是苦恼,那是我就想如果能将Vue组件作为Overlay的element就好了,这样的话既可以很方便的去定义dom树的内容,又可以使用vue的语法。这第三种方案正是可以在一定程度上实现上述的想法。

1.基本思路

整体的思路也很简单,在一个vue文件中的<template>部分写好Overlay element的内容。然后在<script>部分通过模版引用获取到dom,进而创建Overlay。

JavaScript 复制代码
<template>
  <base-map @loadend="onLoadEnd" />

  <!-- overlay -->
  <div class="text-overlay" v-show="textOverlayVisible" ref="textOverlay">
    我是一个overlay
  </div>
</template>

<script setup>
  import { ref, useTemplateRef, onMounted, nextTick } from "vue";
  import * as echarts from "echarts";
  import {
    addOverlay,
    findOverlay,
    removeOverlay,
    clearOverlay,
    setOverlayVisibility,
    toggleOverlayVisibility,
  } from "@/utils/overlay.js";


  const textOverlayVisible = ref(false);
  const textOverlay = ref(null);

  function onLoadEnd() {
    window.map.on("click", e => {
      textOverlayVisible.value = true;
      addOverlay(window.map, {
        position: e.coordinate,
        element: textOverlay.value,
      });
    });
  }

</script>

<style lang="less">
.chart-overlay {
  width: 600px;
  height: 250px;
  #main {
    width: 100%;
    height: 100%;
  }
}
</style>

2.实现对element的复用

如果是希望复用某一个element,那么就可以通过列表渲染在<template>中将一个element复制多份,跟多个Overlay进行绑定。

JavaScript 复制代码
<template>
  <base-map @loadend="onLoadEnd" />

  <template v-for="overlay in textOverlayList" :key="overlay.id">
    <div class="text-overlay" v-show="overlay.visible" :id="overlay.id">
      我是一个overlay
    </div>
  </template>
</template>

<script setup>
import { ref, useTemplateRef, onMounted, nextTick } from "vue";
import * as echarts from "echarts";
import {
  addOverlay,
  findOverlay,
  removeOverlay,
  clearOverlay,
  setOverlayVisibility,
  toggleOverlayVisibility,
} from "@/utils/overlay.js";


const textOverlayList = ref([]);

function onLoadEnd() {
  window.map.on("click", e => {
    const overlayConfig = {
      id: "text-overlay-" + (textOverlayList.value.length + 1),
      visible: true,
    };

    textOverlayList.value.push(overlayConfig);

    nextTick(() => {
      addOverlay(window.map, {
        position: e.coordinate,
        element: document.getElementById(overlayConfig.id),
      });
    });
  });
}

</script>

<style lang="less">
.chart-overlay {
  width: 600px;
  height: 250px;
  #main {
    width: 100%;
    height: 100%;
  }
}
</style>

3.在Overlay中展示echarts图表

利用这种方式当然也就可以实现,带有echarts图表的Overlay

JavaScript 复制代码
<template>
  <base-map @loadend="onLoadEnd" />
  <template v-for="chartOverlay in chartOverlayList" :key="chartOverlay.id">
    <div
      class="chart-overlay"
      v-show="chartOverlay.visible"
      :id="chartOverlay.id">
      <div id="main"></div>
    </div>
  </template>
</template>

<script setup>
import { ref, useTemplateRef, onMounted, nextTick } from "vue";
import * as echarts from "echarts";
import {
  addOverlay,
  findOverlay,
  removeOverlay,
  clearOverlay,
  setOverlayVisibility,
  toggleOverlayVisibility,
} from "@/utils/overlay.js";

const chartOverlayList = ref([]);

function onLoadEnd() {
  window.map.on("click", e => {
    const overlayConfig = {
      id: "chart-overlay-" + (chartOverlayList.value.length + 1),
      visible: true,
    };

    chartOverlayList.value.push(overlayConfig);

    nextTick(() => {
      initChart(document.querySelector(`#${overlayConfig.id}>#main`));

      addOverlay(window.map, {
        position: e.coordinate,
        element: document.getElementById(overlayConfig.id),
      });
    });
  });
}

function initChart(containerDOM) {
  // 初始化echarts图表
  const chartInstance = echarts.init(containerDOM);
  chartInstance.setOption({
    tooltip: {
      trigger: "item",
    },
    legend: {
      top: "5%",
      left: "center",
      type: "scroll",
    },
    series: [
      {
        name: "Access From",
        type: "pie",
        radius: ["40%", "70%"],
        avoidLabelOverlap: false,
        itemStyle: {
          borderRadius: 10,
          borderColor: "#fff",
          borderWidth: 2,
        },
        label: {
          show: false,
          position: "center",
        },
        emphasis: {
          label: {
            show: true,
            fontSize: 40,
            fontWeight: "bold",
          },
        },
        labelLine: {
          show: false,
        },
        data: [
          { value: 1048, name: "Search Engine" },
          { value: 735, name: "Direct" },
          { value: 580, name: "Email" },
          { value: 484, name: "Union Ads" },
          { value: 300, name: "Video Ads" },
        ],
      },
    ],
  });
}
</script>

<style lang="less">
.chart-overlay {
  width: 300px;
  height: 200px;
  border-right-color: pink;
  #main {
    width: 100%;
    height: 100%;
  }
}
</style>

4.缺陷

这种方法虽然相比前两种方法有了很大的进步但是依旧还有一些不足:

  1. 与单文件组件高度绑定,所有的逻辑无论是<template>中的HTMLElement还是添加Overlay的代码,都必需放在单文件组件当中,导致灵活性不足。
  2. 复用element时较为繁琐,需要通过列表渲染才能实现对element的复用,而列表渲染又需要我们去管理一个数组,相对来说比较麻烦。

四、通过Vue实例创建element

Vue是渐进式的开发框架,虽然平时我们更多的是在单文件组件中使用它,但是Vue是可以渐进式增强静态的 HTM,也可以在任何的页面中作为Web Component插入。因此我完全可以在一个js文件中使用Vue去创建我想要的Overlay.element。

当然在Vue2和Vue3中实现的语法上是有一定的差别的,下面我将一 一介绍

1.Vue3中的实现方法

基本的思路如下:

基于上面的思路我便可以将创建图表Overlay的代码封装到一个js文件中,在项目中任何我需要的地方调用里面的方法创建Overlay。

JavaScript 复制代码
import { createApp, ref, onMounted, nextTick, h } from "vue";
import * as echarts from "echarts";
import Overlay from "ol/Overlay.js";

// 导入overlay的样式
import "./chartOverlay.less";

// 添加图表Overlay
export const addChartOverlay = (map, position) => {
  const chartElement = getChartElement();
  // console.log(chartElement);

  const chartOverlay = new Overlay({
    element: chartElement,
    position: position,
    positioning: "center-center",
  });

  map.addOverlay(chartOverlay);
};

// 获取图表Overlay的element
const getChartElement = () => {
  const vm = createApp({
    setup() {
      const chartContainer = ref(null);

      onMounted(() => {
        nextTick(() => {
          initChart(chartContainer.value);
        });
      });

      function initChart(containerDOM) {
        // 初始化echarts图表
        const chartInstance = echarts.init(containerDOM);
        chartInstance.setOption({
          tooltip: {
            trigger: "item",
          },
          legend: {
            top: "5%",
            left: "center",
            type: "scroll",
          },
          series: [
            {
              name: "Access From",
              type: "pie",
              radius: ["40%", "70%"],
              avoidLabelOverlap: false,
              itemStyle: {
                borderRadius: 10,
                borderColor: "#fff",
                borderWidth: 2,
              },
              label: {
                show: false,
                position: "center",
              },
              emphasis: {
                label: {
                  show: true,
                  fontSize: 40,
                  fontWeight: "bold",
                },
              },
              labelLine: {
                show: false,
              },
              data: [
                { value: 1048, name: "Search Engine" },
                { value: 735, name: "Direct" },
                { value: 580, name: "Email" },
                { value: 484, name: "Union Ads" },
                { value: 300, name: "Video Ads" },
              ],
            },
          ],
        });
      }

      return () =>
        h("div", { class: "chart-overlay" }, [
          h("div", { id: "main", ref: chartContainer }),
        ]);
    },
  }).mount(document.createElement("div"));

  return vm.$el;
};

上面我使用的是组合式API,也可以使用选项式API

JavaScript 复制代码
import { createApp, ref, onMounted, nextTick, h } from "vue";
import * as echarts from "echarts";
import Overlay from "ol/Overlay.js";

// 导入overlay的样式
import "./chartOverlay.less";

export const addChartOverlay = (map, position) => {
  const chartElement = getChartElement();
  // console.log(chartElement);

  const chartOverlay = new Overlay({
    element: chartElement,
    position: position,
    positioning: "center-center",
  });

  map.addOverlay(chartOverlay);
};

const getChartElement = () => 
  {
    const vm = createApp({
      render: () =>
        h(
          "div",
          { class: "chart-overlay" },
          [
            h("div", {
              id: "main",
              ref: "chartContainer",
            }),
          ]
        ),

      mounted() {
        const chartContainer = this.$refs.chartContainer;
        this.initChart(chartContainer);
      },
      methods: {
        initChart(containerDOM) {
          nextTick(()=>{
            // 初始化echarts图表
            const chartInstance = echarts.init(containerDOM);
            chartInstance.setOption({
              tooltip: {
                trigger: "item",
              },
              legend: {
                top: "5%",
                left: "center",
                type: "scroll",
              },
              series: [
                {
                  name: "Access From",
                  type: "pie",
                  radius: ["40%", "70%"],
                  avoidLabelOverlap: false,
                  itemStyle: {
                    borderRadius: 10,
                    borderColor: "#fff",
                    borderWidth: 2,
                  },
                  label: {
                    show: false,
                    position: "center",
                  },
                  emphasis: {
                    label: {
                      show: true,
                      fontSize: 40,
                      fontWeight: "bold",
                    },
                  },
                  labelLine: {
                    show: false,
                  },
                  data: [
                    { value: 1048, name: "Search Engine" },
                    { value: 735, name: "Direct" },
                    { value: 580, name: "Email" },
                    { value: 484, name: "Union Ads" },
                    { value: 300, name: "Video Ads" },
                  ],
                },
              ],
            });
          })
        },
      },
    }).mount(document.createElement("div"));

    return vm.$el;
  };

还可以直接将一个外部组件作为构建Vue实例时的根组件。

外部组件:

JavaScript 复制代码
 <template>
  <div class="chart-overlay">
    <div id="main" ref="chartContainer"></div>
  </div>
</template>

<script setup>
import { nextTick, onMounted, ref } from "vue";
import * as echarts from "echarts";

const chartContainer = ref(null);
onMounted(() => {
  nextTick(() => {
    // 初始化echarts图表
    const myChart = echarts.init(chartContainer.value);
    myChart.setOption({
      tooltip: {
        trigger: "item",
      },
      legend: {
        top: "5%",
        left: "center",
        type: "scroll",
      },
      series: [
        {
          name: "Access From",
          type: "pie",
          radius: ["40%", "70%"],
          avoidLabelOverlap: false,
          itemStyle: {
            borderRadius: 10,
            borderColor: "#fff",
            borderWidth: 2,
          },
          label: {
            show: false,
            position: "center",
          },
          emphasis: {
            label: {
              show: true,
              fontSize: 40,
              fontWeight: "bold",
            },
          },
          labelLine: {
            show: false,
          },
          data: [
            { value: 1048, name: "Search Engine" },
            { value: 735, name: "Direct" },
            { value: 580, name: "Email" },
            { value: 484, name: "Union Ads" },
            { value: 300, name: "Video Ads" },
          ],
        },
      ],
    });
  });
});
</script>

<style lang="less">
.chart-overlay {
  width: 200px;
  height: 150px;
  #main {
    width: 100%;
    height: 100%;
  }
}
</style>

创建Overlay的方法

JavaScript 复制代码
import { createApp, ref, onMounted, nextTick, h } from "vue";
import Overlay from "ol/Overlay.js";

// 导入overlay的模板组件
import chartOverlay from "./chartOverlay.vue";

export const addChartOverlay = (map, position) => {
  const chartElement = getChartElement();
  const chartOverlay = new Overlay({
    element: chartElement,
    position: position,
    positioning: "center-center",
  });

  map.addOverlay(chartOverlay);
};

const getChartElement = () => {
  const vm = createApp(chartOverlay).mount(document.createElement("div"));
  return vm.$el;
};

2.Vue2中的实现方式

在Vue2中使用实例创建overlay.element的是思路和步骤与Vue3中基本一致,唯一的区别就是Vue2中通过Vue函数创建实例而不是createApp

JavaScript 复制代码
import Vue from "vue";
import * as echarts from "echarts";
import Overlay from "ol/Overlay.js";

// 导入overlay的样式
import "./chartOverlay.less";

export const addChartOverlay = (map, position) => {
  const chartElement = getChartElement();
  // console.log(chartElement);

  const chartOverlay = new Overlay({
    element: chartElement,
    position: position,
    positioning: "center-center",
  });

  map.addOverlay(chartOverlay);
};

const getChartElement = () => 
  {
    const vm = Vue({
      render: () =>
        h(
          "div",
          { class: "chart-overlay" },
          [
            h("div", {
              id: "main",
              ref: "chartContainer",
            }),
          ]
        ),

      mounted() {
        const chartContainer = this.$refs.chartContainer;
        this.initChart(chartContainer);
      },
      methods: {
        initChart(containerDOM) {
          nextTick(()=>{
            // 初始化echarts图表
            const chartInstance = echarts.init(containerDOM);
            chartInstance.setOption({
              tooltip: {
                trigger: "item",
              },
              legend: {
                top: "5%",
                left: "center",
                type: "scroll",
              },
              series: [
                {
                  name: "Access From",
                  type: "pie",
                  radius: ["40%", "70%"],
                  avoidLabelOverlap: false,
                  itemStyle: {
                    borderRadius: 10,
                    borderColor: "#fff",
                    borderWidth: 2,
                  },
                  label: {
                    show: false,
                    position: "center",
                  },
                  emphasis: {
                    label: {
                      show: true,
                      fontSize: 40,
                      fontWeight: "bold",
                    },
                  },
                  labelLine: {
                    show: false,
                  },
                  data: [
                    { value: 1048, name: "Search Engine" },
                    { value: 735, name: "Direct" },
                    { value: 580, name: "Email" },
                    { value: 484, name: "Union Ads" },
                    { value: 300, name: "Video Ads" },
                  ],
                },
              ],
            });
          })
        },
      },
    }).$mount(document.createElement("div"));

    return vm.$el;
  };

参考资料

  1. OpenLayers v10.5.0 API - Class: Overlay
  2. Vue.js
  3. API --- Vue.js
相关推荐
风无雨1 小时前
react antd 项目报错Warning: Each child in a list should have a unique “key“prop
前端·react.js·前端框架
人无远虑必有近忧!1 小时前
video标签播放mp4格式视频只有声音没有图像的问题
前端·video
记得早睡~4 小时前
leetcode51-N皇后
javascript·算法·leetcode·typescript
安分小尧6 小时前
React 文件上传新玩法:Aliyun OSS 加持的智能上传组件
前端·react.js·前端框架
编程社区管理员6 小时前
React安装使用教程
前端·react.js·前端框架
小小鸭程序员6 小时前
Vue组件化开发深度解析:Element UI与Ant Design Vue对比实践
java·vue.js·spring·ui·elementui
拉不动的猪6 小时前
vue自定义指令的几个注意点
前端·javascript·vue.js
yanyu-yaya6 小时前
react redux的学习,单个reducer
前端·javascript·react.js
陌路物是人非6 小时前
SpringBoot + Netty + Vue + WebSocket实现在线聊天
vue.js·spring boot·websocket·netty
skywalk81636 小时前
OpenRouter开源的AI大模型路由工具,统一API调用
服务器·前端·人工智能·openrouter