feat(购票页): 添加动态路线箭头并优化代码格式
新增沿路线循环移动的动态指示箭头,自动适配路线总长度计算动画参数;修复文件开头多余的零宽空格,统一renderRouteOverlay函数内的代码缩进。
This commit is contained in:
+68
-32
@@ -1,4 +1,4 @@
|
|||||||
(() => {
|
(() => {
|
||||||
const $ = (sel) => document.querySelector(sel);
|
const $ = (sel) => document.querySelector(sel);
|
||||||
const fromEl = $('#from');
|
const fromEl = $('#from');
|
||||||
const toEl = $('#to');
|
const toEl = $('#to');
|
||||||
@@ -438,46 +438,82 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderRouteOverlay() {
|
function renderRouteOverlay() {
|
||||||
const svgEl = mapContainer.querySelector('svg');
|
const svgEl = mapContainer.querySelector('svg');
|
||||||
if (!svgEl) return;
|
if (!svgEl) return;
|
||||||
|
|
||||||
const existing = svgEl.querySelector('.route-overlay-group');
|
const existing = svgEl.querySelector('.route-overlay-group');
|
||||||
if (existing) existing.remove();
|
if (existing) existing.remove();
|
||||||
|
|
||||||
if (!Array.isArray(currentRoute) || currentRoute.length < 2) return;
|
if (!Array.isArray(currentRoute) || currentRoute.length < 2) return;
|
||||||
|
|
||||||
const points = currentRoute.map(getStationPoint).filter(Boolean);
|
const points = currentRoute.map(getStationPoint).filter(Boolean);
|
||||||
if (points.length < 2) return;
|
if (points.length < 2) return;
|
||||||
|
|
||||||
const ns = 'http://www.w3.org/2000/svg';
|
const ns = 'http://www.w3.org/2000/svg';
|
||||||
const group = document.createElementNS(ns, 'g');
|
const group = document.createElementNS(ns, 'g');
|
||||||
group.setAttribute('class', 'route-overlay-group');
|
group.setAttribute('class', 'route-overlay-group');
|
||||||
|
|
||||||
const pathData = points.map((pt, idx) => `${idx === 0 ? 'M' : 'L'} ${pt.x} ${pt.y}`).join(' ');
|
const pathData = points.map((pt, idx) => `${idx === 0 ? 'M' : 'L'} ${pt.x} ${pt.y}`).join(' ');
|
||||||
|
|
||||||
const glow = document.createElementNS(ns, 'path');
|
const glow = document.createElementNS(ns, 'path');
|
||||||
glow.setAttribute('d', pathData);
|
glow.setAttribute('d', pathData);
|
||||||
glow.setAttribute('fill', 'none');
|
glow.setAttribute('fill', 'none');
|
||||||
glow.setAttribute('stroke', 'rgba(250, 204, 21, 0.38)');
|
glow.setAttribute('stroke', 'rgba(250, 204, 21, 0.38)');
|
||||||
glow.setAttribute('stroke-width', '18');
|
glow.setAttribute('stroke-width', '18');
|
||||||
glow.setAttribute('stroke-linecap', 'round');
|
glow.setAttribute('stroke-linecap', 'round');
|
||||||
glow.setAttribute('stroke-linejoin', 'round');
|
glow.setAttribute('stroke-linejoin', 'round');
|
||||||
|
|
||||||
const main = document.createElementNS(ns, 'path');
|
const main = document.createElementNS(ns, 'path');
|
||||||
main.setAttribute('d', pathData);
|
main.setAttribute('d', pathData);
|
||||||
main.setAttribute('fill', 'none');
|
main.setAttribute('fill', 'none');
|
||||||
main.setAttribute('stroke', '#facc15');
|
main.setAttribute('stroke', '#facc15');
|
||||||
main.setAttribute('stroke-width', '8');
|
main.setAttribute('stroke-width', '8');
|
||||||
main.setAttribute('stroke-linecap', 'round');
|
main.setAttribute('stroke-linecap', 'round');
|
||||||
main.setAttribute('stroke-linejoin', 'round');
|
main.setAttribute('stroke-linejoin', 'round');
|
||||||
|
|
||||||
group.appendChild(glow);
|
group.appendChild(glow);
|
||||||
group.appendChild(main);
|
group.appendChild(main);
|
||||||
|
|
||||||
const firstStation = svgEl.querySelector('.map-station');
|
// Dynamic arrows pointing to the destination
|
||||||
if (firstStation) svgEl.insertBefore(group, firstStation);
|
let totalDist = 0;
|
||||||
else svgEl.appendChild(group);
|
for (let i = 0; i < points.length - 1; i++) {
|
||||||
}
|
const dx = points[i+1].x - points[i].x;
|
||||||
|
const dy = points[i+1].y - points[i].y;
|
||||||
|
totalDist += Math.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
const speed = 100; // pixels per second
|
||||||
|
const duration = Math.max(1, totalDist / speed);
|
||||||
|
const numArrows = Math.max(1, Math.floor(duration / 0.7)); // one arrow approx every 0.7s
|
||||||
|
const interval = duration / numArrows;
|
||||||
|
|
||||||
|
for (let i = 0; i < numArrows; i++) {
|
||||||
|
const arrowG = document.createElementNS(ns, 'g');
|
||||||
|
const arrowPath = document.createElementNS(ns, 'path');
|
||||||
|
// Chevron shape
|
||||||
|
arrowPath.setAttribute('d', 'M -6 -5 L 4 0 L -6 5');
|
||||||
|
arrowPath.setAttribute('fill', 'none');
|
||||||
|
arrowPath.setAttribute('stroke', '#854d0e'); // Dark brown/orange to contrast with yellow
|
||||||
|
arrowPath.setAttribute('stroke-width', '2.5');
|
||||||
|
arrowPath.setAttribute('stroke-linecap', 'round');
|
||||||
|
arrowPath.setAttribute('stroke-linejoin', 'round');
|
||||||
|
|
||||||
|
const animateMotion = document.createElementNS(ns, 'animateMotion');
|
||||||
|
animateMotion.setAttribute('dur', `${duration}s`);
|
||||||
|
animateMotion.setAttribute('repeatCount', 'indefinite');
|
||||||
|
animateMotion.setAttribute('path', pathData);
|
||||||
|
animateMotion.setAttribute('rotate', 'auto');
|
||||||
|
animateMotion.setAttribute('begin', `-${i * interval}s`);
|
||||||
|
|
||||||
|
arrowG.appendChild(arrowPath);
|
||||||
|
arrowG.appendChild(animateMotion);
|
||||||
|
group.appendChild(arrowG);
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstStation = svgEl.querySelector('.map-station');
|
||||||
|
if (firstStation) svgEl.insertBefore(group, firstStation);
|
||||||
|
else svgEl.appendChild(group);
|
||||||
|
}
|
||||||
|
|
||||||
function updateSelectionUI(skipPreview = false) {
|
function updateSelectionUI(skipPreview = false) {
|
||||||
if (!(selection[0] && selection[1])) {
|
if (!(selection[0] && selection[1])) {
|
||||||
|
|||||||
Reference in New Issue
Block a user