20221112_220533

vue2+canvas 根据坐标实现小车移动

最近接手了一个大屏项目,负责部分技术支持,其中包括本次小车移动动画。在思考后共有两种方案,第一种是使用position:absolute,加上top,left和transform:rotate()属性控制小车移动,第二种使用canvas中的定位加旋转画布实现。本文主要介绍使用canvas实现。

小车的移动轨迹绘画

demo中使用了两层canvas,小车的轨迹使用lineTo与moveTo绘画较为简单,以下先给出轨迹坐标及小车移动轨迹的函数。

轨迹html

<div class="map">
 <canvas id="canvasBG" width="1920" height="750"></canvas>
</div>

移动坐标

mapCoordinateList: [ { x: 10, y: 10 }, { x: 60, y: 40 }, { x: 645, y: 45, }, { x: 650, y: 50, }, { x: 650, y: 300 }, { x: 300, y: 350 },  { x: 250,  y: 400 }, { x: 250, y: 600, },  { x: 300, y: 650 }, { x: 600, y: 650 }, { x: 600, y: 700 }, { x: 10, y: 700 }, ],

轨迹函数

handleCanvasBG() {
  const myContext = document.getElementById('canvasBG')
  const ctx = myContext.getContext('2d')
  ctx.beginPath()
  this.mapCoordinateList.forEach((item, index) => {
    if(index+1 < this.mapCoordinateList.length) {
      ctx.moveTo(item.x, item.y)
      ctx.lineTo(this.mapCoordinateList[index+1].x, this.mapCoordinateList[index+1].y)
    } else {
      ctx.moveTo(item.x, item.y)
      ctx.lineTo(this.mapCoordinateList[0].x, this.mapCoordinateList[0].y)
    }
  })
  ctx.stroke()
}

在这里插入图片描述

小车canvas绘画

在编写小车的移动动画时,主要控制原点及画布,我使用过三种方法进行控制,但是最终选择了一下方法。

第一步:将小车的中心移动至坐标的初始位置(画布translate),保证在下一个拐点是以下一个坐标为初始位置。(使用js创建)

// 创建小车画布
handleCreateCanvas() {
  this.moveLen = this.mapCoordinateList.length
  const map = document.querySelector(".map")
  const myCanvas = document.createElement('canvas')
  myCanvas.className = 'canvas'
  myCanvas.width = 1920
  myCanvas.height = 750
  myCanvas.style.position = 'absolute'
  myCanvas.style.left = 0
  myCanvas.style.top = 0
  myCanvas.style.backgroundColor = 'rgba(0,0,0,0)'
  myCanvas.style.Zindex = 2
  map.appendChild(myCanvas)
  const ctx = myCanvas.getContext('2d')
  ctx.beginPath()
  this.handleMove(ctx, map, myCanvas)
},
// 画布移动原点判断
handleMove(ctx) { // 小车的移动函数
        if(this.mapIndex) {
          const valx = this.mapCoordinateList[this.mapIndex].x - this.mapCoordinateList[this.mapIndex-1].x
          const valy = this.mapCoordinateList[this.mapIndex].y - this.mapCoordinateList[this.mapIndex-1].y
          const len = Math.sqrt(valx*valx+valy*valy)
          ctx.translate(len, 0)
        } else {
          ctx.translate(this.mapCoordinateList[this.mapIndex].x, this.mapCoordinateList[this.mapIndex].y)
        }

第二步:因为小车当前坐标与下一个坐标有角度,所以需要先计算角度并将车头移动至平行角度(画布rotate)。

逻辑想法:画布被旋转了,没有做恢复设置,如果恢复原始角度将会出现闪屏的现象,因此当前计算的与x轴角度需要减去上一次旋转的角度。

	const valx = this.mapCoordinateList[this.mapIndex+1].x - this.mapCoordinateList[this.mapIndex].x
        const valy = this.mapCoordinateList[this.mapIndex+1].y - this.mapCoordinateList[this.mapIndex].y
        let laugl = this.handleGetGugle(valx, valy) // 计算当前坐标与x轴正方向的角度
        let moveIndex = 0 // 当前移动了多少
        const len = Math.sqrt(valx*valx+valy*valy) // 移动距离计算
        const lauglLen = Math.floor((laugl - this.afterLaugl) * 100) // 需要移动的角度,角度被放大,每次动画执行0.01角度
        const surplus = laugl - this.afterLaugl - lauglLen / 100
        let index = 0
        const lauglTime = setInterval(() => {
          ctx.clearRect(-11, -6, 1920, 750)
          if(index < Math.abs(lauglLen)) {
            ctx.strokeRect( - 10, -5, 20, 10) // 是一个矩形仿小车
            if(lauglLen > 0) {
              ctx.rotate(0.01)
            } else {
              ctx.rotate(-0.01)
            }
            index++ //当前旋转了多少
          } else {
            ctx.rotate(surplus) // 若角度不足0.01将会在小车移动时自动旋转

计算当前坐标与x轴正方的角度

handleGetGugle(valx, valy) {
  let laugl = 0
   if(valx > 0) {
     if(valy >= 0 ) {
       laugl = Math.atan(valy / valx)
     } else {
       laugl = Math.atan(Math.abs(valx / valy)) + 1.5*Math.PI
     }
   } else if(valx < 0) {
     if(valy > 0) {
       laugl = Math.atan(Math.abs(valx / valy)) + 0.5*Math.PI
     } else if(valy < 0) {
       laugl = Math.atan(valy / valx) + Math.PI
     } else {
       laugl = Math.PI
     }
   } else {
     if (valy > 0) {
       laugl = Math.PI*0.5
     } else {
       laugl = Math.PI*1.5
     }
   }
   return laugl
 }

第三步:让小车匀速移动至下一个坐标。

const timer = setInterval(() => {
 ctx.clearRect(-11, -6, 1920, 750)
  ctx.strokeRect(moveIndex - 10, -5, 20, 10)
  ctx.stroke()
  if(moveIndex < len) {
    moveIndex += 1
  } else {
    clearInterval(timer)
    if(this.mapIndex > this.moveLen ) {
      this.mapIndex = 1
      this.mapCoordinateList.splice(0, this.moveLen)
    }
    if(this.mapIndex < this.mapCoordinateList.length - 2){
      this.mapIndex++
      this.handleMove(ctx)
    } else {
      this.mapCoordinateList.push(...this.mapCoordinateList)
      this.mapIndex++
      this.handleMove(ctx)
    }
  }
}, 10)
moveIndex++

第四步:在到达下一个坐标时,需要将原点移动至下一个坐标在当前画布的位置,循环执行以上操作。

重新执行handleMove函数。

完整代码奉上

<template>
  <div class="map">
    <canvas id="canvasBG" width="1920" height="750"></canvas>
  </div>
</template>

<script>
  export default {
    name: '',
    created() {
    },
    mounted() {
      this.handleCanvasBG()
      this.handleCreateCanvas()
    },
    data() {
      return {
        mapCoordinateList: [ { x: 10, y: 10 }, { x: 60, y: 40 }, { x: 645, y: 45, }, { x: 650, y: 50, }, { x: 650, y: 300 }, { x: 300, y: 350 },  { x: 250,  y: 400 }, { x: 250, y: 600, },  { x: 300, y: 650 }, { x: 600, y: 650 }, { x: 600, y: 700 }, { x: 10, y: 700 }, ],
        mapIndex: 0,
        afterLaugl: 0,
        moveLen: 0,
      }
    },
    methods: {
      handleCreateCanvas() {
        this.moveLen = this.mapCoordinateList.length
        const map = document.querySelector(".map")
        const myCanvas = document.createElement('canvas')
        myCanvas.className = 'canvas'
        myCanvas.width = 1920
        myCanvas.height = 750
        myCanvas.style.position = 'absolute'
        myCanvas.style.left = 0
        myCanvas.style.top = 0
        myCanvas.style.backgroundColor = 'rgba(0,0,0,0)'
        myCanvas.style.Zindex = 2
        map.appendChild(myCanvas)
        const ctx = myCanvas.getContext('2d')
        ctx.beginPath()
        this.handleMove(ctx, map, myCanvas)
      },
      handleMove(ctx) {
        if(this.mapIndex) {
          const valx = this.mapCoordinateList[this.mapIndex].x - this.mapCoordinateList[this.mapIndex-1].x
          const valy = this.mapCoordinateList[this.mapIndex].y - this.mapCoordinateList[this.mapIndex-1].y
          const len = Math.sqrt(valx*valx+valy*valy)
          ctx.translate(len, 0)
        } else {
          ctx.translate(this.mapCoordinateList[this.mapIndex].x, this.mapCoordinateList[this.mapIndex].y)
        }
        const valx = this.mapCoordinateList[this.mapIndex+1].x - this.mapCoordinateList[this.mapIndex].x
        const valy = this.mapCoordinateList[this.mapIndex+1].y - this.mapCoordinateList[this.mapIndex].y
        let laugl = this.handleGetGugle(valx, valy)
        let moveIndex = 0
        const len = Math.sqrt(valx*valx+valy*valy)
        const lauglLen = Math.floor((laugl - this.afterLaugl) * 100)
        const surplus = laugl - this.afterLaugl - lauglLen / 100
        let index = 0
        const lauglTime = setInterval(() => {
          ctx.clearRect(-11, -6, 1920, 750)
          if(index < Math.abs(lauglLen)) {
            ctx.strokeRect( - 10, -5, 20, 10)
            if(lauglLen > 0) {
              ctx.rotate(0.01)
            } else {
              ctx.rotate(-0.01)
            }
            index++
          } else {
            ctx.rotate(surplus)
            clearInterval(lauglTime)
            const timer = setInterval(() => {
              ctx.clearRect(-11, -6, 1920, 750)
              ctx.strokeRect(moveIndex - 10, -5, 20, 10)
              ctx.stroke()
              if(moveIndex < len) {
                moveIndex += 1
              } else {
                clearInterval(timer)
                if(this.mapIndex > this.moveLen ) {
                  this.mapIndex = 1
                  this.mapCoordinateList.splice(0, this.moveLen)
                }
                if(this.mapIndex < this.mapCoordinateList.length - 2){
                  this.mapIndex++
                  this.handleMove(ctx)
                } else {
                  this.mapCoordinateList.push(...this.mapCoordinateList)
                  this.mapIndex++
                  this.handleMove(ctx)
                }
              }
            }, 10)
            moveIndex++
          }
        }, 10)
        this.afterLaugl = laugl
      },
      handleCanvasBG() {
        const myContext = document.getElementById('canvasBG')
        const ctx = myContext.getContext('2d')
        ctx.beginPath()
        this.mapCoordinateList.forEach((item, index) => {
          if(index+1 < this.mapCoordinateList.length) {
            ctx.moveTo(item.x, item.y)
            ctx.lineTo(this.mapCoordinateList[index+1].x, this.mapCoordinateList[index+1].y)
          } else {
            ctx.moveTo(item.x, item.y)
            ctx.lineTo(this.mapCoordinateList[0].x, this.mapCoordinateList[0].y)
          }
        })
        ctx.stroke()
      },
      handleGetGugle(valx, valy) {
        let laugl = 0
        if(valx > 0) {
          if(valy >= 0 ) {
            laugl = Math.atan(valy / valx)
          } else {
            laugl = Math.atan(Math.abs(valx / valy)) + 1.5*Math.PI
          }
        } else if(valx < 0) {
          if(valy > 0) {
            laugl = Math.atan(Math.abs(valx / valy)) + 0.5*Math.PI
          } else if(valy < 0) {
            laugl = Math.atan(valy / valx) + Math.PI
          } else {
            laugl = Math.PI
          }
        } else {
          if (valy > 0) {
            laugl = Math.PI*0.5
          } else {
            laugl = Math.PI*1.5
          }
        }
        return laugl
      }
    }
  }
</script>

<style lang="scss" scoped>
.map{
  position: relative;
  .canvasBG{
    z-index: 1;
  }
}
</style>

有兴趣可以直接拉代码,打开就可以看见。欢迎赐教。

Logo

前往低代码交流专区

更多推荐