绘制地铁线路和模拟地铁移动

前言

这个只是一个思路小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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值