游乐游手机版
首页/前端开发/文章详情

OpenLayers图层按需分层渲染至多Canvas画布

时间:2026-06-13 06:48
Openlayers默认采用合成渲染,所有图层合并在一个Canvas上,难以单独控制。按需分层渲染通过设置图层不同类名、背景色或间隔使用Canvas2D与WebGL图层,使特定图层渲染到独立Canvas,便于CSS样式与动画控制。该实现基于Openlayers已有属性,简单有效。

Openlayers图层按需分层渲染到不同Canvas画布

从事GIS开发的开发者都清楚,Openlayers的渲染机制颇具特色。在默认情况下,它采用“合成渲染”策略——通过协调Canvas 2D和WebGL两个渲染管线,将各个图层统一合并到一个或两个Canvas画布上。听起来很省心?但这种方案的利弊同样突出。

抖音极速版赚钱快速提现方法☜☜☜☜☜点击保存

各个库版本信息如下:

    "ol": "^10.9.0",
    "proj4": "^2.20.8",
    "vue3-openlayers": "^12.2.2"

1 合成渲染模式

Openlayers当前的渲染策略为合成渲染,即通过调度Canvas 2D与WebGL两个渲染管线,借助Composite渲染合成器将各图层(如切片图层、矢量图层等)按不同渲染机制处理,最终在浏览器DOM中对应的Canvas画布上呈现图像(默认情况下,Canvas 2D对应一个canvas,WebGL对应一个canvas)。

  • 优势:在常规简单场景中,同一渲染管线处理的数据仅呈现于一个Canvas画布,管理较为便捷。
  • 劣势:一旦通过CSS样式、动画对Canvas画布进行控制或添加效果,将影响画布上的所有元素——换言之,所有合成在当前Canvas的图层都会同步变动,无法单独操控某一图层。

2 分层渲染模式

实际上,在早期版本(6.0及之前)的Openlayers中,以及早期版本的ArcGIS、Leaflet,均采用按图层分层渲染的方式。那时的DOM呈现方式非常直观:每个图层对应独立的DOM元素(Div、Canvas、Svg)。

  • 优势:可直接利用CSS样式、动画对特定图层进行控制或添加效果,操作自由度极高。
  • 劣势:当图层数量较多且图层元素复杂时,DOM元素会显著增加,性能压力不容小觑。

3 按需分层渲染方案

本文所讨论的“按需分层渲染”,核心目标只有一个:让Openlayers对特定图层实施分层渲染,将这些图层输出到不同的Canvas画布上,从而便于通过CSS控制Canvas的样式。听起来是不是有点“鱼与熊掌兼得”的意味?

典型应用场景:

  1. 选中矢量图形时,对切片图层应用黑暗模式。
  2. 选中矢量图形时,对目标图层使用CSS动画,平滑过渡更改透明度。

这两种需求在合成渲染模式下基本难以优雅实现,而按需分层渲染则能轻松应对。

4 实现方法

实际上,实现按需分层渲染并不复杂,因为这些功能Openlayers已经内置支持,只需对部分属性进行配置即可。满足以下任一条件即可实现:

  1. 为图层设置不同的类名className(未设置时,默认所有图层类名为ol-layer)
  2. 为图层设置不同的背景色background
  3. 在加入地图的顺序中间隔使用Canvas 2D图层和WebGL图层

5 对比分析(Openlayers原生)

5.1 Openlayers默认合成渲染效果

5.1.1 渲染结果展示

结合以下代码可以发现:

  • tiandituImgLayer、tiandituCiaLayer、vectorLayer1、vectorLayer2、vectorLayer3被合并渲染至绿色框内的canvas中。
  • webglTileLayer、webglVectorLayer被合并渲染至蓝色框内的canvas中。

直观对比来看,在DOM中直接操作:

  • 删除绿色框内的canvas后:

  • 删除蓝色框内的canvas后:

5.1.2 代码示例

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import Map from "ol/Map";
import View from "ol/View";
import TileLayer from "ol/layer/Tile";
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import { XYZ } from "ol/source";
import { Style, Fill, Stroke } from "ol/style";
import { Polygon, Point } from "ol/geom";
import Feature from "ol/Feature";
import { fromLonLat } from "ol/proj";
import WebGLTile from "ol/layer/WebGLTile";
import WebGLVector from "ol/layer/WebGLVector";// 天地图 Token(请替换为自己的 Token)
const TK = "";const mapContainer = ref<HTMLDivElement>();
let map: Map | null = null;// ===== 天地图影像底图 =====
const tiandituImgLayer = new TileLayer({
  source: new XYZ({
    url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${TK}`,
  }),
});// 天地图影像注记层
const tiandituCiaLayer = new TileLayer({
  source: new XYZ({
    url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=${TK}`,
  }),
});// ===== 三个矢量图层(不同形状、描边、填充) =====// 图层1: 三角形 - 蓝色实线描边, 浅蓝填充
const vectorLayer1 = new VectorLayer({
  source: new VectorSource({
    features: [
      new Feature({
        geometry: new Polygon([
          [
            fromLonLat([100, 28]),
            fromLonLat([108, 28]),
            fromLonLat([104, 36]),
            fromLonLat([100, 28]),
          ],
        ]),
      }),
    ],
  }),
  style: new Style({
    stroke: new Stroke({ color: "#0066FF", width: 3 }),
    fill: new Fill({ color: "rgba(0, 102, 255, 0.3)" }),
  }),
});// 图层2: 矩形 - 红色虚线描边, 浅红填充
const vectorLayer2 = new VectorLayer({
  source: new VectorSource({
    features: [
      new Feature({
        geometry: new Polygon([
          [
            fromLonLat([110, 24]),
            fromLonLat([118, 24]),
            fromLonLat([118, 30]),
            fromLonLat([110, 30]),
            fromLonLat([110, 24]),
          ],
        ]),
      }),
    ],
  }),
  style: new Style({
    stroke: new Stroke({ color: "#FF0000", width: 2, lineDash: [10, 5] }),
    fill: new Fill({ color: "rgba(255, 0, 0, 0.2)" }),
  }),
});// 图层3: 五边形 - 绿色实线描边, 浅绿填充
const vectorLayer3 = new VectorLayer({
  source: new VectorSource({
    features: [
      new Feature({
        geometry: new Polygon([
          [
            fromLonLat([108, 38]),
            fromLonLat([113, 35]),
            fromLonLat([118, 38]),
            fromLonLat([115, 43]),
            fromLonLat([110, 43]),
            fromLonLat([108, 38]),
          ],
        ]),
      }),
    ],
  }),
  style: new Style({
    stroke: new Stroke({ color: "#00CC00", width: 4 }),
    fill: new Fill({ color: "rgba(0, 204, 0, 0.25)" }),
  }),
});// ===== WebGLTile 图层 - 天地图矢量底图, 透明度 0.5 =====
const webglTileLayer = new WebGLTile({
  opacity: 0.5,
  source: new XYZ({
    url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${TK}`,
  }) as any,
});// ===== WebGLVector 图层 - 城市点位 =====
const webglVectorLayer = new WebGLVector({
  source: new VectorSource({
    features: [
      new Feature({ geometry: new Point(fromLonLat([116.4, 39.9])) }),
      new Feature({ geometry: new Point(fromLonLat([121.5, 31.2])) }),
      new Feature({ geometry: new Point(fromLonLat([113.3, 23.1])) }),
      new Feature({ #

(注:此处代码较长,为保持文章结构工整,完整代码请参考原始文档)

5.2 Openlayers按需分层渲染实现

5.2.1 设置类名与背景色
5.2.1.1 渲染效果呈现

结合以下代码可以看出:

  • tiandituImgLayer、tiandituCiaLayer,类名均为layer1,被合并渲染至红色框内的canvas中。
  • vectorLayer1,类名为layer2,被单独渲染至橙色框内的canvas中。
  • vectorLayer2,类名为layer3,被渲染至绿色框内的canvas中。
  • vectorLayer3,类名同样为layer3,但设置了背景色rgba(0,0,0,0.5),被渲染至蓝色框内的canvas中。
  • webglTileLayer,类名为layer4,被渲染至黑色框内的canvas中。
  • webglVectorLayer,类名为layer5,被渲染至灰色框内的canvas中。
5.2.1.2 关键代码展示

// 其他代码保持不变,关键改动仅在于为每个图层显式指定 className
const tiandituImgLayer = new TileLayer({
  source: new XYZ({
    url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${TK}`,
  }),
  className: "layer1",  // 关键配置
});

const tiandituCiaLayer = new TileLayer({
  source: new XYZ({
    url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=${TK}`,
  }),
  className: "layer1",  // 与底图共享同一个canvas
});

const vectorLayer1 = new VectorLayer({
  // ... 省略几何等配置
  className: "layer2",  // 独立canvas
});

const vectorLayer2 = new VectorLayer({
  // ... 省略几何等配置
  className: "layer3",  // 独立canvas
});

const vectorLayer3 = new VectorLayer({
  // ... 省略几何等配置
  className: "layer3",  // 与vectorLayer2同class,但background不同
  background: "rgba(0,0,0,0.5)",  // 背景色不同,也会触发独立canvas
});

const webglTileLayer = new WebGLTile({
  // ... 
  className: "layer4",
});

const webglVectorLayer = new WebGLVector({
  // ...
  className: "layer5",
});
5.2.2 通过调整图层顺序实现分层
5.2.2.1 渲染效果展示

结合以下代码可以发现:

  • tiandituImgLayer、tiandituCiaLayer,类名均为ol-layer(默认值),被合并渲染至红色框内的canvas中。
  • webglTileLayer,同样是ol-layer,被渲染至橙色框内的canvas中——此处已被独立出来,因为它是WebGL图层,与前面的Canvas 2D图层类型不同。
  • vectorLayer1,ol-layer,被渲染至绿色框内的canvas中。
  • webglVectorLayer,ol-layer,被渲染至蓝色框内的canvas中。
  • vectorLayer2、vectorLayer3,ol-layer,被渲染至黑色框内的canvas中。

注意:本例中所有图层均未设置className,但通过交替添加Canvas 2D和WebGL图层,使Openlayers识别到渲染管线差异,自动分配到不同的canvas中。

5.2.2.2 关键代码实例

// 关键:通过调整 layers 数组中的排列顺序
onMounted(() => {
  map = new Map({
    target: mapContainer.value!,
    layers: [
      tiandituImgLayer,    // Canvas 2D
      tiandituCiaLayer,    // Canvas 2D
      webglTileLayer,      // WebGL -> 与前面不同,触发新canvas
      vectorLayer1,        // Canvas 2D -> 与前面不同,触发新canvas
      webglVectorLayer,    // WebGL -> 触发新canvas
      vectorLayer2,        // Canvas 2D -> 触发新canvas
      vectorLayer3,        // Canvas 2D (与vectorLayer2同类型,合并)
    ],
    view: new View({
      center: fromLonLat([108, 34]),
      zoom: 5,
    }),
  });
});
5.2.3 为图层添加CSS动画效果
5.2.3.1 渲染效果展示

5.2.3.2 关键代码实现

// 为特定类名的图层添加CSS动画
// 在style中写入:
:deep(.layer1) {
  animation: fadeIn 2s ease-in infinite;
}

@keyframes fadeIn {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

如此一来,只有类名为layer1的图层(天地图影像底图和注记层)会执行淡入淡出动画,其他图层完全不受干扰。

6 对比分析(Vue3-Openlayers版本)

6.1 默认合成渲染模式

6.1.1 渲染结果展示

结合代码可以发现:

  • tiandituImgLayer、tiandituCiaLayer、vectorLayer1、vectorLayer2、vectorLayer3被合并渲染至绿色框内的canvas中。
  • webglTileLayer、webglVectorLayer被合并渲染至蓝色框内的canvas中。

另外值得留意的一个细节:使用Vue3-Openlayers后,DOM中会额外出现一些空div,高度均为0。这算是框架封装带来的一点小代价。

6.1.2 代码示例


// 该部分代码较长,与原生写法类似,文档中已详细展示,此处不再重复

6.2 按需分层渲染实现

6.2.1 设置类名与背景色
6.2.1.1 渲染效果呈现

6.2.1.2 关键代码展示

与原生写法一致,只需在组件上绑定className属性和background属性即可达到预期效果。




  




  




  
    
      
      
    
  




  
    
      
      
    
  




  
    
      
      
    
  




  




  

6.2.2 通过调整图层顺序实现分层
6.2.2.1 渲染效果展示

6.2.2.2 关键代码实例

同样只需调整组件在模板中的排列顺序即可实现。



...
...
...  
...                                
...  
...                                
...                                

从实际效果来看,按需分层渲染的思路非常清晰,实现成本也很低,基本上只需配置几个属性即可。无论是原生Openlayers还是Vue3-Openlayers封装版本,都可以轻松实现。关键在于理解Openlayers内部判断“是否需要新建canvas”的逻辑:当两个图层的className不同、background不同,或者渲染管线类型交替出现时,就会触发分层渲染。掌握了这一要点,复杂的CSS动画和样式控制便不再是难题。

(注:所有图片、代码示例均基于Openlayers ^10.9.0、Vue3-Openlayers ^12.2.2版本测试验证。)

来源:https://juejin.cn/post/7646820023176642586
上一篇TinyVue图标使用完全指南从入门到精通 下一篇vxe-table全局默认参数:setConfig/setIcon/setTheme设置
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
如何在JavaScript中实现基于旋转视野的FOV射线绘制详解
前端开发 · 2026-07-01

如何在JavaScript中实现基于旋转视野的FOV射线绘制详解

如果用一句话概括核心,那就是:在 RayCasting 游戏开发中,绘制动态视野边界线(FOV)最可靠的方式是在逻辑层通过数学公式将坐标“算”出来,而不是依赖 Canvas 绘图上下文的旋转操作。 在实现类似 Doom 风格的 RayCasting 游戏时,动态视野(Field of View, F

TypeScript后端数据正确映射为前端接口类型的方法
前端开发 · 2026-07-01

TypeScript后端数据正确映射为前端接口类型的方法

在后端数据与前端类型之间来回转换,几乎是每位 TypeScript 开发者都无法回避的常态。后端返回的 car_brand、reg_number,和前端接口中定义的 brand、govtNumber,命名风格常常对不上号。此时,如果为了省事直接用 as 类型断言“强行”指认类型,那就踩进了常见的陷阱

动态HTML表格按层级条件合并单元格的JavaScript实现
前端开发 · 2026-07-01

动态HTML表格按层级条件合并单元格的JavaScript实现

本文详细讲解一种递归式 JavaScript 合并单元格方法,用于按列优先级(如前3列)智能合并表格行:仅当前一列已合并的前提下,才允许后续列合并相同值,从而精准实现多级分组与层级表格合并效果。 在动态生成的 HTML 表格中,按业务逻辑合并重复行是常见需求。然而,简单地对单列分别遍历合并——例如先

Next.js 13+重定向后滚动失效解决方案
前端开发 · 2026-07-01

Next.js 13+重定向后滚动失效解决方案

在 Next js App Router 的日常开发中,有一个令人颇为困扰的异常现象——当服务端执行 `redirect()` 跳转后,目标页面竟然无法正常滚动。没错,页面已经渲染完成,内容也完整显示,但垂直滚动条仿佛凭空消失。这个问题在 Next js 13 5 4 版本中尤为突出。 先给出结论:

WebGL图像加载延迟的纹理初始化时立即显示方法
前端开发 · 2026-07-01

WebGL图像加载延迟的纹理初始化时立即显示方法

本文详细介绍如何利用 Promise 与 async await 重构 WebGL 纹理加载流程,彻底解决首次渲染显示蓝色占位色、需要手动交互才能刷新的问题,实现文件导入后四张纹理平面即时正确渲染。 实际上,这个坑在 WebGL 开发中相当常见——纹理异步加载的小陷阱,说起来不大,但第一次遇到确实令