美文网首页js css html
canvas入门案例

canvas入门案例

作者: 没名字的某某人 | 来源:发表于2021-07-29 13:52 被阅读0次

    写在最前:文章转自掘金

    1. canvas实现时钟转动

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

    • 找到canvas的中心,画出表心,以及表框
    • 获取当前时间,并根据时间画出时针,分针,秒针,还有刻度
    • 使用定时器,每过一秒获取新的时间,并重新绘图,达到时钟转动的效果


    1.1 表心,表框

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

    • 找到canvas的中心位置
    • 绘制圆形
    // html 
    <canvas id="canvas" width="600" height="600"></canvas>
    
    // js
    const c = document.querySelector("#canvas")
    const ctx = c.getContext('2d')
    ctx.translate(300,300) // 设置中心,此时300,300变成了坐标的0,0
    ctx.beginPath() // 画大圆
    ctx.arc(0,0,100,0,2*Math.PI) // 画圆线使用arc(中心x,中心y,半径,其实角度,结束角度)
    ctx.stroke() // 执行画线段的操作stroke
    ctx.closePath() 
    
    ctx.beginPath() // 画小圆
    ctx.arc(0,0,5,0,2*Math.PI)
    ctx.stroke() // 执行画线段的操作stroke
    ctx.closePath()
    

    1.2 时针,分针,秒针

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

    • 根据当前的时,分,秒去计算角度
    • 在计算好的角度上画出时针,分针,秒针

    例如当前是3点,那么时针就应该以12点为起点,顺时针旋转 2*Math.PI/12*3=90°,分针和秒针也是同样的道理,只不过跟时针不同的是比例问题而已,因为时在表上有12份,而分针和秒针都是60份

    我算出了90°后,那我们怎么画出时针呢?我们可以使用moveTo和lineTo去画线段。至于90°我们只需要将x轴顺时针旋转90°,然后再画出这条线段,那就得到了指定角度的指针了,但是上面说了,是要以12点为起始点,我们的默认轴x轴是水平的所以我们时分秒针算出角度后,每次都要减去90°。下面通过图演示一下。


    当我画完时针,再想画分针时,x轴已经在我画分针的时候偏转了,这时候画出的分针是不准的。save和restore就派上用场了,save是把ctx当前的转台打包压入栈中,restore是取出栈顶部状态并赋值给ctx,sava可多次,但是restore取状态的次数必须等于save次数

    懂得上面所说的,身下画刻度了,起始刻度的道理跟时分秒针道理一样,只不过刻度是死的,不需要计算规则画出60个小刻度,和12个大刻度就行了。

    // 获取当前时分秒
    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()
    ctx.moveTo(-10,0) // 线条起点
    ctx.lineTo(40,0) // 线条终点
    ctx.lineWidth = 3
    ctx.stroke()
    ctx.closePath() 
    ctx.restore() //恢复上一次sava的状态
    ctx.save() //恢复完再保存一次
    
    // 分针
    ctx.rotate(2*Math.PI/60*min-2*Math.PI/2)
    ctx.beginPath()
    ctx.moveTo(-10,0)
    ctx.lineTo(60,0)
    ctx.lineWidth = 2
    ctx.strokeStyle = '#999'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()
    
    // 分针
    ctx.rotate(2*Math.PI/60*sec-2*Math.PI/2)
    ctx.beginPath()
    ctx.moveTo(-10,0)
    ctx.lineTo(80,0)
    ctx.lineWidth = 1
    ctx.strokeStyle = '#dc0000'
    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.stroke()
      ctx.closePath()
    }
    ctx.restore()
    ctx.save()
    ctx.lineWidth = 2
    for(let i = 0;i<12;i++){
      ctx.rotate(2*Math.PI/12)
      ctx.beginPath()
      ctx.moveTo(86,0)
      ctx.lineTo(100,0)
      ctx.stroke()
      ctx.closePath()
    }
    ctx.restore()
    

    最后一步就是更新视图,使时钟转起来,第一想到的肯定是定时器,但注意一个问题,每次更新视图的时候都要把上一次的画布清除,再重新画视图。
    最终代码:

    const c = document.querySelector("#canvas")
      const ctx = c.getContext('2d')
      setInterval(_ => {
        ctx.save() // 保存初始状态
        ctx.clearRect(0,0,600,600) //清空矩阵中的内容
        ctx.translate(300, 300) // 设置中心,此时300,300变成了坐标的0,0
        ctx.save() // 把状态保存起来
    
        ctx.beginPath() // 画大圆
        ctx.arc(0, 0, 100, 0, 2 * Math.PI) // 画圆线使用arc(中心x,中心y,半径,其实角度,结束角度)
        ctx.stroke() // 执行画线段的操作stroke
        ctx.closePath()
    
        ctx.beginPath() // 画小圆
        ctx.arc(0, 0, 5, 0, 2 * Math.PI)
        ctx.stroke() // 执行画线段的操作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()
        ctx.moveTo(-10, 0) // 线条起点
        ctx.lineTo(40, 0) // 线条终点
        ctx.lineWidth = 3
        ctx.stroke()
        ctx.closePath()
        ctx.restore() //恢复上一次sava的状态
        ctx.save() //恢复完再保存一次
    
        // 分针
        ctx.rotate(2 * Math.PI / 60 * min - 2 * Math.PI / 2)
        ctx.beginPath()
        ctx.moveTo(-10, 0)
        ctx.lineTo(60, 0)
        ctx.lineWidth = 2
        ctx.strokeStyle = '#999'
        ctx.stroke()
        ctx.closePath()
        ctx.restore()
        ctx.save()
    
        // 分针
        ctx.rotate(2 * Math.PI / 60 * sec - 2 * Math.PI / 2)
        ctx.beginPath()
        ctx.moveTo(-10, 0)
        ctx.lineTo(80, 0)
        ctx.lineWidth = 1
        ctx.strokeStyle = '#dc0000'
        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.stroke()
          ctx.closePath()
        }
        ctx.restore()
        ctx.save()
        ctx.lineWidth = 2
        for (let i = 0; i < 12; i++) {
          ctx.rotate(2 * Math.PI / 12)
          ctx.beginPath()
          ctx.moveTo(86, 0)
          ctx.lineTo(100, 0)
          ctx.stroke()
          ctx.closePath()
        }
        ctx.restore()
        ctx.restore() // 恢复初始状态
      }, 1000)
    

    2. canvas实现刮刮卡

    思路是这样的:

    • 底下答案是一个div,顶部灰皮是一个canvas,一开始盖住div
    • 鼠标事件,点击并移动时,鼠标经过的路径都画圆形开路,并设置globalCompositeOperationdestination-out,使鼠标经过的路径变成透明,下方答案自然显示。

    关于fill这个方法,其实是对标strokefill是把图形填充,stroke只是画出边框线。

    const c = document.querySelector("#canvas")
      const ctx = c.getContext('2d')
    
      // 设置填充颜色
      ctx.fillStyle = 'darkgray'
      // 填充矩形(起始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.beginPath()
        ctx.arc(x, y, 10, 0, 2 * Math.PI)
        // 填充圆
        ctx.fill()
        ctx.closePath()
      }
      canvas.onmouseup = function () {
        isDraw = false
      }
    

    3. canvas实现画板和保存

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

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

    第一点,只需要计算出鼠标点击的点坐标,以及鼠标的当前坐标,就可以计算了,矩形长宽计算:x - beginX, y - beginY,圆形则要利用勾股定理:Math.sqrt((x - beginX) * (x - beginX) + (y - beginY) * (y - beginY))
    第二点,则要利用canvasgetImageDataputImageData方法
    第三点,思路是将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>
    

    相关文章

      网友评论

        本文标题:canvas入门案例

        本文链接:https://www.haomeiwen.com/subject/njmemltx.html