Canvas绘制七巧板,大家一起来玩呀~

654 阅读3分钟
image.png

我正在参加「码上掘金挑战赛」详情请看:码上掘金挑战赛来了!

大家好,我是小七月,今天我为大家带来了七巧板的绘制。没错,我最近正在学习canvas,由于工作上用的比较少,所以对于这块不是很熟悉,现在让我来为你们展示学习成果。

在学习canvas的过程中,我感悟最深的就是还是得学好数学和物理。canvas最重要的一点是确定各个点的坐标,然后再使用canvas进行绘制。我分析出的一个七巧板的顶点的公式表达如下图。

image.png

图片中标注的序号也是我绘制的图形顺序。其中(x,y)表示的是七巧板的左上角在屏幕中的位置,a表示的是七巧板合起来的正方形的边长。在画布中,x轴向右为正,y轴向下为正。(0,0)原点为屏幕左上角。下面我将七巧板中的七个图形的顶点用变量存储起来。绘制七巧板的代码如下:

<template>
  <div class="tangram-wrapper">
    <canvas
      ref="canvas"
      class="canvas-box"
      :width="size.width"
      :height="size.height"
      @mousedown="handleMouseDown"
      @mousemove="handleMouseMove"
      @mouseup="handleMouseUp"
    ></canvas>
  </div>
</template>

<script>
export default {
  name: 'TangramCpn',
  components: {},
  props: {},
  data() {
    return {
      size: {
        width: 500,
        height: 500
      },
      ctx: null,
      tangramConfig: {
        x: 8,
        y: 8,
        a: 240
      },
      shapeList: [],
      dragShape: null,
      downPosition: null
    };
  },
  computed: {
    tangramDots() {
      return this.calcShapeDots();
    }
  },
  mounted() {
    this.ctx = this.$refs.canvas.getContext('2d');
    this.shapeList = this.calcShapeDots();
    this.render();
  },
  methods: {
    render() {
      // console.log('render', this.shapeList[0]);
      this.shapeList.forEach((shape) => {
        this.drawShape(shape);
      });
    },
    calcShapeDots() {
      const { x, y, a } = this.tangramConfig;
      let shapeList = [
        {
          dots: [
            { x, y },
            { x: x + a / 2, y },
            { x, y: y + a / 2 },
            { x, y }
          ],
          color: 'pink',
          tranX: 0,
          transY: 0
        },
        {
          dots: [
            { x: x + a / 2, y },
            { x: x + a, y },
            { x: x + (3 * a) / 4, y: y + a / 4 },
            { x: x + a / 2, y }
          ],
          color: 'skyblue',
          tranX: 0,
          transY: 0
        },
        {
          dots: [
            { x: x + (3 * a) / 4, y: y + a / 4 },
            { x: x + a / 2, y: y + a / 2 },
            { x: x + a / 4, y: y + a / 4 },
            { x: x + a / 2, y },
            { x: x + (3 * a) / 4, y: y + a / 4 }
          ],
          color: 'orange',
          tranX: 0,
          transY: 0
        },
        {
          dots: [
            { x: x + a / 4, y: y + a / 4 },
            { x: x + a / 2, y: y + a / 2 },
            { x: x + a / 4, y: y + (3 * a) / 4 },
            { x: x + a / 4, y: y + a / 4 }
          ],
          color: 'purple',
          tranX: 0,
          transY: 0
        },
        {
          dots: [
            { x: x + a / 4, y: y + (3 * a) / 4 },
            { x, y: y + a },
            { x, y: y + a / 2 },
            { x: x + a / 4, y: y + a / 4 },
            { x: x + a / 4, y: y + (3 * a) / 4 }
          ],
          color: 'red',
          tranX: 0,
          transY: 0
        },
        {
          dots: [
            { x: x, y: y + a },
            { x: x + a / 2, y: y + a / 2 },
            { x: x + a, y: y + a },
            { x: x, y: y + a }
          ],
          color: 'tomato',
          tranX: 0,
          transY: 0
        },
        {
          dots: [
            { x: x + a, y: y + a },
            { x: x + a / 2, y: y + a / 2 },
            { x: x + a, y },
            { x: x + a, y: y + a }
          ],
          color: 'green',
          tranX: 0,
          transY: 0
        }
      ];
      return shapeList;
    },
  }
};
</script>
<style lang="less" scoped>
.canvas-box {
  border: solid 1px #999;
  border-radius: 8px;
}
.tangram-wrapper {
  // position: absolute;
}
</style>

canvas的使用很简单,我们只需要使用一个<canvas></canvas>标签,然后找到这个元素并且获取context对象即可。简化代码如下:

<canvas
      ref="canvas"
      class="canvas-box"
      :width="size.width"
      :height="size.height"
      @mousedown="handleMouseDown"
      @mousemove="handleMouseMove"
      @mouseup="handleMouseUp"
    >
 </canvas>
 
  this.ctx = this.$refs.canvas.getContext('2d');

七巧板其实是有一个个线段组成,所以我们只要确定两端的点,然后画线就好。这边对于canvas的使用我不再详细写,等我学习完之后我将会出一个细致的canvas总结。

 drawShape(shapeConfig) {
      const { dots, color, tranX, transY } = shapeConfig;
      const ctx = this.ctx;
      ctx.save();
      ctx.translate(tranX, transY);
      ctx.beginPath();
      ctx.fillStyle = color;
      dots.forEach((dot) => {
        const { x, y } = dot;
        ctx.lineTo(x, y);
      });
      ctx.fill();
      ctx.stroke();
      ctx.restore();
      ctx.closePath();
    },

七巧板完成绘制后,我们还要需要它能够挪动。所以我们要给canvas注册鼠标移动事件。在这里会有一个问题,我们该如何确定鼠标点击到哪个图形然后移动呢?

可能有很多小伙伴将直接根据鼠标落下的点以及形状包括的区域来判断到底哪个图形被点击。 但是canvas给我们提供了一个好用的方法isPointInPath,传入鼠标落下的点的坐标即可,它将会返回一个boolean值,true表示点击到了图形。但是该方法有一个问题,它只能判断鼠标是否落在最后一个绘制的路径区域内。一个路径是指在beginPath方法和下一个beginPath之间的路径。所以我们应该写一个方法,将七个图形重新绘制一下,看看这个坐标点是否落在该图形上。实现方法如下:

judgeDragShape(clientX, clientY) {
  for (let shape of this.shapeList) {
    this.drawShape(shape);
    let isIn = this.ctx.isPointInPath(clientX, clientY);
    if (isIn) return shape;
    }
},

这样我们就可以很轻松的判断是哪个图形被点击了。最后要实现拖拽功能,我们需要不断的改变图形的位置。canvas为我们提供了translate(x,y)方法来对绘制的图形进行平移,x表示左右,y表示上下。注意设置了translate的距离之后,需要用到save()restore()组合,否则会影响到下一次的绘制。下面是我的拖拽功能实现:

handleMouseDown(e) {
  const { clientX, clientY } = e;
  this.dragShape = this.judgeDragShape(clientX, clientY);
  console.log(clientX, clientY);
  console.log('offset', this.$refs.canvas.offsetLeft, this.$refs.canvas.offsetTop);
  if (this.dragShape) {
    this.downPosition = { clientX, clientY };
  }
  return false;
},
judgeDragShape(clientX, clientY) {
  for (let shape of this.shapeList) {
    this.drawShape(shape);
    let isIn = this.ctx.isPointInPath(clientX, clientY);
    if (isIn) return shape;
  }
},

handleMouseMove(e) {
  if (this.dragShape) {
    const { clientX, clientY } = e;
    this.dragShape.tranX = clientX - this.downPosition.clientX;
    this.dragShape.transY = clientY - this.downPosition.clientY;
    // console.log(this.drawShape.tranX);
    // console.log(this.shapeList[this.shapeList.length - 1], this.dragShape);
    this.ctx.clearRect(0, 0, this.size.width, this.size.height);
    this.render();
  }
},
handleMouseUp(e) {
  const { clientX, clientY } = e;
  console.log(clientX, clientY);
  this.dragShape = null;
  this.downPosition = null;
  console.log('松开');
}

总结:我后来发现计算transX和transY的逻辑有点问题,因为我只是使用鼠标落下点和鼠标松开点来作为它们俩的值。但是当一个图形被移动之后她自己本身已经存在一个偏移值了,需要计算进去,否则图形被拖拽时被偏移。等我去修复完并测试好之后我将会再更改一下这块的代码。欢迎随时指正。