前言
这个只是一个思路小demo,并不能直接使用,由于作者本人没有地铁原尺寸的数据,所以这个只能给各位大佬提供写这个东西的思路,并不能直接使用。
我这里用的技术不是canvas,也不是svg,而是大家熟知的Vue和三件套。html、css、js相对于跟简单,可以更好实现大家想要的效果,canvas和svg大家不一定都熟悉使用。
后期大家可以完善自己的地铁线路图具体效果,我这里样式简单主要是做样式使用。
前端实现地铁线路
下面我将分模块来来说明如何实现这个效果。
绘制点和文字
其实这个是最简单的,只需通过 CSS 和 HTML 来设置他们即可。需要注意的是,文字和点需要根据实际展示的效果来调节一下偏差的问题。
连线
其实这个才是最难的,这里需要用到高中的两点直线公式,来计算距离和角度。这里需要用js来实现,当然也是可以绑定样式类的。 但是老实说我东西都忘记都差不多了。不过没关系,后面我还是解决了。你们不会也没关系,直接看后面的代码就知道如何实现了。
模拟地铁运动
由于我没有数据,我也懒得造了。就用定时器来代替了,然后运动的核心属性就是:
- 运动时间
- 暂停时间
这里我都是写死的,也是为了方便演示。
然后还会需要用到一个动画,它就是 animejs ,使用起来也是非常的简单。
首先就是安装:
yarn add animejs -S
具体使用大家看文档就行,里面解释很详细,而且是有中文版本的。
animejs 的作用就是去实现 地铁的轨迹运行。
完整代码
接下来,就展示以下完整代码:
<template>
<div id="view">
<!-- 线路信息 -->
<div class="line_info" v-for="c1 in list" :key="c1.id">
<div class="wc">
<!-- 线路标题 -->
<div
class="line_title"
:style="{
left: c1.points[0].x + 'px',
top: c1.points[0].y - 30 + 'px',
color: c1.color,
}"
>
{{ c1.name }}
</div>
<!-- 点 -->
<div
class="point"
v-for="c2 in c1.points"
:key="c2.id"
:style="{ left: c2.x + 'px', top: c2.y + 'px' }"
>
<!-- 换乘点 -->
<div class="change_point" v-if="c2.isTransfer"></div>
<!-- 普通点 -->
<div
class="base_point"
:style="{ border: `5px solid ${c1.color}` }"
v-else
></div>
</div>
<!-- 文字信息 -->
<div
class="tip_text"
v-for="c2 in c1.points"
:key="c2.id"
:style="{
left: c2.x + c1.textOffsetX + 'px',
top: c2.y + c1.textOffsetY + 'px',
}"
>
{{ c2.name }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, nextTick } from "vue";
import data from "./assets/json/data.json";
import anime from "animejs/lib/anime.es.js";
const list = ref([]);
// 创建线段 pol1:起点坐标 pol2:终点坐标 color:颜色
const createLine = (pol1, pol2, color) => {
// 计算线的长度
const dx = pol2.x - pol1.x;
const dy = pol2.y - pol1.y;
const length = Math.sqrt(dx * dx + dy * dy);
// 计算角度
const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
// 创建线段
const line = document.createElement("div");
line.style.position = "absolute";
line.style.left = pol1.x + 10 + "px";
line.style.top = pol1.y + 5 + "px";
line.style.width = length + "px";
line.style.height = "10px";
line.style.backgroundColor = color;
line.style.transformOrigin = "left center";
line.style.transform = `rotate(${angle}deg)`;
// 添加线段添加到页面
nextTick(() => {
document.querySelector(".wc").appendChild(line);
});
};
// 创建地铁并移动 xlIndex:线路索引
const createSubway = (xlIndex) => {
nextTick(() => {
const point = document.createElement("div");
point.style.position = "absolute";
point.style.width = "15px";
point.style.height = "15px";
point.style.backgroundColor = "black";
point.style.borderRadius = "50%";
point.style.left = "0px";
point.style.top = "0px";
point.style.zIndex = "10";
point.style.display = "none";
document.querySelectorAll(".wc")[xlIndex].appendChild(point);
let index = 0; // 初始位置index
let stopTime = 2; // 停留时间 单位s
let runTime = 1; // 运行时间 单位s
let state = 0; // 0: 正常停留 1: 运行
setInterval(() => {
if (index <= list.value[xlIndex].points.length - 1) {
if (index > 0) {
point.style.display = "block";
}
anime({
targets: point,
translateX: list.value[xlIndex].points[index].x,
translateY: list.value[xlIndex].points[index].y,
duration: runTime * 1000,
loop: true,
direction: "alternate",
easing: "linear",
loop: false,
});
index++;
} else {
point.style.display = "none";
}
}, runTime * 1000 + stopTime * 1000);
});
};
onMounted(() => {
// 处理数据
const zoom = 1;
list.value = data.list.map((c1) => {
let oldx = 0;
let oldy = 0;
c1.points = c1.points.map((c2, i2) => {
c2.x = c2.x * zoom;
c2.y = c2.y * zoom;
if (i2 != 0) {
createLine({ x: oldx, y: oldy }, { x: c2.x, y: c2.y }, c1.color);
}
oldx = c2.x;
oldy = c2.y;
return c2;
});
return c1;
});
createSubway(0);
createSubway(1);
});
</script>
<style scoped>
#view {
position: relative;
width: 100%;
height: 100vh;
}
.line_info {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 1500px;
height: 932px;
}
.wc {
position: relative;
width: 100%;
height: 100%;
}
.line_title {
position: absolute;
font-weight: bold;
font-size: 18px;
}
.point {
cursor: pointer;
z-index: 9;
}
.change_point {
width: 10px;
height: 10px;
background-color: #fff;
border: 5px solid blue;
border-radius: 50%;
}
.base_point {
width: 10px;
height: 10px;
background-color: #fff;
border-radius: 50%;
}
.tip_text {
font-size: 12px;
}
.point,
.tip_text,
.line {
position: absolute;
}
</style>