1.0.2 • Published 5d ago
road-traffic-viewer
Licence
MIT
Version
1.0.2
Deps
0
Size
126 kB
Vulns
0
Weekly
17
RoadTrafficViewer — 车道车流可视化组件
基于 Canvas 2D 的道路俯视图车流可视化组件,支持 Vue 3 / Vue 2 / 纯 JavaScript 三种使用方式。
仿照大屏车流可视化工具类的道路俯视图渲染逻辑,在此基础上扩展了多车道、路段流量染色、SVG 图片渲染、鼠标拖拽平移、桩号灵活输入、全配置化颜色等能力。
组件介绍
RoadTrafficViewer 是一款专为交通态势大屏设计的 Canvas 2D 道路可视化组件,能够在网页中实时渲染道路俯视图,直观展示多车道车辆的行驶状态和路段拥堵程度。
核心能力
- 多车道渲染:支持上下行独立配置多车道(1-8车道),车道间白色虚线分隔,中央隔离带配合双黄线
- 车辆图片渲染:车辆仅支持图片渲染(SVG / PNG / JPEG / GIF / WebP 等),不再使用 Canvas 默认绘制。车辆图片通过
carSvgUrl全局配置,或每台车单独指定 - 路段流量染色:根据交通状态(畅通/缓行/拥堵/严重拥堵)对道路区间进行颜色渲染
- 灵活桩号系统:支持
K112、k112.500、K112+500、112000等多种输入格式 - 全配置化样式:道路底色、路肩、隔离带、双黄线、车道线、文字颜色等全部可自定义
- 鼠标拖拽平移:当视口小于路线总长时,支持鼠标拖拽平移路线
- 高性能渲染:requestAnimationFrame 单帧调度,轻松支撑 1000+ 台车辆实时渲染
适用场景
- 高速公路/城市道路态势监控大屏
- 交通指挥中心实时可视化系统
- 车路协同仿真演示平台
- 物流运输轨迹监控系统
目录
安装
通过 npm、yarn 或 pnpm 安装组件包:
# 使用 npm 安装
npm install road-traffic-viewer
# 使用 yarn 安装
yarn add road-traffic-viewer
# 使用 pnpm 安装
pnpm add road-traffic-viewer特性概览
| 特性 | 说明 |
|---|---|
| 灵活桩号 | 支持 "K112" / "k112.500" / "K112+500" / 112000 等多种输入格式,整公里显示 K112,非整公里显示 K112+500 |
| 多车道 | 上下行独立配置车道数,车道间白色虚线分隔,车辆 lane 字段定位到具体车道 |
| 全配置化颜色 | 道路底色(rgba)、路肩、隔离带、双黄线、车道线、文字等全部可自定义 |
| 路段流量染色 | 根据 TrafficFlow 数据对道路区间按 0:畅通/1:缓行/2:拥堵/3:严重拥堵 染色,颜色可配置 |
| 图片渲染 | 车辆必须通过 carSvgUrl 提供图片(SVG/PNG/JPEG/GIF/WebP),单台车可独立指定 |
| SVG 图片 | 车辆和道路底图均支持图片 URL / data URI / 原始 SVG 字符串 |
| 鼠标拖拽 | 当视口小于路线总长时,鼠标拖拽平移路线(grab/grabbing 光标) |
| 比例尺 | scale = 视口可见米数,值越小放得越大,车辆移动越明显 |
| 行驶方向 | 上行 (direction=-1) 桩号小→大 车头朝右,下行 (direction=1) 桩号大→小 车头朝左 |
| RAF 节流 | requestAnimationFrame 单帧调度,轻松支撑 1000+ 台车辆 |
| 灵活尺寸 | 宽高支持 px 数值和百分比字符串 |
| 标签可控 | showCarSpeed / showCarId 控制是否显示速度/ID 标签,speed 字段可选 |
| 框架无关 | 核心渲染类可在纯 JS 中直接 new 使用,同步提供 Vue 3 / Vue 2 组件 |
快速开始
Vue 3 使用方式
<template>
<div style="width: 100%; height: 200px;">
<RoadTrafficViewer
ref="viewerRef"
:config="config"
:vehicles="vehicles"
:traffic-flows="trafficFlows"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RoadTrafficViewer } from 'road-traffic-viewer'
import type { RoadTrafficConfig, Vehicle, TrafficFlow, RoadTrafficViewerExposed } from 'road-traffic-viewer'
// 1. 配置
const config: RoadTrafficConfig = {
startPile: 'K112',
endPile: 'K122',
scale: 2000, // 视口显示 2000 米路线
lanesDown: 2, // 下行 2 车道
lanesUp: 2, // 上行 2 车道
segmentIntervalMeters: 1000,
paddingStartMeters: 50,
paddingEndMeters: 50,
carSvgUrl: '/car-icon.svg', // 车辆图片(必传)
}
// 2. 车辆数据(direction: 1=下行, -1=上行)
const vehicles = ref<Vehicle[]>([
{
id: 'V001',
position: 'K115+200', // K115+200 = 115200 米
speed: 80,
direction: 1, // 下行(隔离带下方,自右向左行驶)
carColor: '#00ff88',
lane: 2,
},
{
id: 'V002',
position: 116500, // 直接传米数
speed: 30,
direction: -1, // 上行(隔离带上方,自左向右行驶)
carColor: '#ff4444',
lane: 1,
},
])
// 3. 路段流量数据
const trafficFlows = ref<TrafficFlow[]>([
{ startPile: 'K115', endPile: 'K117', direction: 1, status: 3 }, // 下行严重拥堵
{ startPile: 'K118', endPile: 'K120', direction: -1, status: 1 }, // 上行缓行
])
const viewerRef = ref<RoadTrafficViewerExposed | null>(null)
onMounted(() => {
console.log('核心实例就绪:', viewerRef.value?.core)
})
</script>Vue 2 使用方式
<template>
<div style="width: 100%; height: 200px;">
<RoadTrafficViewerV2
ref="viewer"
:config="config"
:vehicles="vehicles"
:traffic-flows="trafficFlows"
@ready="onReady"
/>
</div>
</template>
<script>
import { RoadTrafficViewerV2 } from 'road-traffic-viewer'
export default {
components: { RoadTrafficViewerV2 },
data() {
return {
config: {
startPile: 'K0',
endPile: 'K20',
scale: 5000,
lanesDown: 2,
lanesUp: 2,
carSvgUrl: '/car-icon.svg',
},
vehicles: [
{ id: 1, position: 'K5', speed: 80, direction: 1 },
{ id: 2, position: 'K15', speed: 60, direction: -1, carColor: '#ffaa00' },
],
trafficFlows: [
{ startPile: 'K8', endPile: 'K12', direction: 1, status: 2 },
],
}
},
methods: {
onReady(core) {
console.log('核心实例就绪:', core)
},
},
}
</script>纯 JS 使用方式
<div id="road-container" style="width: 100%; height: 200px;"></div>
<script type="module">
import { RoadTrafficCore } from 'road-traffic-viewer'
const container = document.getElementById('road-container')
const core = new RoadTrafficCore(container, {
startPile: 'K112',
endPile: 'K122',
scale: 5000,
lanesDown: 2,
lanesUp: 2,
carSvgUrl: '/car-icon.svg',
})
core.render()
core.updateVehicles([
{ id: 'V1', position: 'K115', speed: 80, direction: 1, lane: 1 },
{ id: 'V2', position: 'K118', speed: 45, direction: -1, lane: 2 },
])
core.updateTrafficFlows([
{ startPile: 'K115', endPile: 'K117', direction: 1, status: 3 },
])
window.addEventListener('resize', () => core.render())
</script>桩号系统
输入格式(自由)
| 输入 | 含义 | 解析结果(米) |
|---|---|---|
"K112" |
K112 | 112000 |
"k112.500" |
K112+500 | 112500 |
"K112+500" |
K112+500 | 112500 |
112000 |
纯米数 | 112000 |
"112000" |
数字字符串 | 112000 |
"k0.123" |
K0+123 | 123 |
显示格式
内部统一以米为单位存储和计算,展示时整公里省略 +000:
formatPile(112000) // => "K112"
formatPile(112500) // => "K112+500"
formatPile(123) // => "K0+123"
工具函数
import { parsePile, formatPile, formatPileShort } from 'road-traffic-viewer'
parsePile('K112+500') // => 112500 (米)
formatPile(112000) // => "K112"(整公里省略 +000)
formatPile(112500) // => "K112+500"
formatPileShort(112500) // => "K112"(始终只显示公里数)配置项详解
以下列出
RoadTrafficConfig中全部可配置属性,含类型、默认值和详细注释。
路线与桩号
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
startPile |
number | string |
必填 | 路线起点桩号。支持 "K112" / "K112+500" / 112000 等格式 |
endPile |
number | string |
必填 | 路线终点桩号。格式同 startPile |
paddingStartMeters |
number |
0 |
起点外侧额外空白米数,防止起点桩号标记被遮挡 |
paddingEndMeters |
number |
0 |
终点外侧额外空白米数,防止终点桩号标记被遮挡 |
segmentIntervalMeters |
number |
1000 |
桩号标尺分段间隔(米)。默认 1000 米一段 |
比例尺与尺寸
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
scale |
number |
1000 |
显示比例尺:画布视口内可见的路线米数。值越小看得越细(放大)。若 scale < 路线总长,支持鼠标拖拽平移 |
canvasWidth |
number | string |
"100%" |
画布宽度。支持 px 数值或百分比字符串 |
canvasHeight |
number | string |
自动计算 | 画布高度。不传则根据车道数自动计算。支持 px 数值或百分比字符串 |
比例尺参考(scale = 视口可见米数):
| scale | 视口可见 | 10km 路线能否全览 | 适用场景 |
|---|---|---|---|
500 |
500 米 | 需拖拽 | 放大细览,车辆清晰可见 |
2000 |
2 公里 | 需拖拽 | 中览,适合主视图 |
5000 |
5 公里 | 需拖拽 | 粗览 |
10000 |
10 公里 | 全览 | 总览,刚好显示整条路线 |
20000 |
20 公里 | 全览 | 超粗览 |
方向定义
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
directionDown |
number | string |
1 |
下行(桩号由大变小,隔离带下方)对应的 direction 值。可自定义映射 |
directionUp |
number | string |
-1 |
上行(桩号由小变大,隔离带上方)对应的 direction 值。可自定义映射 |
swapDirections |
boolean |
false |
是否颠倒上下行视觉位置。true 时隔离带上方为下行、下方为上行 |
车道布局
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
lanesUp |
number |
1 |
上行车道数量(隔离带上方,桩号小→大方向)。传 0 则不显示 |
lanesDown |
number |
1 |
下行车道数量(隔离带下方,桩号大→小方向)。传 0 则不显示 |
laneHeight |
number |
28 |
单条车道高度(像素)。影响整个道路区域的总高度 |
laneLineColor |
string |
"#ffffff" |
车道间间隔虚线颜色。默认白色 |
roadPadding |
number |
3 |
路肩高度(像素),车道外侧到道路边缘的间距 |
medianHeight |
number |
6 |
中央隔离带高度(像素),上下行车道之间的分隔带高度 |
车道编号规则:
- 车道编号从 1 开始,1 号车道 = 最靠近中央隔离带的车道
- 编号越大,离中央隔离带越远
- 车辆的
lane字段默认值为 1
┌──────────────────────────────────────┐
│ 路肩 (roadPadding) │
├──────────────────────────────────────┤
│ 上行 车道3 (离隔离带最远) │ lane: 3
│ - - - - - - - - - - - - - - - - - │ ← 虚线 (laneLineColor)
│ 上行 车道2 │ lane: 2
│ - - - - - - - - - - - - - - - - - │ ← 虚线
│ 上行 车道1 (离隔离带最近) │ lane: 1
│ direction=-1 桩号小→大 →→→ │ (车头朝右)
├══════════════════════════════════════┤
│ ═══ 隔离带 (medianHeight) ════════════ │ + 双黄线 (medianLineColor)
├══════════════════════════════════════┤
│ 下行 车道1 (离隔离带最近) │ lane: 1
│ ←←← direction=1 桩号大→小 │ (车头朝左)
│ - - - - - - - - - - - - - - - - - │ ← 虚线
│ 下行 车道2 │ lane: 2
│ - - - - - - - - - - - - - - - - - │ ← 虚线
│ 下行 车道3 (离隔离带最远) │ lane: 3
├──────────────────────────────────────┤
│ 路肩 (roadPadding) │
└──────────────────────────────────────┘
道路颜色
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
roadBg |
string |
"#141a28" |
道路主底色。支持 rgba 透明度,如 "rgba(20,26,40,0.8)" |
roadBgImage |
string |
— | 道路底图 SVG(URL / data URI / 原始 SVG)。传入后替代 roadBg |
roadShoulder |
string |
"#1e2a3e" |
路肩区域颜色 |
roadLine |
string |
"#2a3a55" |
路边线颜色(最外侧边界线) |
roadEdge |
string |
"#3a5070" |
道路边缘刻度/标尺线颜色 |
medianBg |
string |
"#1a2535" |
中央隔离带背景填充色 |
medianLineColor |
string |
"#ffb020" |
中央隔离带双黄线颜色。默认金黄色 |
canvasBg |
string |
"#0a0f1a" |
画布整体背景色(道路区域之外的填充色) |
markerColor |
string |
"#3a5070" |
桩号标记文字和刻度线的颜色 |
laneLabelColor |
string |
"#2a3a55" |
车道标签文字("上行"/"下行")的颜色 |
车辆渲染
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
defaultCarColor |
string |
"#00ff88" |
车辆标签颜色(数据中无 carColor 时使用) |
carLength |
number |
16 |
车辆图标长度(像素) |
carWidth |
number |
12 |
车辆图标宽度(像素) |
carSvgUrl |
string |
必传 | 车辆图片地址。支持所有图片类型(SVG/PNG/JPEG/GIF/WebP),URL / data URI / 原始 SVG 字符串。车辆必须通过此字段或单车 carSvgUrl 提供图片 |
showCarSpeed |
boolean |
true |
是否在车身上方显示速度标签(km/h) |
showCarId |
boolean |
true |
是否在车身下方显示车辆 ID 标签 |
路段流量状态颜色
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
trafficStatusColors |
{ key: number; color: string }[] |
见下方 | 拥堵状态 → 路面颜色映射。key 为状态值,color 为 CSS 颜色 |
默认映射:
[
{ key: 3, color: '#ff4d6a' }, // 严重拥堵 → 红色
{ key: 2, color: '#ff9500' }, // 拥堵 → 橙色
{ key: 1, color: '#00bfff' }, // 缓行 → 青色
{ key: 0, color: '#00ff88' }, // 畅通 → 绿色
]数据格式
车辆数据 Vehicle
interface Vehicle {
/** 车辆唯一 ID */
id: string | number
/** 车辆所在桩号 */
position: number | string
/** 速度 km/h(可选),不传则不显示速度标签 */
speed?: number
/** 行驶方向:1=下行, -1=上行 */
direction: number
/** 车辆颜色(可选) */
carColor?: string
/** 所在车道编号(可选),从 1 开始,默认 1 */
lane?: number
/** 车辆图片地址(必传),优先级高于全局 carSvgUrl。支持所有图片类型 */
carSvgUrl?: string
}路段流量数据 TrafficFlow
interface TrafficFlow {
/** 流量状态起点桩号 */
startPile: number | string
/** 流量状态终点桩号 */
endPile: number | string
/** 方向:1=下行, -1=上行 */
direction: number
/** 拥堵状态:0=畅通 / 1=缓行 / 2=拥堵 / 3=严重拥堵 */
status: number | string
}API 方法
RoadTrafficCore(核心类)
class RoadTrafficCore {
constructor(container: HTMLElement, config: RoadTrafficConfig)
/** 重绘道路(窗口 resize 后调用) */
render(): void
/** 更新车辆数据(RAF 节流) */
updateVehicles(vehicles: Vehicle[]): void
/** 更新路段流量数据 */
updateTrafficFlows(flows: TrafficFlow[]): void
/** 同时更新车辆和流量 */
updateAll(vehicles: Vehicle[], flows: TrafficFlow[]): void
/** 部分更新配置并重绘 */
updateConfig(partial: Partial<RoadTrafficConfig>): void
/** 获取 Canvas 元素 */
getCanvas(): HTMLCanvasElement
/** 获取渲染度量参数 */
getMetrics(): RenderMetrics | null
/** 销毁实例 */
destroy(): void
}RoadTrafficViewer(Vue 3 组件)
| Prop | 类型 | 说明 |
|---|---|---|
config |
RoadTrafficConfig |
渲染配置(必填) |
vehicles |
Vehicle[] |
车辆数据 |
trafficFlows |
TrafficFlow[] |
路段流量数据 |
| 事件 | 参数 | 说明 |
|---|---|---|
ready |
core: RoadTrafficCore |
渲染引擎就绪时触发 |
| 暴露 | 类型 | 说明 |
|---|---|---|
core |
RoadTrafficCore | null |
核心渲染实例 |
containerRef |
HTMLDivElement | null |
容器 DOM 引用 |
RoadTrafficViewerV2(Vue 2 组件)
| Prop | 类型 | 说明 |
|---|---|---|
config |
Object |
渲染配置(必填) |
vehicles |
Array |
车辆数据 |
trafficFlows |
Array |
路段流量数据 |
| 事件 | 参数 | 说明 |
|---|---|---|
ready |
core |
渲染引擎就绪时触发 |
使用案例
案例 1:基础双向单车道
<template>
<div style="width: 100%; height: 180px;">
<RoadTrafficViewer ref="viewerRef" :config="config" :vehicles="vehicles" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RoadTrafficViewer } from 'road-traffic-viewer'
import type { RoadTrafficConfig, Vehicle, RoadTrafficViewerExposed } from 'road-traffic-viewer'
const config: RoadTrafficConfig = {
startPile: 'K100',
endPile: 'K120',
scale: 2000, // 视口显示 2000 米路线
carSvgUrl: '/car-icon.svg',
}
// direction: 1=下行(隔离带下方), -1=上行(隔离带上方)
const vehicles = ref<Vehicle[]>([
{ id: 1, position: 'K105', speed: 80, direction: 1 }, // 下行
{ id: 2, position: 'K108', speed: 60, direction: 1, carColor: '#ffaa00' },
{ id: 3, position: 'K110', speed: 90, direction: -1 }, // 上行
])
const viewerRef = ref<RoadTrafficViewerExposed | null>(null)
onMounted(() => { console.log(viewerRef.value?.core) })
</script>案例 2:双向 3 车道 + 流量染色
<script setup lang="ts">
const config: RoadTrafficConfig = {
startPile: 'K50', endPile: 'K70', scale: 3000,
lanesDown: 3, lanesUp: 3,
segmentIntervalMeters: 500,
paddingStartMeters: 100, paddingEndMeters: 100,
carSvgUrl: '/car-icon.svg',
}
const vehicles = ref<Vehicle[]>([
{ id: 'A1', position: 'K52', speed: 75, direction: 1, lane: 1 },
{ id: 'A2', position: 'K53', speed: 70, direction: 1, lane: 2 },
{ id: 'B1', position: 'K60', speed: 85, direction: -1, lane: 1 },
{ id: 'B2', position: 'K61', speed: 80, direction: -1, lane: 2 },
])
const trafficFlows = ref<TrafficFlow[]>([
{ startPile: 'K55', endPile: 'K58', direction: 1, status: 3 },
{ startPile: 'K60', endPile: 'K63', direction: -1, status: 1 },
])
</script>案例 3:自定义颜色 + SVG 车辆图片
<script setup lang="ts">
const config: RoadTrafficConfig = {
startPile: 'K0', endPile: 'K10', scale: 5000,
// 亮色道路主题 + 透明度
roadBg: 'rgba(232,232,232,0.9)',
roadShoulder: '#cccccc',
medianBg: '#dddddd',
medianLineColor: '#ffaa00',
canvasBg: '#ffffff',
// SVG 车辆图片
carSvgUrl: '<svg viewBox="0 0 1024 1024" ...>...</svg>',
carLength: 24, carWidth: 18,
// 自定义流量颜色
trafficStatusColors: [
{ key: 3, color: '#cc0000' },
{ key: 2, color: '#ee8800' },
{ key: 1, color: '#eebb00' },
{ key: 0, color: '#00aa44' },
],
}
</script>案例 4:纯 JS 环境 + 定时器
<div id="road" style="width: 1200px; height: 200px;"></div>
<script type="module">
import { RoadTrafficCore, parsePile } from 'road-traffic-viewer'
const core = new RoadTrafficCore(document.getElementById('road'), {
startPile: 'K0', endPile: 'K20', scale: 5000,
lanesDown: 2, lanesUp: 2,
carSvgUrl: '/car-icon.svg',
})
core.render()
setInterval(() => {
core.updateVehicles(
Array.from({ length: 200 }, (_, i) => ({
id: i,
position: Math.random() * 20000,
speed: Math.random() * 120,
direction: Math.random() > 0.5 ? 1 : -1,
lane: Math.ceil(Math.random() * 2),
carColor: ['#00ff88', '#ffaa00', '#ff4444'][Math.floor(Math.random() * 3)],
}))
)
}, 1000)
</script>案例 5:单车道 + WebSocket 实时推送
<script setup lang="ts">
const config: RoadTrafficConfig = {
startPile: 'K0', endPile: 'K50', scale: 3000,
lanesDown: 1, lanesUp: 0, // 仅下行单车道
segmentIntervalMeters: 2000,
paddingStartMeters: 80, paddingEndMeters: 80,
carSvgUrl: '/car-icon.svg',
}
const vehicles = ref<Vehicle[]>([])
const trafficFlows = ref<TrafficFlow[]>([])
let ws: WebSocket | null = null
onMounted(() => {
ws = new WebSocket('wss://your-server.com/traffic')
ws.onmessage = (e) => {
const raw = JSON.parse(e.data)
vehicles.value = raw.vehicles || []
if (raw.flows) trafficFlows.value = raw.flows
}
})
onUnmounted(() => { ws?.close() })
</script>案例 6:双向多车道 + WebSocket 实时推送
高速公路场景:上下行各 3 车道,WebSocket 高频推送 500+ 台车辆,含心跳保活和预过滤。
<script setup lang="ts">
const config: RoadTrafficConfig = {
startPile: 'K100', endPile: 'K160', scale: 5000,
lanesDown: 3, lanesUp: 3, laneHeight: 24,
paddingStartMeters: 100, paddingEndMeters: 100,
carSvgUrl: '/car-icon.svg',
}
const vehicles = ref<Vehicle[]>([])
const trafficFlows = ref<TrafficFlow[]>([])
let ws: WebSocket | null = null
let heartbeatTimer: ReturnType<typeof setInterval> | null = null
onMounted(() => {
ws = new WebSocket('wss://highway.example.com/ws')
ws.onopen = () => {
heartbeatTimer = setInterval(() => ws?.send(JSON.stringify({ type: 'ping' })), 30000)
}
ws.onmessage = (e) => {
const msg = JSON.parse(e.data)
switch (msg.type) {
case 'vehicles':
vehicles.value = msg.data.filter((v: Vehicle) => {
const pos = typeof v.position === 'number' ? v.position : parsePile(v.position)
return pos >= 100000 - 200 && pos <= 160000 + 200
})
break
case 'traffic_flow':
trafficFlows.value = msg.data
break
}
}
})
onUnmounted(() => {
if (heartbeatTimer) clearInterval(heartbeatTimer)
ws?.close()
})
</script>案例 7:道路底图 SVG + 车辆 SVG + rgba 透明度
<script setup lang="ts">
const config: RoadTrafficConfig = {
startPile: 'K0', endPile: 'K30', scale: 5000,
lanesDown: 2, lanesUp: 2,
// 道路底图
roadBgImage: '<svg viewBox="0 0 1024 200" xmlns="...">...</svg>',
// 半透明底色兜底(SVG 加载前显示)
roadBg: 'rgba(20, 26, 40, 0.6)',
// SVG 车辆
carSvgUrl: 'https://example.com/car-icon.svg',
carLength: 20, carWidth: 16,
}
</script>案例 8:隐藏标签 + speed 为空
不显示速度和 ID 标签,适合纯图标展示场景。
<script setup lang="ts">
const config: RoadTrafficConfig = {
startPile: 'K0', endPile: 'K50', scale: 5000,
lanesDown: 2, lanesUp: 2,
showCarSpeed: false, // 隐藏速度标签
showCarId: false, // 隐藏 ID 标签
}
const vehicles = ref<Vehicle[]>([
{ id: 1, position: 'K10', direction: 1, carSvgUrl: '<svg ...>...</svg>' },
{ id: 2, position: 'K20', direction: -1, carSvgUrl: '<svg ...>...</svg>' }, // speed 为空,不显示速度标签
])
</script>案例 9:定时器模拟行驶 — 全部配置展示(深色主题)
用 setInterval 模拟两辆车在上下行车道来回行驶,展示全部 30+ 配置项含默认值注释。
<template>
<div style="width: 100%; height: 220px;">
<RoadTrafficViewer ref="viewer" :config="config" :vehicles="vehicles" :traffic-flows="flows" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { RoadTrafficViewer, parsePile } from 'road-traffic-viewer'
import type { RoadTrafficConfig, Vehicle, TrafficFlow } from 'road-traffic-viewer'
const config: RoadTrafficConfig = {
// ---- 路线与桩号 ----
startPile: 'K100', // 起点桩号,K100 = 100000 米
endPile: 'K120', // 终点桩号,K120 = 120000 米
paddingStartMeters: 60, // 起点空白米数,默认 0
paddingEndMeters: 60, // 终点空白米数,默认 0
segmentIntervalMeters: 1000, // 分段间隔(米),默认 1000
// ---- 比例尺与尺寸 ----
scale: 5000, // 视口可见 5000 米,默认 1000
canvasWidth: '100%', // 画布宽度,默认 "100%"
// canvasHeight 不传,自动计算
// ---- 方向定义 ----
directionDown: 1, // 下行方向值,默认 1
directionUp: -1, // 上行方向值,默认 -1
swapDirections: false, // 颠倒上下行视觉位置,默认 false
// ---- 车道布局 ----
lanesDown: 1, // 下行车道数,默认 1
lanesUp: 1, // 上行车道数,默认 1
laneHeight: 28, // 单车道高度(px),默认 28
laneLineColor: '#ffffff', // 车道虚线颜色,默认白色
roadPadding: 3, // 路肩高度(px),默认 3
medianHeight: 6, // 隔离带高度(px),默认 6
// ---- 道路颜色 ----
roadBg: '#141a28', // 道路底色(支持 rgba),默认深蓝灰
roadBgImage: undefined, // 道路底图 SVG(可选)
roadShoulder: '#1e2a3e', // 路肩颜色
roadLine: '#2a3a55', // 路边线颜色
roadEdge: '#3a5070', // 道路边缘颜色
medianBg: '#1a2535', // 隔离带背景色
medianLineColor: '#ffb020', // 双黄线颜色
canvasBg: '#0a0f1a', // 画布背景色
markerColor: '#3a5070', // 桩号文字颜色
laneLabelColor: '#2a3a55', // 车道标签颜色
// ---- 车辆渲染 ----
defaultCarColor: '#00ff88', // 默认车辆标签颜色
carLength: 16, // 车辆长度(px),默认 16
carWidth: 12, // 车辆宽度(px),默认 12
carSvgUrl: '/car-icon.svg', // 车辆图片(必传)
// ---- 标签显示 ----
showCarSpeed: true, // 显示速度标签,默认 true
showCarId: true, // 显示 ID 标签,默认 true
// ---- 流量状态颜色 ----
trafficStatusColors: [
{ key: 3, color: '#ff4d6a' }, // 严重拥堵→红
{ key: 2, color: '#ff9500' }, // 拥堵→橙
{ key: 1, color: '#00bfff' }, // 缓行→青
{ key: 0, color: '#00ff88' }, // 畅通→绿
],
}
// 路段流量
const flows = ref<TrafficFlow[]>([
{ startPile: 'K105', endPile: 'K108', direction: 1, status: 2 },
{ startPile: 'K115', endPile: 'K118', direction: -1, status: 1 },
])
// 两台车
const vehicles = ref<Vehicle[]>([
{ id: 'CAR-DOWN', position: 'K102', speed: 65, direction: 1, lane: 1 },
{ id: 'CAR-UP', position: 'K118', speed: 55, direction: -1, lane: 1 },
])
// 定时模拟移动
const R_MIN = parsePile(config.startPile), R_MAX = parsePile(config.endPile)
let timer: ReturnType<typeof setInterval> | null = null
onMounted(() => {
timer = setInterval(() => {
vehicles.value = vehicles.value.map(v => {
const pos = parsePile(v.position)
const step = v.speed! / 18
let newPos = v.direction === -1 ? pos + step : pos - step
if (newPos >= R_MAX || newPos <= R_MIN) {
return { ...v, position: newPos >= R_MAX ? R_MAX : R_MIN, direction: -v.direction as (1 | -1) }
}
return { ...v, position: newPos }
})
}, 200)
})
onUnmounted(() => { if (timer) clearInterval(timer) })
</script>案例 10:定时器多车 + 亮色主题 — 全部配置展示
双向各 2 车道,4 辆车在不同车道行驶,到达终点后折返+切换车道。
<script setup lang="ts">
const config: RoadTrafficConfig = {
startPile: 'K30', endPile: 'K48', scale: 4000,
paddingStartMeters: 80, paddingEndMeters: 80,
segmentIntervalMeters: 2000,
directionDown: 1, directionUp: -1,
lanesDown: 2, lanesUp: 2,
laneHeight: 24, laneLineColor: '#cccccc',
roadPadding: 4, medianHeight: 8,
// 亮色主题
roadBg: '#e0e0e0', roadShoulder: '#bdbdbd',
roadLine: '#9e9e9e', roadEdge: '#757575',
medianBg: '#d0d0d0', medianLineColor: '#ff8f00',
canvasBg: '#f5f5f5', markerColor: '#616161',
laneLabelColor: '#9e9e9e',
defaultCarColor: '#1976d2',
carLength: 18, carWidth: 14,
carSvgUrl: '/car-icon.svg',
showCarSpeed: true, showCarId: true,
trafficStatusColors: [
{ key: 3, color: '#d32f2f' },
{ key: 2, color: '#f57c00' },
{ key: 1, color: '#fbc02d' },
{ key: 0, color: '#388e3c' },
],
}
const vehicles = ref<Vehicle[]>([
{ id: 'D1', position: 'K31+200', speed: 80, direction: 1, carColor: '#e91e63', lane: 1 },
{ id: 'D2', position: 'K35+800', speed: 50, direction: 1, carColor: '#2196f3', lane: 2 },
{ id: 'U1', position: 'K46+500', speed: 70, direction: -1, carColor: '#4caf50', lane: 1 },
{ id: 'U2', position: 'K40+100', speed: 45, direction: -1, carColor: '#ff9800', lane: 2 },
])
const flows = ref<TrafficFlow[]>([
{ startPile: 'K33', endPile: 'K36', direction: 1, status: 3 },
{ startPile: 'K42', endPile: 'K45', direction: -1, status: 2 },
])
</script>案例 11:Vue 2 使用方式
<template>
<div style="width: 100%; height: 200px;">
<RoadTrafficViewerV2
:config="config"
:vehicles="vehicles"
:traffic-flows="trafficFlows"
@ready="onReady"
/>
</div>
</template>
<script>
import { RoadTrafficViewerV2 } from 'road-traffic-viewer'
export default {
components: { RoadTrafficViewerV2 },
data() {
return {
config: {
startPile: 'K0', endPile: 'K20', scale: 5000,
lanesDown: 2, lanesUp: 2,
carSvgUrl: '/car-icon.svg',
},
vehicles: [
{ id: 1, position: 'K5', speed: 60, direction: 1 },
{ id: 2, position: 'K15', speed: 80, direction: -1 },
],
trafficFlows: [],
}
},
methods: {
onReady(core) {
console.log('核心实例就绪:', core)
// 可保存 core 引用用于后续手动操作
this.core = core
},
},
}
</script>性能说明
节流机制
- 车辆渲染使用
requestAnimationFrame单帧调度 - 高频调用
updateVehicles()时数据缓存,仅在下一帧执行一次渲染 - 一帧内多次调用只触发一次实际绘制
车辆数量建议
| 车辆数 | 性能 | 建议 |
|---|---|---|
| < 100 | 毫无压力 | 无需特殊处理 |
| 100–1000 | 流畅 | 默认模式即可 |
| 1000–3000 | 基本流畅 | 建议预过滤超出视口的车辆 |
| > 3000 | 可能掉帧 | 强烈建议预过滤 |
优化提示
- 大数组使用不可变替换:
vehicles.value = [...newData]而非 push/splice - 预过滤:只保留视口范围内 ± 缓冲的车辆
- 合理设置 scale:scale 越小视口越窄,需渲染的桩号标记越少
文件结构
src/
types.ts — TypeScript 类型定义
pile-utils.ts — 桩号解析/格式化工具
road-traffic-core.ts — 核心 Canvas 渲染类(框架无关,~1400 行)
RoadTrafficViewer.vue — Vue 3 组件封装
RoadTrafficViewerV2.vue — Vue 2 组件封装
index.ts — 统一导出入口
README.md — 本文档