自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

實(shí)戰(zhàn)三個(gè)有趣案例,十分鐘入門Canvas

開發(fā) 前端
canvas在前端的地位是越來(lái)越重要了,為此,我特地寫了3個(gè)小項(xiàng)目,讓你們10分鐘就能入門canvas。

前言

大家好,我是林三心,回想起我當(dāng)年校招的時(shí)候啊,多次被面試官問(wèn)到canvas,但是我卻不會(huì),后來(lái)一直想找個(gè)機(jī)會(huì)學(xué)一下canvas,但是一直沒時(shí)間。canvas在前端的地位是越來(lái)越重要了,為此,我特地寫了3個(gè)小項(xiàng)目,讓你們10分鐘就能入門canvas,是的,我的心里沒有她,只有你們

一、canvas實(shí)現(xiàn)時(shí)鐘轉(zhuǎn)動(dòng)

實(shí)現(xiàn)以下效果,分為幾步:

1、找到canvas的中心,畫出表心,以及表框

2、獲取當(dāng)前時(shí)間,并根據(jù)時(shí)間畫出時(shí)針,分針,秒針,還有刻度

3、使用定時(shí)器,每過(guò)一秒獲取新的時(shí)間,并重新繪圖,達(dá)到時(shí)鐘轉(zhuǎn)動(dòng)的效果

圖片圖片

1.1 表心,表框

畫表心,表框有兩個(gè)知識(shí)點(diǎn):

1、找到canvas的中心位置

2、繪制圓形

//html

<canvas id="canvas" width="600" height="600"></canvas>

// js

// 設(shè)置中心點(diǎn),此時(shí)300,300變成了坐標(biāo)的0,0
ctx.translate(300, 300)
// 畫圓線使用arc(中心點(diǎn)X,中心點(diǎn)Y,半徑,起始角度,結(jié)束角度)
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.arc(0, 0, 5, 0, 2 * Math.PI)
// 執(zhí)行畫線段的操作stroke
ctx.stroke()

讓我們來(lái)看看效果,發(fā)現(xiàn)了,好像不對(duì)啊,我們是想畫兩個(gè)獨(dú)立的圓線,怎么畫出來(lái)的兩個(gè)圓連到一起了:

圖片圖片

原因是:上面代碼畫連個(gè)圓時(shí),是連著畫的,所以畫完大圓后,線還沒斬?cái)?,就接著畫小圓,那肯定會(huì)大圓小圓連一起,解決辦法就是:beginPath,closePath

ctx.translate(300, 300) // 設(shè)置中心點(diǎn),此時(shí)300,300變成了坐標(biāo)的0,0

// 畫大圓
+ ctx.beginPath()
// 畫圓線使用arc(中心點(diǎn)X,中心點(diǎn)Y,半徑,起始角度,結(jié)束角度)
  ctx.arc(0, 0, 100, 0, 2 * Math.PI)
  ctx.stroke() // 執(zhí)行畫線段的操作
+ ctx.closePath()

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

1.2 時(shí)針,分針,秒針

畫這三個(gè)指針,有兩個(gè)知識(shí)點(diǎn):

1、根據(jù)當(dāng)前時(shí),分,秒去計(jì)算角度

2、在計(jì)算好的角度上去畫出時(shí)針,分針,秒針 如何根據(jù)算好的角度去畫線呢,比如算出當(dāng)前是3點(diǎn),那么時(shí)針就應(yīng)該以12點(diǎn)為起始點(diǎn),順時(shí)針旋轉(zhuǎn)2 * Math.PI / 12 * 3 = 90°,分針和秒針也是同樣的道理,只不過(guò)跟時(shí)針不同的是比例問(wèn)題而已,因?yàn)闀r(shí)在表上有12份,而分針和秒針都是60份

圖片截屏2021-07-19 下午10.07.19.png

這時(shí)候又有一個(gè)新問(wèn)題,還是以上面的例子為例,我算出了90°,那我們?cè)趺串嫵鰰r(shí)針呢?我們可以使用moveTo和lineTo去畫線段。至于90°,我們只需要將x軸順時(shí)針旋轉(zhuǎn)90°,然后再畫出這條線段,那就得到了指定角度的指針了。但是上面說(shuō)了,是要以12點(diǎn)為起始點(diǎn),我們的默認(rèn)x軸確是水平的,所以我們時(shí)分秒針?biāo)愠鼋嵌群?,每次都要減去90°??赡苓@有點(diǎn)繞,我們通過(guò)下面的圖演示一下,還是以上面3點(diǎn)的例子:

圖片圖片

截屏2021-07-19 下午10.30.23.png

圖片圖片

這樣就得出了3點(diǎn)指針的畫線角度了。

又又又有新問(wèn)題了,比如現(xiàn)在我畫完了時(shí)針,然后我想畫分針,x軸已經(jīng)在我畫時(shí)針的時(shí)候偏轉(zhuǎn)了,這時(shí)候肯定要讓x軸恢復(fù)到原來(lái)的模樣,我們才能繼續(xù)畫分針,否則畫出來(lái)的分針是不準(zhǔn)的。這時(shí)候save和restore就派上用場(chǎng)了,save是把ctx當(dāng)前的狀態(tài)打包壓入棧中,restore是取出棧頂?shù)臓顟B(tài)并賦值給ctx,save可多次,但是restore取狀態(tài)的次數(shù)必須等于save次數(shù)

圖片圖片

截屏2021-07-19 下午10.42.06.png

懂得了上面所說(shuō),剩下畫刻度了,起始刻度的道理跟時(shí)分秒針道理一樣,只不過(guò)刻度是死的,不需要計(jì)算,只需要規(guī)則畫出60個(gè)小刻度,和12個(gè)大刻度就行

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

ctx.translate(300, 300) // 設(shè)置中心點(diǎn),此時(shí)300,300變成了坐標(biāo)的0,0
// 把狀態(tài)保存起來(lái)
+ ctx.save()

// 畫大圓
ctx.beginPath()
// 畫圓線使用arc(中心點(diǎn)X,中心點(diǎn)Y,半徑,起始角度,結(jié)束角度)
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.stroke() // 執(zhí)行畫線段的操作
ctx.closePath()

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

----- 新加代碼  ------

// 獲取當(dāng)前 時(shí),分,秒
let time = new Date()
let hour = time.getHours() % 12
let min = time.getMinutes()
let sec = time.getSeconds()

// 時(shí)針
ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
ctx.beginPath()
// moveTo設(shè)置畫線起點(diǎn)
ctx.moveTo(-10, 0)
// lineTo設(shè)置畫線經(jīng)過(guò)點(diǎn)
ctx.lineTo(40, 0)
// 設(shè)置線寬
ctx.lineWidth = 10
ctx.stroke()
ctx.closePath()
// 恢復(fù)成上一次save的狀態(tài)
ctx.restore()
// 恢復(fù)完再保存一次
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()

// 繪制刻度,也是跟繪制時(shí)分秒針一樣,只不過(guò)刻度是死的
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()

圖片圖片

最后一步就是更新視圖,使時(shí)鐘轉(zhuǎn)動(dòng)起來(lái),第一想到的肯定是定時(shí)器setInterval,但是注意一個(gè)問(wèn)題:每次更新視圖的時(shí)候都要把上一次的畫布清除,再開始畫新的視圖,不然就會(huì)出現(xiàn)千手觀音的景象

圖片圖片

附上最終代碼:

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

setInterval(() => {
    ctx.save()
    ctx.clearRect(0, 0, 600, 600)
    ctx.translate(300, 300) // 設(shè)置中心點(diǎn),此時(shí)300,300變成了坐標(biāo)的0,0
    ctx.save()

    // 畫大圓
    ctx.beginPath()
    // 畫圓線使用arc(中心點(diǎn)X,中心點(diǎn)Y,半徑,起始角度,結(jié)束角度)
    ctx.arc(0, 0, 100, 0, 2 * Math.PI)
    ctx.stroke() // 執(zhí)行畫線段的操作
    ctx.closePath()

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

    // 獲取當(dāng)前 時(shí),分,秒
    let time = new Date()
    let hour = time.getHours() % 12
    let min = time.getMinutes()
    let sec = time.getSeconds()

    // 時(shí)針
    ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
    ctx.beginPath()
    // moveTo設(shè)置畫線起點(diǎn)
    ctx.moveTo(-10, 0)
    // lineTo設(shè)置畫線經(jīng)過(guò)點(diǎn)
    ctx.lineTo(40, 0)
    // 設(shè)置線寬
    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()

    // 繪制刻度,也是跟繪制時(shí)分秒針一樣,只不過(guò)刻度是死的
    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啊:

clock的副本clock的副本

二、canvas實(shí)現(xiàn)刮刮卡

小時(shí)候很多人都買過(guò)充值卡把,懂的都懂啊哈,用指甲刮開這層灰皮,就能看底下的答案了。

圖片圖片

思路是這樣的:

1、底下答案是一個(gè)div,頂部灰皮是一個(gè)canvas,canvas一開始蓋住div

2、鼠標(biāo)事件,點(diǎn)擊時(shí)并移動(dòng)時(shí),鼠標(biāo)經(jīng)過(guò)的路徑都畫圓形開路,并且設(shè)置globalCompositeOperation為destination-out,使鼠標(biāo)經(jīng)過(guò)的路徑都變成透明,一透明,自然就顯示出下方的答案信息。

關(guān)于fill這個(gè)方法,其實(shí)是對(duì)標(biāo)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,終點(diǎn)X,終點(diǎn)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
    // 計(jì)算鼠標(biāo)在canvas里的位置
    const x = e.pageX - canvas.offsetLeft
    const y = e.pageY - canvas.offsetTop
    // 設(shè)置globalCompositeOperation
    ctx.globalCompositeOperation = 'destination-out'
    // 畫圓
    ctx.arc(x, y, 10, 0, 2 * Math.PI)
    // 填充圓形
    ctx.fill()
}
canvas.onmouseup = function () {
    isDraw = false
}

效果如下:

guaguaka.gifguaguaka.gif

三、canvas實(shí)現(xiàn)畫板和保存

框架:使用vue + elementUI

其實(shí)很簡(jiǎn)單,難點(diǎn)有以下幾點(diǎn):

1、鼠標(biāo)拖拽畫正方形和圓形

2、畫完一個(gè)保存畫布,下次再畫的時(shí)候疊加

3、保存圖片

第一點(diǎn),只需要計(jì)算出鼠標(biāo)點(diǎn)擊的點(diǎn)坐標(biāo),以及鼠標(biāo)的當(dāng)前坐標(biāo),就可以計(jì)算了,矩形長(zhǎng)寬計(jì)算:x - beginX, y - beginY,圓形則要利用勾股定理:Math.sqrt((x - beginX) * (x - beginX) + (y - beginY) * (y - beginY))

第二點(diǎn),則要利用canvas的getImageData和putImageData方法

第三點(diǎn),思路是將canvas生成圖片鏈接,并賦值給具有下載功能的a標(biāo)簽,并主動(dòng)點(diǎn)擊a標(biāo)簽進(jìn)行圖片下載

看看效果吧:

圖片圖片

圖片圖片

具體代碼我就不過(guò)多講解了,說(shuō)難也不難,只要前面兩個(gè)項(xiàng)目理解了,這個(gè)項(xiàng)目很容易就懂了:

<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>


責(zé)任編輯:武曉燕 來(lái)源: 前端之神
相關(guān)推薦

2023-10-12 09:31:27

SkyWalking微服務(wù)

2020-12-17 06:48:21

SQLkafkaMySQL

2022-06-16 07:31:41

Web組件封裝HTML 標(biāo)簽

2012-07-10 01:22:32

PythonPython教程

2023-03-13 07:52:13

2019-04-01 14:59:56

負(fù)載均衡服務(wù)器網(wǎng)絡(luò)

2023-06-07 08:27:10

Docker容器

2021-09-07 09:40:20

Spark大數(shù)據(jù)引擎

2024-06-19 09:58:29

2023-04-12 11:18:51

甘特圖前端

2024-01-29 00:20:00

GolangGo代碼

2015-09-06 09:22:24

框架搭建快速高效app

2023-11-30 10:21:48

虛擬列表虛擬列表工具庫(kù)

2024-05-13 09:28:43

Flink SQL大數(shù)據(jù)

2025-03-18 12:20:00

編程

2023-07-15 18:26:51

LinuxABI

2009-10-09 14:45:29

VB程序

2019-09-16 09:14:51

2024-11-07 16:09:53

2022-08-26 09:01:07

CSSFlex 布局
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)