
deck.gl Views 视图体系完全指南View、ViewState 与 Viewport 的协作与多视图实战【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl导读deck.gl 的视图View体系决定了同一份数据如何通过不同的相机被投影到屏幕上它定义了一个或多个矩形视口、每个视口内的相机参数以及与之绑定的交互控制器。本指南以 docs/developer-guide/views.md 为主线结合 modules/core/src/views 下的真实源码实现系统讲解View/ViewState/Viewport三个核心概念的职责划分、六种内置视图类的适用场景、单视图与多视图的代码写法、多画布渲染、按视图过滤图层与拾取以及视图相关的性能注意事项。读完本文你将能够独立搭建分屏地图、小地图minimap、第一人称 俯视联动、以及面向非地理数据的正交/轨道视图等典型应用。视图体系概览为什么需要理解 View API同一份数据可以用不同的投影方法得到截然不同的屏幕呈现。deck.gl 的默认视图是 MapView它实现 Web Mercator 投影专门用于与 Mapbox、Google Maps 等外部底图库对齐。只要使用地理数据且只想要一个铺满画布的标准视口应用甚至不需要显式配置任何 View——deck.gl 会自动创建填满整个画布的MapView。但视图体系被设计成灵活且可组合的它能够处理并排视图、重叠视图、多画布等多种配置。如果你要处理非地理数据图表、图、3D 模型等或需要同时展示多个视口就值得花时间熟悉ViewAPI。视图类的核心职责是让应用指定一个或多个矩形视口并控制每个视口内渲染什么内容。View、ViewState 与 Viewport三个概念的职责分工理解视图体系的关键是先分清三个容易混淆的概念。它们分别对应相机的硬配置、相机的实时状态和相机本身。View相机的硬配置一个 View 实例定义以下信息唯一id在多视图场景中id用于匹配 view state 与放置内容默认取构造类的displayName见 view.ts 中this.id id || this.constructor.displayName || view。可选的canvasId在多画布模式下选择呈现目标画布。在画布上的位置与范围x、y、width、height以及padding。这些属性含 padding接受 CSS 风格的表达式可混用数字、百分比、px单位、括号与calc()加减运算例如calc(50% - 10px)。相机参数视野field of view、近/远裁剪面、透视 vs 正交等决定数据如何被投影进该视图。控制器controller监听指针事件与触摸手势把用户输入翻译为 view state 变化启用后相机即变为可交互。从源码看View基类的构造器把x/y/width/height/padding全部交给parsePosition解析为布局表达式view.ts运行时再根据画布尺寸求值。其解析器实现在 positions.ts支持数字字面量、百分比、带/-与括号的二元表达式以及可选的px后缀与calc()包装非法表达式会抛出明确错误。这正是文档中CSS 风格表达式的底层实现。总结View实例包装了相机的硬配置一旦定义通常不需要频繁改变。deck.gl 允许多个视图并存将屏幕划分为多个相同或不同区域这些视图可被用户或应用同步联动、也可分别独立控制。ViewState相机的实时属性View必须与viewState对象配合使用。view state 描述视图在运行时的临时属性如相机位置、朝向、缩放等如果视图可交互用户每次平移/旋转/缩放时 view state 都会被更新以反映变化。它描述的是相机的实时属性会在交互或过渡期间持续更新。Viewport相机本身Viewport 实例就是相机。它由View实例结合其viewState解析而来承担坐标投影/反投影、投影矩阵计算、以及 shader 所需 GLSL uniforms 等数学运算。每当viewState更新视图就会在底层创建新的 viewport。通常 deck.gl 使用者无需直接操作 viewport但在某些场景下viewport 实例提供的 JavaScript 投影/反投影函数会很实用。注意如果使用 Deck 画布作为另一个库渲染的底图的叠加层overlay可能需要用该库提供的 API 更新 viewport而非通过 deck.gl参见 using-with-map。六种内置 View 类与适用场景deck.gl 提供一组View类封装了可视化和交互所需的相机与控制器逻辑。可按数据类型地理 vs 二维图表与期望视角俯视、第一人称等选择一个或多个。不同视图使用的 view state 参数集合不同具体参数请查阅各视图类文档。下表整理了六种视图类的定位与状态对应 views.md 及 modules/core/src/views 源码View 类适用场景状态说明View通用/高级—基类需自行提供原始视图矩阵与投影矩阵通常仅在处理外部来源如 WebVR API提供的视图时才直接实例化。MapView默认地理完整支持使用 Web Mercator 投影渲染数据设计用于与 Mapbox、Google Maps 等外部底图库对齐。GlobeView地理实验性以 3D 地球形式渲染数据。FirstPersonView地理完整支持相机位于给定的地理位置并朝给定方向观察类似第一人称游戏视角。OrthographicView信息可视化2D完整支持相机从正上方看向目标点不旋转。OrbitView信息可视化3D完整支持相机从给定方向看向目标点可绕目标旋转。从源码看各视图的构成每个具体 View 类都非常薄本质上是viewport 类型 控制器类型 默认参数的组合声明MapViewmap-view.tsgetViewportType()返回WebMercatorViewport控制器为MapController。其MapViewState包含longitude、latitude、zoom、pitch、bearing默认值 0/0/20/0/60 等区间约束MapViewProps还支持repeat低缩放级别平铺多份地图、nearZMultiplier/farZMultiplier、fovy、altitude默认 1.5、orthographic默认透视投影等。FirstPersonViewfirst-person-view.tsviewport 为FirstPersonViewport控制器为FirstPersonController。FirstPersonViewState含longitude/latitude、position相机相对锚点的米制偏移默认[0,0,0]、bearing、pitch默认 0范围minPitch/maxPitch默认 ±90、modelMatrixFirstPersonViewProps含fovy默认 75°、near0.1、far1000、focalDistance。OrthographicVieworthographic-view.tsviewport 为OrthographicViewport控制器为OrthographicController。OrthographicViewState以target默认[0,0,0]与zoom默认 0即 1 单位距离映射 1 像素为核心还支持独立的zoomX/zoomY、zoomAxis以及 X/Y 各自的 min/max 缩放约束OrthographicViewProps提供near、far与flipY默认true即左上角原点坐标系。OrbitVieworbit-view.tsviewport 为OrbitViewport控制器为OrbitController。OrbitViewState含target、zoom、rotationOrbit、rotationX默认范围 ±90等OrbitViewProps的orbitAxis默认Z可改为Yfovy默认 50°支持orthographic开关。GlobeViewglobe-view.tsviewport 为GlobeViewport控制器为GlobeController构造时会合并cullMode: back默认 GPU 参数。值得注意的细节是getViewportType(viewState)会返回viewState.zoom 12 ? WebMercatorViewport : GlobeViewport——即缩放超过 12 级时自动切换到 Web Mercator 视口以获得更清晰的平面渲染。单视图实战从默认 MapView 到自定义视图使用一个 View如果未指定Deck的viewspropdeck.gl 自动创建一个填满整个画布的MapView因此基础地理应用通常无需显式指定任何 View。处理非地理数据时则需要手动创建适合信息可视化的视图import {Deck, OrthographicView} from deck.gl/core; const deck new Deck({ // ... views: new OrthographicView() });TypeScript 版本完全相同import {Deck, OrthographicView} from deck.gl/core;React 版本则把views传给deck.gl/react的DeckGL组件import React from react; import {DeckGL} from deck.gl/react; import {OrthographicView} from deck.gl/core; function App() { return DeckGL // ... views{new OrthographicView()} /; }使用 View View State受控与非受控两种模式有状态stateful模式initialViewState提供initialViewState后deck.gl 自动追踪交互式视图的 view stateimport {Deck} from deck.gl/core; const INITIAL_VIEW_STATE { longitude: -122.4, latitude: 37.8, zoom: 12, pitch: 0, bearing: 0 }; const deckInstance new Deck({ initialViewState: INITIAL_VIEW_STATE, controller: true });TypeScript 可显式标注类型import {Deck, MapViewState} from deck.gl/core;并写作const INITIAL_VIEW_STATE: MapViewState {...}React 中同样把initialViewState与controller传给DeckGL。无状态stateless模式viewStateonViewStateChange若需要在 deck.gl 之外自行管理与操纵 view state则提供viewStateprop此时 deck.gl 作为无状态组件并自行监听onViewStateChange回调、手动更新 view stateimport {Deck, OrthographicView} from deck.gl/core; const INITIAL_VIEW_STATE { target: [0, 0, 0], zoom: 1 }; const deckInstance new Deck({ viewState: INITIAL_VIEW_STATE, controller: true, onViewStateChange: e { deckInstance.setProps({ viewState: e.viewState }); } }); document.getElementById(reset-btn).onclick () { deckInstance.setProps({ viewState: INITIAL_VIEW_STATE }); }TypeScript 对应写法new DeckOrthographicView({...})并用OrthographicViewState标注初始状态。React 则通过useState持有 view state把onViewStateChange{e setViewState(e.viewState)}传给DeckGL实现Reset按钮只需setViewState(INITIAL_VIEW_STATE)。两种模式的取舍initialViewState适合让 deck.gl 全权管理相机交互自动生效viewState适合需要外部同步相机状态、动画、多实例联动或实现自定义重置逻辑的场景。deck.gl 源码中viewState与initialViewState均为 DeckProps 的可选成员viewState描述相机状态由外部管理initialViewState则让 Deck 内部自动追踪变化。多视图实战分屏、小地图与多画布viewsprop 接受一个View实例数组。视图用x左、y上、width、height指定视口在屏幕上的位置与范围既可以是数字也可以是 CSS 风格百分比字符串如width: 50%画布尺寸变化时会在运行时重新求值。并排双地图 相机同步下面示例展示两张地图并排显示任一地图的相机变化都会同步到另一张import {Deck, MapView} from deck.gl/core; const deckInstance new Deck({ views: [ new MapView({id: left, x: 0, width: 50%, controller: true}), new MapView({id: right, x: 50%, width: 50%, controller: true}) ], viewState: { longitude: -122.4, latitude: 37.8, zoom: 12 }, onViewStateChange: ({viewState}) { deckInstance.setProps({viewState}); } });React 版本用useState持有MapViewState把views数组与onViewStateChange{evt setViewState(evt.viewState)}传给DeckGL。TypeScript 中可用new Deck[MapView, MapView]({...})显式声明视图元组类型。渲染到多个画布Multi-canvas 模式当页面布局需要多个相互独立的地图表面、同时又想共享同一个Deck实例时多画布模式非常有用。此时若提供Deck._canvases每个视图还可指定canvasId该模式下每个画布拥有独立的呈现目标presentation target与事件管理器event manager视图布局相对于所分配的画布解析而非全局 deck 矩形控制器与拾取picking被限定在所分配的画布范围内视图专属 widget 根据其viewId定位到对应呈现画布但其 DOM 仍位于共享的 widget 根节点之下参见 Using with Multiple Canvases。先在页面布局中创建呈现画布canvas idcanvas-london/canvas canvas idcanvas-tokyo/canvas再把已有元素 id 传给Deck._canvases并将每个视图绑定到匹配的画布import {Deck, MapView} from deck.gl/core; const views [ new MapView({id: london, canvasId: canvas-london, controller: true}), new MapView({id: tokyo, canvasId: canvas-tokyo, controller: true}) ]; new Deck({ _canvases: [canvas-london, canvas-tokyo], views, layers });源码层面视图管理由 view-manager.ts 承担未声明呈现画布的视图使用DEFAULT_CANVAS_ID default-canvas第 42 行每个视图的控制器事件被挂到其当前呈现画布的事件管理器上按像素查询视口时会先按canvasId限定画布、再测试像素包含关系第 219-225 行。getCanvasId(viewId)第 266 行起即用于把视图解析到对应画布。多视图 多 view state小地图minimap联动多视图时每个View既可拥有独立的 view state也可与其他视图共享同一 view state。要为特定视图定义 view state只需在viewState对象中添加与该视图 id 匹配的键。下面示例在角落显示一个与主视图同步、但视角不同的小地图import {Deck, MapView} from deck.gl/core; let currentViewState { main: { longitude: -122.4, latitude: 37.8, pitch: 30, zoom: 12, }, minimap: { longitude: -122.4, latitude: 37.8, zoom: 8 } }; function onViewStateChange({viewId, viewState}) { if (viewId main) { // 主视图相机移动时小地图跟随 currentViewState { main: viewState, minimap: { ...currentViewState.minimap, longitude: viewState.longitude, latitude: viewState.latitude } }; } else { // 小地图只允许用户修改缩放 currentViewState { main: currentViewState.main, minimap: { ...currentViewState.minimap, zoom: viewState.zoom } }; } // 应用新的 view state deckInstance.setProps({viewState: currentViewState}); }; const deckInstance new Deck({ views: [ new MapView({id: main, controller: true}), new MapView({id: minimap, x: 10, y: 10, width: 300, height: 200, controller: true}) ], viewState: currentViewState, onViewStateChange });这段代码的要点主视图main的相机变化会被完整采纳同时只把小地图的位置经纬度同步过去小地图自身只允许用户调整缩放。React 版本用useStateuseCallback实现同样的联动逻辑注意原文档示例中 JS/TS 分支里存在currentViewStates拼写笔误应统一使用currentViewState。按视图过滤图层layerFilter 的三种用法默认情况下所有可见图层都会渲染进所有视图。若某些图层只应出现在特定视图中可借助Deck的layerFilterprop。场景一第一人称视图隐藏遮挡模型以下示例渲染两个视图以跟随地图上行驶的汽车一个是驾驶舱的第一人称视角另一个是所在街区的俯视视角。3D 汽车模型不应出现在第一人称视图中会遮挡相机因此被过滤掉import {Deck, FirstPersonView, MapView} from deck.gl/core; import {SimpleMeshLayer} from deck.gl/mesh-layers; import {MVTLayer} from deck.gl/geo-layers; const deckInstance new Deck({ views: [ new FirstPersonView({id: first-person}), new MapView({id: minimap, x: 10, y: 10, width: 20%, height: 20%}) ], layerFilter: ({layer, viewport}) { if (viewport.id first-person layer.id car) { // 不在第一人称视图中绘制汽车图层 return false; } return true; } }); /** 周期性调用以更新地图到汽车最新位置 */ function updateCar(carPose) { deckInstance.setProps({ layers: [ new MVTLayer({ id: base-map, // ... }), new SimpleMeshLayer({ id: car, mesh: /path/to/model.obj, data: [carPose], getPosition: d [d.longitude, d.latitude, 0], getOrientation: d [0, -d.heading * Math.PI / 180, 0] }) ], viewState: { first-person: { longitude: carPose.longitude, latitude: carPose.latitude, bearing: carPose.heading, position: [0, 0, 2] }, minimap: { longitude: carPose.longitude, latitude: carPose.latitude, zoom: 10 } } }); }注意两个视图的 view state 使用视图 idfirst-person、minimap作为键名分别配置这也印证了上文多视图 多 view state的键值匹配规则。React 版本用useMemo缓存layers与views用useCallback稳定layerFilter: DeckProps[layerFilter]。场景二重图层按视图复制实例TileLayer、MVTLayer、HeatmapLayer、ScreenGridLayer等图层会在视口变化时执行昂贵的操作数据获取与/或聚合因此通常不建议把它们渲染进多个视图。若确需在多个视图中显示例如瓦片底图应为每个视图创建一个图层实例并用layerFilter限制其渲染范围import {Deck, MapView} from deck.gl/core; import {MVTLayer} from deck.gl/geo-layers; const deck new Deck({ // ... views: [ new MapView({id: main, controller: true}), new MapView({id: minimap, x: 10, y: 10, width: 300, height: 200}) ], layers: [ new MVTLayer({ id: tiles-for-main, // ... }), new MVTLayer({ id: tiles-for-minimap, // ... }) ], layerFilter: ({layer, viewport}) { return layer.id tiles-for-${viewport.id}; } });这条layerFilter通过tiles-for-${viewport.id}命名约定把每个 MVT 图层实例精确地绑定到对应 id 的视图。另一个补充事实从 v8.5 起Tile3DLayer支持用单个瓦片缓存渲染进多个视图是上述重图层慎入多视图规则的特例。多视图中的拾取Pickingdeck.gl 内置的拾取能力天然支持多视口拾取过程会渲染所有视口。但要注意pickInfo对象中不包含 viewport 引用因此无法直接判断对象是在哪个视口被拾取的。在按视图过滤图层的例子基础上你可以在layerFilter中按视图控制图层的可拾取性。FilterContext提供了第三个布尔参数isPickingconst layerFilter ({layer, viewport, isPicking}) { if (viewport.id first-person layer.id car) { // 不在第一人称视图中绘制汽车图层 return false; } if (isPicking viewport.id minimap) { // 不在小地图中进行拾取 return false; } return true; };源码层面layerFilter同时作用于渲染与拾取两条链路它被保存在 deck-picker.ts 的DeckPicker实例中并在每次拾取 pass 时随views、viewports一起传入第 834-841 行渲染侧则由 deck-renderer.ts 透传给绘制 pass。因此一份layerFilter即可同时控制画不画与能不能被点中。在多视图后方自动定位 UI 组件deck.gl 项目的一个主要使用场景是构建 WebGL 可视化与 HTML 标记、图表、列表等 UI 组件完美同步的复杂 Web 应用。考虑到此类应用的规模通常期望使用响应式的虚拟 DOM 框架目前这些能力仅在 React 中实现相关示例参见 DeckGL React 组件文档。性能注意事项views/viewState 变更的开销views与viewState对帧率的影响如下两次重渲染之间deck.gl 会对views与viewState做深比较以判断是否发生变化。即使每次渲染都构造新的视图实例只要与上次比较被视为等价就几乎没有性能负担View.equals在 view.ts 中通过构造函数类型 deepEqual(this.props, view.props, 2)完成比较。若views/viewState确实变化则会构造新的 viewport此时发生两件事图层获得机会重算状态并创建额外 GPU 资源——通过shouldUpdateState生命周期方法其UpdateParameters参数带有changeFlags.viewportChanged: true。默认情况下大多数图层会忽略视口变化只要无其他变化就不会触发updateState但部分图层如 TileLayer、HeatmapLayer在视口变化时必须更新交互与过渡期间每秒可能发生多次从而带来显著的性能开销。随后所有图层按新视口重绘。这一步相对廉价因为重活由 GPU 承担CPU 只需把新视口参数交给 GPU。更深入的图层生命周期讨论参见 Layer Lifecycles。结语按需组合先概念后 APIdeck.gl 的视图体系通过View硬配置、ViewState实时状态、Viewport相机本体的三层分离实现了一套数据、多路相机、自由组合的能力。实际选型时地理数据且单视口直接使用默认MapView非地理 2D/3D 数据分别选择OrthographicView/OrbitView需要沉浸感与多视角联动时组合FirstPersonView与MapView并辅以layerFilter控制各视图的渲染与拾取范围。所有视图类与其参数均可直接在 modules/core/src/views 目录下查阅 TypeScript 定义结合本文示例即可快速落地。【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考