开发一个网站的过程是什么,wordpress提供restful,wordpress网址更换,经典的jq查询网站摘要本文演示如何用 Vue 3#xff08;script setup#xff09;和 OpenLayers#xff08;ol#xff09;实现一辆小汽车沿线路行驶并且把走过的轨迹用另一种颜色显示的效果。代码包含#xff1a;地图初始化、轨迹数据、动画控制#xff08;开始 / 暂停 / 结束…摘要本文演示如何用 Vue 3script setup和 OpenLayersol实现一辆小汽车沿线路行驶并且把走过的轨迹用另一种颜色显示的效果。代码包含地图初始化、轨迹数据、动画控制开始 / 暂停 / 结束、速度调整、以及组件卸载清理。适合想把轨迹动态可视化加入到项目中的前端工程师阅读与实践。效果预览文字说明页面上有一条折线表示路line页面上有一辆小汽车图标沿着这条线匀速移动汽车每走过一段就把该段以另一种颜色轨迹颜色绘制出来形成“走过后轨道呈现不同颜色”的视觉效果。同时提供“开始/暂停/结束”按钮和速度滑块能实时控制动画。
准备工作Vue 3 Vite推荐或 Vue CLIElement Plus用于按钮和滑块可替换为任意 UI 组件OpenLayers包名 ol
# 推荐命令基于 Vite TypeScript
npm init vitelatest my-map -- --template vue-ts
cd my-map
npm install
npm install ol element-plus说明示例代码使用 TypeScriptlangts如果你的项目是纯 JS把 langts 改为 langjs 即可并去掉类型注解。组件完整代码可直接复制到一个 .vue 文件
!--* Author: 彭麒* Date: 2025/9/19* Email: 1062470959qq.com* Description: 此源码版权归吉檀迦俐所有可供学习和借鉴或商用。--
templatediv classcontainerdiv classw-full flex justify-center flex-wrapdiv classfont-bold text-[24px]vue3openlayers: 小汽车移动轨迹动画走过后轨道呈现不同颜色/div/divh4el-button typeprimary sizesmall clickstart开始/el-buttonel-button typeinfo sizesmall clickpause暂停/el-buttonel-button typedanger sizesmall clickend结束/el-button/h4div idvue-openlayers/div/div
/templatescript setup langts
import ol/ol.css
import { onMounted, ref } from vue
import { Map, View } from ol
import TileLayer from ol/layer/Tile
import VectorLayer from ol/layer/Vector
import VectorSource from ol/source/Vector
import OSM from ol/source/OSM
import Feature from ol/Feature
import { Point, LineString } from ol/geom
import Style from ol/style/Style
import Fill from ol/style/Fill
import Icon from ol/style/Icon
import Stroke from ol/style/Stroke
import carImg from /assets/OpenLayers/car.png
// -------------------- 状态定义 --------------------
const map refMap | null(null)const dataSource new VectorSource({ wrapX: false })
const passSource new VectorSource({ wrapX: false })const lineData [[116, 39],[116.055, 38.956],[116.105, 39.015],[116.166, 38.948],[116.2, 39.005],
]let lineFeature: FeatureLineString | null null
let pointFeature: FeaturePoint | null null
const step1 ref(0)
let requestID: number | null null// -------------------- 方法 --------------------
const featureStyle () {return new Style({fill: new Fill({ color: #f0f }),stroke: new Stroke({width: 2,color: #f0f,}),})
}const showTrack () {// 绘制轨迹线lineFeature new Feature({geometry: new LineString(lineData),})dataSource.addFeature(lineFeature)// 小汽车图标pointFeature new Feature({geometry: new Point([116, 39]),})pointFeature.setStyle(new Style({image: new Icon({src: carImg,rotateWithView: true,scale: 0.3,}),zIndex: 1000,}))dataSource.addFeature(pointFeature)
}const animation (step: number) {requestID window.requestAnimationFrame(() {if (!lineFeature || !pointFeature) returnif (step 1) {const second lineFeature.getGeometry()!.getCoordinateAt(step)const first pointFeature.getGeometry()!.getCoordinates()const angle -Math.atan2(second[1] - first[1], second[0] - first[0])// 移动小车pointFeature.getGeometry()!.setCoordinates(second)pointFeature.getStyle()?.getImage()?.setRotation(angle)// 绘制走过的轨迹const tempFeature new Feature({geometry: new LineString([first, second]),})passSource.addFeature(tempFeature)step 0.001step1.value stepanimation(step1.value)} else {// 结束点修正const second lineFeature.getGeometry()!.getCoordinateAt(1)const first pointFeature.getGeometry()!.getCoordinates()const angle -Math.atan2(second[1] - first[1], second[0] - first[0])pointFeature.getGeometry()!.setCoordinates(second)pointFeature.getStyle()?.getImage()?.setRotation(angle)const tempFeature new Feature({geometry: new LineString([first, second]),})passSource.addFeature(tempFeature)}})
}// -------------------- 控制按钮 --------------------
const start () {animation(step1.value)
}const pause () {if (requestID) {cancelAnimationFrame(requestID)}
}const end () {step1.value 0passSource.clear() // 清除轨迹if (requestID) {cancelAnimationFrame(requestID)}if (pointFeature lineFeature) {pointFeature.getGeometry()!.setCoordinates(lineFeature.getGeometry()!.getCoordinateAt(step1.value))}
}// -------------------- 地图初始化 --------------------
const initMap () {const mapLayer new TileLayer({source: new OSM(),})const featureLayer new VectorLayer({source: dataSource,style: featureStyle(),})const passLayer new VectorLayer({source: passSource,style: new Style({fill: new Fill({ color: blue }),stroke: new Stroke({width: 2,color: blue,}),}),})map.value new Map({target: vue-openlayers,layers: [mapLayer, featureLayer, passLayer],view: new View({projection: EPSG:4326,center: [116.105, 39],zoom: 12,}),})showTrack()
}// -------------------- 生命周期 --------------------
onMounted(() {initMap()
})
/scriptstyle scoped
.container {width: 840px;height: 570px;margin: 50px auto;border: 1px solid #42b983;
}#vue-openlayers {width: 800px;height: 400px;margin: 0 auto;border: 1px solid #42b983;position: relative;
}
/style说明小车图片请放在 src/assets/OpenLayers/car.png或改为你项目中的图片路径。Element Plus 的 el-slider / el-button 用法请按你项目中 Element Plus 的全局/按需导入方式使用。代码详解逐块说明1. 数据结构与思路lineData一个数组按顺序保存折线的经纬度[lon, lat]。我们把整条线用 LineString(lineData) 表示并把小车用 Point 放在起点。动画通过 requestAnimationFrame 逐帧更新用 lineFeature.getGeometry().getCoordinateAt(fraction) 获取线段上任意 fraction0 ~ 1对应的坐标。把当前点与上一个点组成一个小线段 LineString([prev, curr])加入 passSource并由 passLayer 着色形成“已走过轨迹”效果。2. 关键函数——getCoordinateAtgetCoordinateAt(t) 接受 t0-1作为参数返回对应的坐标在当前几何长度上的相对位置。因此动画只需把 t 从 0 增加到 1。3. 关键函数——角度计算图标朝向
const angle -Math.atan2(second[1] - first[1], second[0] - first[0])
Math.atan2(dy, dx) 返回两点连线的角度弧度。加负号是为了修正 icon 的朝向不同图片方向不同若方向不正确可尝试去掉负号或加/减 PI/2。4. 速度控制speed 控制每帧 t 增量值越大动画越快。我们用 el-slider 提供实时调整。5. 暂停 / 继续 / 结束pause取消当前 requestAnimationFrame保留 step1 值以便继续。start如果没有正在运行的 requestID则从当前 step1 继续动画。end把 step1 清 0、清空 passSource走过轨迹、并把小车回到起点。6. 清理组件卸载时需取消 requestAnimationFrame避免内存泄漏。销毁 mapmap.setTarget(undefined)确保 OpenLayers 释放资源。
常见问题FAQ图标不朝向前进方向尝试调整 angle 的正负号或在角度上加上偏移 Math.PI/2因为不同图标初始朝向不同。瓦片 / 坐标错位示例中 View 使用 EPSG:4326经纬度但常见 OSM 瓦片使用 EPSG:3857墨卡托。如果你发现瓦片与几何不对齐可把 View 改为 EPSG:3857并把 lineData 坐标用 fromLonLat([lon, lat]) 转换后再使用。性能问题大量段如果轨迹段太多短步长会生成大量 feature会影响渲染性能。优化方式合并短段为较长的 polyline 并做样式渐变或用 Canvas/Custom layer 自绘轨迹更灵活高效。小车图标很大/很小调整 Icon.scale 或使用合适分辨率的图片。需要多车并行为每辆车维护独立的 pointFeature、lineFeature 和 passSource并分别启动动画即可或用同一个 requestAnimationFrame 在一帧内更新所有车辆。