Openlayers默认采用合成渲染,所有图层合并在一个Canvas上,难以单独控制。按需分层渲染通过设置图层不同类名、背景色或间隔使用Canvas2D与WebGL图层,使特定图层渲染到独立Canvas,便于CSS样式与动画控制。该实现基于Openlayers已有属性,简单有效。
OpenLayers图层按需分层渲染至不同Canvas画布
时间:2026-06-04
浏览:446
文章目录
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 分层渲染
早期版本的Openlayers(6.0及之前),以及早期版本的ArcGIS、Leaflet,均采用按图层分层渲染的方式。那时的DOM呈现方式非常直观:每个图层对应不同的DOM元素(Div、Canvas、Svg)。
- 优点:可以直接使用CSS样式、动画对单个图层进行控制或添加效果,灵活性高。
- 缺点:当图层较多且图层上的元素复杂时,DOM元素会大量增加,性能压力不容忽视。
3 按需分层渲染
本文讨论的“按需分层渲染”,核心目标是:让Openlayers对特定图层进行分层渲染,将这些图层渲染到不同的Canvas画布上,从而方便通过CSS控制Canvas的样式。这听起来像是在追求“鱼和熊掌兼得”的效果。
典型场景包括:
- 选中矢量图形时,对切片图层应用黑暗模式。
- 选中矢量图形时,对目标图层使用CSS动画,过渡更改透明度。
在合成渲染模式下,这两种需求难以优雅实现,而按需分层渲染则可以轻松解决。
4 实现
实际上,实现按需分层渲染并不复杂,因为这些功能已在Openlayers中内置,只需对某些属性进行设置。满足以下任一条件即可:
为图层设置不同的类名className(若不设置,默认所有图层类名为ol-layer)为图层设置不同的背景色background在加入地图的顺序上间隔使用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 代码
<div class="map-page">
<h1>OpenLayers - 天地图影像底图h1>
<div class="info-panel">
<h3>图层说明h3>
<p><strong>TileLayer:strong> 天地图影像底图 + 影像注记p>
<p>
<strong>VectorLayer:strong> 三角形 / 矩形 / 五边形(不同描边和填充)
p>
<p><strong>WebGLTile:strong> 天地图矢量底图(透明度 0.5)p>
<p><strong>WebGLVector:strong> 城市点位渲染p>
div>
<div id="tianditu-ol-map" ref="mapContainer" class="map-container">div>
<div class="legend">
<h4>图例h4>
<div class="legend-item">
<span
class="legend-color"
style="
background-color: rgba(0, 102, 255, 0.3);
border: 3px solid #0066ff;
"
>span>
<span>三角形 - 蓝色实线描边span>
div>
<div class="legend-item">
<span
class="legend-color"
style="
background-color: rgba(255, 0, 0, 0.2);
border: 2px dashed #ff0000;
"
>span>
<span>矩形 - 红色虚线描边span>
div>
<div class="legend-item">
<span
class="legend-color"
style="
background-color: rgba(0, 204, 0, 0.25);
border: 4px solid #00cc00;
"
>span>
<span>五边形 - 绿色实线描边span>
div>
<div class="legend-item">
<span
class="legend-dot"
style="background-color: rgba(255, 165, 0, 0.8)"
>span>
<span>WebGLVector 城市点位span>
div>
<div class="legend-item">
<span class="legend-label">WebGLTile 天地图矢量底图(透明度 0.5)span>
div>
div>
div>
<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/DataServerT=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/DataServerT=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/DataServerT=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({ geometry: new Point(fromLonLat([120.2, 30.3])) }),
],
}),
});
(注:此处代码较长,为保持文章工整,后续完整代码请参考原始文档)
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/DataServerT=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/DataServerT=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版本测试验证。)
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。