关于前端:为了让她10分钟入门canvas我熬夜写了3个小项目和这篇文章

32次阅读

共计 8367 个字符,预计需要花费 21 分钟才能阅读完成。

(https://juejin.cn/post/697868… “https://juejin.cn/post/6978685539985653767”)」

前言

大家好,我是林三心,回想起我当年校招的时候啊,屡次被面试官问到 canvas,然而我却不会,起初始终想找个机会学一下canvas,然而始终没工夫。canvas 在前端的位置是越来越重要了,为此,我顺便写了 3 个小我的项目,让你们 10 分钟就能入门canvas,是的,我的心里没有她,只有你们

1. canvas 实现时钟转动

实现以下成果,分为几步:

  • 1、找到 canvas 的 核心 ,画出 表心 ,以及 表框
  • 2、获取 以后工夫 ,并依据工夫画出 时针,分针,秒针 ,还有 刻度
  • 3、应用定时器,每过一秒 获取新的工夫 ,并从新绘图,达到时钟 转动的成果

1.1 表心,表框

画表心,表框有两个知识点:

  • 1、找到 canvas 的 核心地位
  • 2、绘制 圆形

    //html
    
    <canvas id="canvas" width="600" height="600"></canvas>
    
    // js
    
    // 设置中心点,此时 300,300 变成了坐标的 0,0
    ctx.translate(300, 300)
    // 画圆线应用 arc(中心点 X, 中心点 Y, 半径, 起始角度, 完结角度)
    ctx.arc(0, 0, 100, 0, 2 * Math.PI)
    ctx.arc(0, 0, 5, 0, 2 * Math.PI)
    // 执行画线段的操作 stroke
    ctx.stroke() 

    让咱们来看看成果,发现了,如同不对啊,咱们是想画 两个独立的圆线 ,怎么画进去的两个圆 连到一起了


起因是:下面代码画连个圆时,是连着画的,所以画完大圆后,线还没斩断,就接着画小圆,那必定会大圆小圆连一起,解决办法就是:beginPath,closePath

ctx.translate(300, 300) // 设置中心点,此时 300,300 变成了坐标的 0,0

// 画大圆
+ ctx.beginPath()
// 画圆线应用 arc(中心点 X, 中心点 Y, 半径, 起始角度, 完结角度)
  ctx.arc(0, 0, 100, 0, 2 * Math.PI)
  ctx.stroke() // 执行画线段的操作
+ ctx.closePath()

// 画小圆
+ ctx.beginPath()
  ctx.arc(0, 0, 5, 0, 2 * Math.PI)
  ctx.stroke()
+ ctx.closePath()

1.2 时针,分针,秒针

画这三个指针,有两个知识点:

  • 1、依据以后 时,分,秒 计算角度
  • 2、在计算好的角度下来画出 时针,分针,秒针
    如何依据算好的角度去画线呢,比方算出以后是 3 点,那么时针就应该以12 点 为起始点,顺时针 旋转 2 * Math.PI / 12 * 3 = 90°,分针和秒针也是同样的情理,只不过跟时针不同的是 比例问题 而已,因为 时在表上有 12 份 ,而 分针和秒针都是 60 份

这时候又有一个新问题,还是以下面的例子为例,我算出了 90°,那咱们怎么画出时针呢?咱们能够应用moveTo 和 lineTo 去画线段。至于 90°,咱们只须要将 x 轴 顺时针旋转 90°,而后再画出这条线段,那就失去了指定角度的指针了。然而下面说了,是要以12 点为起始点,咱们的 默认 x 轴确是程度 的,所以咱们时候秒针算出角度后,每次都要 减去 90°。可能这有点绕,咱们通过上面的图演示一下,还是以下面 3 点 的例子:


这样就得出了 3 点指针的画线角度了。

又又又有新问题了,比方当初我画完了时针,而后我想画分针,x 轴曾经在我画时针的时候偏转了,这时候必定要让 x 轴复原到原来的模样,咱们能力持续画分针,否则画进去的分针是不准的。这时候 save 和 restore 就派上用场了,save 是把 ctx 以后的状态打包压入栈中,restore 是取出栈顶的状态并赋值给 ctxsave 可屡次,然而 restore 取状态的次数必须等于 save 次数

懂得了下面所说,剩下画 刻度 了,起始刻度的情理跟时候秒针情理一样,只不过刻度是死的,不须要计算,只须要规定画出 60 个小刻度,和12 个大刻度 就行

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

ctx.translate(300, 300) // 设置中心点,此时 300,300 变成了坐标的 0,0
// 把状态保存起来
+ ctx.save()

// 画大圆
ctx.beginPath()
// 画圆线应用 arc(中心点 X, 中心点 Y, 半径, 起始角度, 完结角度)
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.stroke() // 执行画线段的操作
ctx.closePath()

// 画小圆
ctx.beginPath()
ctx.arc(0, 0, 5, 0, 2 * Math.PI)
ctx.stroke()
ctx.closePath()

----- 新加代码  ------

// 获取以后 时,分,秒
let time = new Date()
let hour = time.getHours() % 12
let min = time.getMinutes()
let sec = time.getSeconds()

// 时针
ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
ctx.beginPath()
// moveTo 设置画线终点
ctx.moveTo(-10, 0)
// lineTo 设置画线通过点
ctx.lineTo(40, 0)
// 设置线宽
ctx.lineWidth = 10
ctx.stroke()
ctx.closePath()
// 复原成上一次 save 的状态
ctx.restore()
// 复原完再保留一次
ctx.save()

// 分针
ctx.rotate(2 * Math.PI / 60 * min + 2 * Math.PI / 60 * (sec / 60) - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10, 0)
ctx.lineTo(60, 0)
ctx.lineWidth = 5
ctx.strokeStyle = 'blue'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()

// 秒针
ctx.rotate(2 * Math.PI / 60 * sec -  - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10, 0)
ctx.lineTo(80, 0)
ctx.strokeStyle = 'red'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()

// 绘制刻度,也是跟绘制时候秒针一样,只不过刻度是死的
ctx.lineWidth = 1
for (let i = 0; i < 60; i++) {ctx.rotate(2 * Math.PI / 60)
    ctx.beginPath()
    ctx.moveTo(90, 0)
    ctx.lineTo(100, 0)
    // ctx.strokeStyle = 'red'
    ctx.stroke()
    ctx.closePath()}
ctx.restore()
ctx.save()
ctx.lineWidth = 5
for (let i = 0; i < 12; i++) {ctx.rotate(2 * Math.PI / 12)
    ctx.beginPath()
    ctx.moveTo(85, 0)
    ctx.lineTo(100, 0)
    ctx.stroke()
    ctx.closePath()}

ctx.restore()

最初一步就是更新视图,使时钟转动起来,第一想到的必定是定时器 setInterval,然而留神一个问题:每次更新视图的时候都要把上一次的画布革除,再开始画新的视图,不然就会呈现 千手观音 的现象

附上最终代码:

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

setInterval(() => {ctx.save()
    ctx.clearRect(0, 0, 600, 600)
    ctx.translate(300, 300) // 设置中心点,此时 300,300 变成了坐标的 0,0
    ctx.save()

    // 画大圆
    ctx.beginPath()
    // 画圆线应用 arc(中心点 X, 中心点 Y, 半径, 起始角度, 完结角度)
    ctx.arc(0, 0, 100, 0, 2 * Math.PI)
    ctx.stroke() // 执行画线段的操作
    ctx.closePath()

    // 画小圆
    ctx.beginPath()
    ctx.arc(0, 0, 5, 0, 2 * Math.PI)
    ctx.stroke()
    ctx.closePath()

    // 获取以后 时,分,秒
    let time = new Date()
    let hour = time.getHours() % 12
    let min = time.getMinutes()
    let sec = time.getSeconds()

    // 时针
    ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
    ctx.beginPath()
    // moveTo 设置画线终点
    ctx.moveTo(-10, 0)
    // lineTo 设置画线通过点
    ctx.lineTo(40, 0)
    // 设置线宽
    ctx.lineWidth = 10
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()

    // 分针
    ctx.rotate(2 * Math.PI / 60 * min + 2 * Math.PI / 60 * (sec / 60) - Math.PI / 2)
    ctx.beginPath()
    ctx.moveTo(-10, 0)
    ctx.lineTo(60, 0)
    ctx.lineWidth = 5
    ctx.strokeStyle = 'blue'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()

    // 秒针
    ctx.rotate(2 * Math.PI / 60 * sec - Math.PI / 2)
    ctx.beginPath()
    ctx.moveTo(-10, 0)
    ctx.lineTo(80, 0)
    ctx.strokeStyle = 'red'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()

    // 绘制刻度,也是跟绘制时候秒针一样,只不过刻度是死的
    ctx.lineWidth = 1
    for (let i = 0; i < 60; i++) {ctx.rotate(2 * Math.PI / 60)
        ctx.beginPath()
        ctx.moveTo(90, 0)
        ctx.lineTo(100, 0)
        // ctx.strokeStyle = 'red'
        ctx.stroke()
        ctx.closePath()}
    ctx.restore()
    ctx.save()
    ctx.lineWidth = 5
    for (let i = 0; i < 12; i++) {ctx.rotate(2 * Math.PI / 12)
        ctx.beginPath()
        ctx.moveTo(85, 0)
        ctx.lineTo(100, 0)
        // ctx.strokeStyle = 'red'
        ctx.stroke()
        ctx.closePath()}

    ctx.restore()
    ctx.restore()}, 1000)

成果 very good啊:

2. canvas 实现刮刮卡

小时候很多人都买过充值卡把,懂的都懂啊哈,用指甲刮开这层灰皮,就能看底下的答案了。

思路是这样的:

  • 1、底下答案是一个 div,顶部灰皮是一个canvascanvas 一开始盖住div
  • 2、鼠标事件,点击时并挪动时,鼠标通过的门路都 画圆形 开路,并且设置 globalCompositeOperationdestination-out,使鼠标通过的门路都 变成通明,一通明,天然就显示出下方的答案信息。

对于 fill 这个办法,其实是对标 stroke 的,fill是把图形填充,stroke只是画出边框线

// html
<canvas id="canvas" width="400" height="100"></canvas>
<div class="text"> 祝贺您取得 100w</div>
<style>
        * {
            margin: 0;
            padding: 0;
        }
        .text {
            position: absolute;
            left: 130px;
            top: 35px;
            z-index: -1;
        }
</style>


// js
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

// 填充的色彩
ctx.fillStyle = 'darkgray'
// 填充矩形 fillRect(起始 X, 起始 Y, 起点 X, 起点 Y)
ctx.fillRect(0, 0, 400, 100)
ctx.fillStyle = '#fff'
// 绘制填充文字
ctx.fillText('刮刮卡', 180, 50)

let isDraw = false
canvas.onmousedown = function () {isDraw = true}
canvas.onmousemove = function (e) {if (!isDraw) return
    // 计算鼠标在 canvas 里的地位
    const x = e.pageX - canvas.offsetLeft
    const y = e.pageY - canvas.offsetTop
    // 设置 globalCompositeOperation
    ctx.globalCompositeOperation = 'destination-out'
    // 画圆
    ctx.arc(x, y, 10, 0, 2 * Math.PI)
    // 填充圆形
    ctx.fill()}
canvas.onmouseup = function () {isDraw = false}

成果如下:

3. canvas 实现画板和保留

框架:应用vue + elementUI

其实很简略,难点有以下几点:

  • 1、鼠标拖拽画正方形和圆形
  • 2、画完一个保留画布,下次再画的时候叠加
  • 3、保留图片

第一点,只须要计算出鼠标点击的点坐标,以及鼠标的以后坐标,就能够计算了,矩形长宽计算:x - beginX, y - beginY,圆形则要利用勾股定理:Math.sqrt((x - beginX) * (x - beginX) + (y - beginY) * (y - beginY))

第二点,则要利用 canvas 的 getImageDataputImageData办法

第三点,思路是将 canvas 生成图片链接,并赋值给具备下载性能的 a 标签,并被动点击 a 标签 进行 图片下载

看看成果吧:

具体代码我就不过多解说了,说难也不难,只有后面两个我的项目了解了,这个我的项目很容易就懂了:

<template>
  <div>
    <div style="margin-bottom: 10px; display: flex; align-items: center">
      <el-button @click="changeType('huabi')" type="primary"> 画笔 </el-button>
      <el-button @click="changeType('rect')" type="success"> 正方形 </el-button>
      <el-button
        @click="changeType('arc')"
        type="warning"
        style="margin-right: 10px"
        > 圆形 </el-button
      >
      <div> 色彩:</div>
      <el-color-picker v-model="color"></el-color-picker>
      <el-button @click="clear"> 清空 </el-button>
      <el-button @click="saveImg"> 保留 </el-button>
    </div>
    <canvas
      id="canvas"
      width="800"
      height="400"
      @mousedown="canvasDown"
      @mousemove="canvasMove"
      @mouseout="canvasUp"
      @mouseup="canvasUp"
    >
    </canvas>
  </div>
</template>

<script>
export default {data() {
    return {
      type: "huabi",
      isDraw: false,
      canvasDom: null,
      ctx: null,
      beginX: 0,
      beginY: 0,
      color: "#000",
      imageData: null,
    };
  },
  mounted() {this.canvasDom = document.getElementById("canvas");
    this.ctx = this.canvasDom.getContext("2d");
  },
  methods: {changeType(type) {this.type = type;},
    canvasDown(e) {
      this.isDraw = true;
      const canvas = this.canvasDom;
      this.beginX = e.pageX - canvas.offsetLeft;
      this.beginY = e.pageY - canvas.offsetTop;
    },
    canvasMove(e) {if (!this.isDraw) return;
      const canvas = this.canvasDom;
      const ctx = this.ctx;
      const x = e.pageX - canvas.offsetLeft;
      const y = e.pageY - canvas.offsetTop;
      this[`${this.type}Fn`](ctx, x, y);
    },
    canvasUp() {this.imageData = this.ctx.getImageData(0, 0, 800, 400);
      this.isDraw = false;
    },
    huabiFn(ctx, x, y) {ctx.beginPath();
      ctx.arc(x, y, 5, 0, 2 * Math.PI);
      ctx.fillStyle = this.color;
      ctx.fill();
      ctx.closePath();},
    rectFn(ctx, x, y) {
      const beginX = this.beginX;
      const beginY = this.beginY;
      ctx.clearRect(0, 0, 800, 400);
      this.imageData && ctx.putImageData(this.imageData, 0, 0, 0, 0, 800, 400);
      ctx.beginPath();
      ctx.strokeStyle = this.color;
      ctx.rect(beginX, beginY, x - beginX, y - beginY);
      ctx.stroke();
      ctx.closePath();},
    arcFn(ctx, x, y) {
      const beginX = this.beginX;
      const beginY = this.beginY;
      this.isDraw && ctx.clearRect(0, 0, 800, 400);
      this.imageData && ctx.putImageData(this.imageData, 0, 0, 0, 0, 800, 400);
      ctx.beginPath();
      ctx.strokeStyle = this.color;
      ctx.arc(
        beginX,
        beginY,
        Math.round(Math.sqrt((x - beginX) * (x - beginX) + (y - beginY) * (y - beginY))
        ),
        0,
        2 * Math.PI
      );
      ctx.stroke();
      ctx.closePath();},
    saveImg() {const url = this.canvasDom.toDataURL();
      const a = document.createElement("a");
      a.download = "sunshine";
      a.href = url;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    },
    clear() {
        this.imageData = null
        this.ctx.clearRect(0, 0, 800, 400)
    }
  },
};
</script>

<style lang="scss" scoped>
#canvas {border: 1px solid black;}
</style>

结语

我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜爱前端,想学习前端,那咱们能够交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】

正文完
 0