如何在OpenLayers中按文本框输入值添加动画箭头
问题:在LineString路线上按输入数量逐个添加动画箭头
需求:在文本框输入数字(例如4),点击「添加箭头」按钮后,每隔2秒在路线上添加一个动画箭头,最终添加的箭头数量与输入值一致。
现有实现代码
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Marker Animation</title> <link rel="stylesheet" href="node_modules/ol/ol.css"> <style> .map { width: 100%; height: 400px; } </style> </head> <body> <div id="map" class="map"></div> <label for="speed"> speed: <input id="speed" type="range" min="10" max="999" step="10" value="60"> </label> <button id="start-animation">Start Animation</button> <input type="text" id="textValue"> <button id="btnClick">Add icon</button> <!-- Pointer events polyfill for old browsers --> <script src="https://unpkg.com/elm-pep@1.0.6/dist/elm-pep.js"></script> <script type="module" src="main.js"></script> </body> </html>
JavaScript代码
import Feature from 'ol/Feature'; import LineString from 'ol/geom/LineString'; import Map from 'ol/Map'; import Point from 'ol/geom/Point'; import Polyline from 'ol/format/Polyline'; import VectorSource from 'ol/source/Vector'; import View from 'ol/View'; import OSM from 'ol/source/OSM'; import {Icon, Stroke, Style} from 'ol/style'; import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer'; import {getVectorContext} from 'ol/render'; const center = [-5639523.95, -3501274.52]; const map = new Map({ target: document.getElementById('map'), view: new View({ center: center, zoom: 10, minZoom: 2, maxZoom: 19, }), layers: [ new TileLayer({ source: new OSM(), }), ], }); fetch('data/polyline/route.json').then(function (response) { response.json().then(function (result) { const polyline = result.routes[0].geometry; const route = new Polyline({ factor: 1e6, }).readGeometry(polyline, { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857', }); const routeFeature = new Feature({ type: 'route', geometry: route, }); const startMarker = new Feature({ type: 'icon', geometry: new Point(route.getFirstCoordinate()), }); const endMarker = new Feature({ type: 'icon', geometry: new Point(route.getLastCoordinate()), }); const position = startMarker.getGeometry().clone(); const geoMarker = new Feature({ type: 'geoMarker', geometry: position, }); const styles = { route: new Style({ stroke: new Stroke({ width: 6, color: [237, 212, 0, 0.8], }), }), icon: new Style({ image: new Icon({ anchor: [0.5, 1], src: 'data/icon.png', }), }), geoMarker: new Style({ image: new Icon({ src: 'https://cdn1.iconfinder.com/data/icons/basic-ui-elements-color-round/3/19-32.png', rotation: getAngleAt(route, 0) + Math.PI / 2, }), }), }; const vectorLayer = new VectorLayer({ source: new VectorSource({ features: [routeFeature, geoMarker, startMarker, endMarker], }), style: function (feature) { return styles[feature.get('type')]; }, }); map.addLayer(vectorLayer); const speedInput = document.getElementById('speed'); const startButton = document.getElementById('start-animation'); let animating = false; function getAngleAt(lineString, distance) { const length = lineString.getLength(); const coordinates = lineString.getCoordinates(); for (let i = 1, len = coordinates.length; i <= len; ++i) { if ( new LineString(coordinates.slice(0, i + 1)).getLength() >= length * distance ) { return -Math.atan2( coordinates[i][1] - coordinates[i - 1][1], coordinates[i][0] - coordinates[i - 1][0] ); } } } const lastTimes = []; function moveFeature(event) { const speed = Number(speedInput.value); const time = event.frameState.time; for (let i = 0, ii = lastTimes.length; i < ii; ++i) { let {lastTime, distance, position} = lastTimes[i]; const elapsedTime = time - lastTime; distance = (distance + (speed * elapsedTime) / 1e6) % 2; lastTime = time; lastTimes[i] = {lastTime, distance, position}; const lineDistance = distance > 1 ? 2 - distance : distance; const direction = distance > 1 ? -Math.PI / 2 : Math.PI / 2; const currentCoordinate = route.getCoordinateAt(lineDistance); const angle = getAngleAt(route, lineDistance) + direction; styles.geoMarker.getImage().setRotation(angle); position.setCoordinates(currentCoordinate); const vectorContext = getVectorContext(event); vectorContext.setStyle(styles.geoMarker); vectorContext.drawGeometry(position); } map.render(); } function startAnimation() { lastTimes.push({lastTime: Date.now(), distance: 0, position: startMarker.getGeometry().clone()}); if (!animating) { animating = true; vectorLayer.on('postrender', moveFeature); geoMarker.setGeometry(null); } } startButton.addEventListener('click', startAnimation); // 新增:添加箭头按钮逻辑 const addBtn = document.getElementById('btnClick'); const countInput = document.getElementById('textValue'); addBtn.addEventListener('click', function() { const count = parseInt(countInput.value); if (isNaN(count) || count <= 0) return; let addedCount = 0; const interval = setInterval(() => { if (addedCount >= count) { clearInterval(interval); return; } startAnimation(); addedCount++; }, 2000); }); }); });
关键修改说明
- 独立维护箭头状态:修改
lastTimes数组,每个元素包含独立的position对象,避免多个箭头共用同一位置导致的覆盖问题。 - 定时添加逻辑:为「添加箭头」按钮绑定点击事件,读取输入框数值后,通过
setInterval每隔2秒调用startAnimation添加新箭头,达到指定数量后清除定时器。 - 更新动画绘制:
moveFeature函数遍历所有箭头状态,分别计算每个箭头的位置和角度并绘制,确保每个箭头独立运动。
内容的提问来源于stack exchange,提问作者Sanjay Singh
相关产品推荐
相关产品推荐

