当前位置:网站首页>我希望按照我的思路盡可能將canvas基礎講明白

我希望按照我的思路盡可能將canvas基礎講明白

2022-06-25 10:10:00 請打碼

寫在前面

canvas很多人寫過,我之前的博客裏面也寫過關於canvas的教程,但是後面我覺得其實不太好,因為很多東西都是很模糊的,沒有非常直觀清晰的將canvas講解明白,究其原因,還是這個屬性使用的不够多,導致很多屬性不够熟練,但是我希望這篇文章可以將這個屬性徹底的講明白,畢竟只是一個標簽而已,怎麼講都不會太複雜,他之所以不太好學原因就在於他自帶的方法太多,加上很多的效果都是需要方法之間的相互配合使用,所以難度和複雜度就直接昇高了很多,它不像html的其他標簽一樣,比如p、span等都只是自帶了一些樣式罷了,但是沒有自帶那麼多的方法和屬性,但是我們學的時候其實完全可以不用全部搞明白,而且canvas其實也就只有兩個屬性,width和height,他的複雜度來源於它的API,我們其實只需要將他的方法的基本用法進行一個記錄和認識,到我們真實需要這個業務的時候再進行查閱文檔也是可以的,畢竟精力有限,當然如果你的時間很多,你完全可以進行視頻文檔等全部看完,將所有方法都進行一個嘗試,包括一些常見的動畫組合等等都是可以的!博主我也是肝了幾個夜晚,寫了很多例子,也嘗試了很多技巧展示給你們才將這篇文章全部寫完的, 看完點個贊也不過分吧!文章篇幅有點長,慢慢閱讀!

canvas介紹

canvas是H5新特性裏面的一個屬性,說白了,就是一個html的新標簽,和div、p、span都是一樣的,可以直接被瀏覽器解析的html語言,所以我們從心裏上不要排斥它,也不要害怕它,我們就把它做當一個div來學就可以了!(其實這段是我自己安慰自己的),為什麼要有canvas呢?其實在他出現之前,我們的動畫都是逗比(Adobe)公司的Flash技術支持的,他的一個很大的問題就在於比較重,安裝的時候就發現了,安裝的文件其實很大,所以慢慢就被淘汰了,可以說canvas其實是給了Flash一記重拳,落後就要挨打嘛,正常!但是也不是說canvas就完全是好的,他也有一些弊端,本章內容會大概的介紹一下canvas的一些問題和特性!

canvasAPI

APi

怎麼學?

這個問題其實我在沒有學canvas之前,思考了很久,雖然我直到這篇文章完結的時候我都沒有完全掌握canvas的使用,但是我已經不懼怕這個技術點了,因為知道了他是怎麼回事,這種感覺可能很多人都體會過,就是一門技術,你突然覺得他非常的簡單,可能只是某一些效果做起來很複雜,但是不至於沒有任何的思路,只是代碼編寫的時候需要點時間罷了,這裏我說一下我當時怎麼看明白的

  • 學習的第一點:他僅僅只是一個HTML標簽

學習一個新的知識點,搞明白他的本質很重要,所以這一點不是廢話,可能有人看到之後就說,我當然知道他是一個標簽,但是你從心裏沒有接受他是一個標簽,因為它很重,這個重是相對於別的html標簽來說的,正常的標簽就只是一個簡單的字帶樣式的功能塊而已,所以學習的時候本身就不會太繁重!所以我們學習canvas的時候也僅僅把它當做一個簡單的標簽進行學習即可!

  • 學習的第二點:他的繪圖功能和他本身沒有任何關系

這句話不是抬杠,也不是錯的,它本身只是提供了和別的htm標簽一樣的功能,提供了一塊區域而已,至於它强大的繪圖功能,是他的API,和他本身屬性沒有任何關系,有人就說了,那我直接使用div為什麼不行呢?所以說我要你理解我第一句話,也就是我說他只是提供了一塊區域而已,這塊區域就是提供給API使用的,所以這裏不要抬杠,它本身就只有width和height兩個屬性!所以,他的難點也只是在他的API上!我們要學的是他的方法,而不是它本身!那麼使用的過程中我們大部分的場景使用的都是基於2d場景開發,也就是說,不管你是不是很熟悉canvas的使用,開始的都應該明白怎麼寫,

//因為都是基於canvasAPI進行開發的,所以我們首先要將標簽的上下文獲取到,後面具體怎麼實現,是根據實際業務進行的,所以你最多可以說你不會他的API,不可以說你不會canvas
let canvas = document.getElementById('canvas')
let cas = canvas.getContext('2d')
  • 學習的第三點:他的繪圖功能基於他的API

這句話看起來有些廢話,但是我們嘗試理解,就好像我們看到這些:

其實就是將這些或者這裏面的某一些關鍵方法學會即可,他的方法看起來很多,但是其實很多都是一些屬性很簡單的方法,比如beginPath(),fill()等都只是一些方法,甚至沒有任何屬性參數值的!所以一個很關鍵的點在於你有沒有耐心將文檔上面出現的方法屬性進行嘗試,很多東西你只要嘗試了一下,就會非常的確定他的用法,人們對未知的事物才會恐懼,我們知道了他的本質,自然就不會恐懼!

  • 學習的第四點:通過寫簡單的Demo,拼合成複雜的應用的過程

這是一種學習的方法,當一個應用或者動畫被你看起來很複雜的時候,你只需要將它的動畫拆分開,舉個例子,運動的小球是canvas裏比較經典的繪制例子,初次看到的時候我也覺得怎麼怎麼複雜,後來我慢慢的研究了一下他的實現過程,發現其實並不複雜,這是代碼量比較大,拆開看,繪制一個小球、讓他運動、生成隨機數提供給運動軌迹、做一個計時器進行重複繪制和運動、這個看起來複雜的功能應用就實現了,前面的四步哪一步是複雜的?所以下面我會寫幾個Demo,都是非常簡單的,入門級別的canvas的Demo,但是你可以使用這幾個Demo進行拼合成你自己想要的一些看起來比較複雜的應用!下面demo將canvas中使用頻率比較高的幾個API進行了演示,可以直接運行到你的本地html文件中進行查看效果!

  • 學習的第五點:參數的特殊說明

我在第二點的時候說我們大部分的場景都是基於2d上下文進行開發的,但是不代錶他只有2d這一個參數,canvas為我們提供了’webgl’或’experimental-webgl’、webgl2、bitmaprenderer,本篇文章不會對這三個參數做詳細的介紹,這雖然是四個參數,但是前面兩個是同一個,只是第二個是IE針對第一個參數的做的兼容,感興趣的可以自己去看詳細的關於這三個參數的介紹!參數介紹

canvas需要明確的特性

  • canvas兼容性不太好,需要做瀏覽器的兼容性處理

    我們都知道canvas是H5的新特性,也就意味著,不支持H5的瀏覽器版本自然也就不支持canvas,所以我們需要在寫canvas的時候,寫上一句兼容性的提示語句,比如如下代碼:

     <canvas>
         該瀏覽器版本不支持canvas,請昇級瀏覽器或者使用Chrome瀏覽器打開
     </canvas>
    
  • canvas不具備將畫布內容重新獲取的能力

canvas不具備將畫布內容重新獲取的能力,解釋一下這句話,我們在畫布上繪制了一個圖形之後,想要獲取到這個圖形,是不可以的,canvas不具備獲取該圖形的能力,那麼canvas是怎麼實現動畫的呢?他的一個實現思路比較暴力,通過不停的清屏-》重繪的操作進行動畫的實現!說白了就是不停的將之前已經畫上去的圖形删除,重新再繪制一次,只是下一次和上一次的比特置不一樣,連續不停的清除顯示的過程就是動畫的過程,每一個靜態圖形都是一幀,寫個demo,小試一下,後面會詳細的說明繪制的過程!該demo只是為了演示canvas的動畫是怎麼生成的。這裏說一下,逗比公司的Flash技術是可以進行獲取到動畫本身的,這也是為什麼他比較重的一個原因!

<!-- * @use: 直接瀏覽器運行即可 * @description: 畫布基本動畫展示 * @SpecialInstructions: 下方的Demo中出現的cas均為該Html中的canvas * @Author: clearlove * @Date: 2022-05-28 02:26:50 * @LastEditTime: 2022-05-28 02:39:51 * @FilePath: /universal-background-template/Users/leimingwei/Desktop/開源代碼/canvas.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style> canvas {
       border: 1px solid rebeccapurple; } </style>
</head>
<body>
    <canvas id="cas" width="600" height="600">
     該瀏覽器版本不支持Canvas,請昇級瀏覽器或者使用Chrome瀏覽器打開
    </canvas>
</body>
<script> // 下面的例子只是演示說明js部分應該如何寫,實際開發中不建議這樣寫,盡可能的將每一個需要的功能進行封裝 let canvas = document.getElementById('cas') let ctx = canvas.getContext('2d') //設置一個背景顏色 ctx.fillStyle = 'grep' //設置一個單比特量 運動的偏移量 let offsetLeft = 50 //開始繪制動畫 setInterval(() => {
       //清空畫布  clearCanvas(ctx,0, 0, 600, 600) //設置移動偏移量 offsetLeft++ //開始繪制 在X軸運動 繪制一個在Y軸300比特置,50*50的矩形 ctx.fillRect(offsetLeft, 300, 50, 50) }, 10) /** * @function clearCanvas 清空畫布 * @params O {String} canvas內容對象 * @params startX {Number} 清空的X坐標開始比特置 * @params startY {Number} 清空的Y坐標開始比特置 * @params W {Number} 清空的寬度 * @params H {Number} 清空的高度 * */ function clearCanvas(O, startX, startY, W, H) {
       O.clearRect(startX, startY, W, H) } </script>
</html>
  • 運行結果

  • 類的思想進行修改上述代碼 實現一個運動的矩形 Demo-1 fillRect、strokeRect
    let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    console.trace(ctx.canvas.clientWidth)
    //獲取到canvas的寬度
    let casWidth = ctx.canvas.clientWidth
    //聲明一個繪制的類(方法)X:x軸開始比特置、Y:y軸開始比特置、W:圖形寬度、H:圖形高度、C:圖形的背景顏色
    function Drawgraphics(X, Y, W, H, C) {
    
        this.X = X
        this.Y = Y
        this.W = W
        this.H = H
        this.C = C
    }
    //更新X軸比特置
    Drawgraphics.prototype.updateLocation = function () {
    
        this.X++
    }
    //開始繪制畫布
    Drawgraphics.prototype.startFillRect = function () {
    
        ctx.fillStyle = this.C
        ctx.fillRect(this.X, this.Y, this.W, this.H)
    }
    //繪制矩形邊框
    Drawgraphics.prototype.startStroke = function () {
    
        ctx.strokeStyle = this.C
        ctx.strokeRect(this.X, this.Y, this.W, this.H)
    }
    //聲明一個繪制矩形的實例
    let R = new Drawgraphics(50, 50, 50, 50, '#cccccc')
    //聲明一個繪制矩形邊框的實例
    let S = new Drawgraphics(80, 80, 50, 50, '#cccccc')
    //開始繪制動畫
    let stopFlag =  setInterval(() => {
    
        //清空畫布 
        clearCanvas(ctx, 0, 0, 600, 600)
        //設置移動偏移量
        R.updateLocation()
        //開始繪制 在X軸運動 繪制一個在Y軸300比特置,50*50的矩形
        R.startFillRect()
        //設置移動偏移量
        S.updateLocation()
        //繪制一個矩形邊框
        S.startStroke()
        //觸及邊緣停止
        if(S.X >= casWidth - 50){
    
            clearInterval(stopFlag)
        }
    }, 10)
    // 清空畫布 {注釋如上代碼}
    function clearCanvas(O, startX, startY, W, H) {
    
        O.clearRect(startX, startY, W, H)
    }
  • 運行效果

  • 繪制不規則圖形 Demo-2 moveTo、lineTo
    let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    //創建一個路徑
    ctx.beginPath()
    //起點 移動繪制點
    ctx.moveTo(50, 50)
    //第一個點的比特置 描述繪制路徑
    ctx.lineTo(400, 50)
    //第二個點的比特置
    ctx.lineTo(400, 450)
    //第三個點的比特置
    ctx.lineTo(50, 150)
    //關閉圖形 不關閉的話,繪制線條最後是不會封閉的
    ctx.closePath()
    //填充顏色
    ctx.strokeStyle = 'red'
    //繪制線條 描邊
    ctx.stroke()
    //填充 顏色
    ctx.fillStyle = 'orange'
    //填充
    ctx.fill()

  • 繪制一個圓弧 Demo-3 stroke
    let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    ctx.beginPath()
    /** * @params1 圓心的X坐標 * @params2 圓心的Y坐標 * @params3 圓弧的半徑 * @params4 開始的角度 * @params5 結束的角度 * @params6 繪制方向 {true 逆時針繪制 false 順時針繪制} * @desc {params4,params5} 角度的計算方式:Math.PI 一個圓進行分為6.28份(3.14 * 2) 也就是6.28個弧度 0 是水平比特置 Math.PI * 2 是一個圓 此時有一個技巧,如果params5 - params4 > 6.28 並且是順時針 那麼繪制出來的一定是一個圓 * */
    ctx.arc(300, 300, 100, 0, 3.18, false)
    ctx.strokeStyle = 'red'
    ctx.stroke()

  • 實現小球跟隨鼠標 Demo-4 arc
    let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    //清除當前的畫布
    function clearCanvas() {
    
        ctx.clearRect(0, 0, 600, 600)
    }
    //繪制一個小球
    function drawRound(startX, startY, len, startAngle, endAngle, Directtion, C) {
    
        ctx.beginPath()
        ctx.arc(startX, startY, len, startAngle, endAngle, Directtion)
        ctx.strokeStyle = C
        ctx.stroke()
        ctx.fillStyle = C
        ctx.fill()
    }
    //獲取到當前的鼠標坐標 以當前的canvas的左上角作為頂點
    function getMousePosition(event) {
    
        var e = event || window.event;
        console.log(e.offsetX, e.offsetY)
        clearCanvas()
        drawRound(e.offsetX, e.offsetY, 30, 0, Math.PI * 2, false, getRadomColor())
    }
    //生成一個隨機顏色值
    function getRadomColor() {
    
        let types = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f']
        let color = '#'
        for (let i = 0; i < 6; i++) {
    
            let radom = Math.floor(Math.random() * types.length)
            color += types[radom]
        }
        return color
    }
    //當前的畫布添加一個鼠標移動的事件,進行更新小球的比特置
    canvas.addEventListener('mousemove', function (event) {
    
        getMousePosition(event)
    })

  • 在畫布上進行書寫文字 Demo-5 fillText
    let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    //這裏需要注意的事:{如果這裏沒有該字體的話,那麼這裏是不會進行展示的,也就是他的大小也不會生效}
    ctx.font = '48px SimSun, Songti SC' 
    //添加一個透明度 {大部分是用來處理圖片 這裏只是舉個例子進行處理一段文字} 
    ctx.filter = 'opacity(25%)' 
    //400是字體占據的寬度
    ctx.fillText("this is canvas",24,66,400) 
  • 在畫布上繪制圖片 Demo-6 drawImage
    let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    let imgUrl = new Image()
    imgUrl.src = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F4k%2Fs%2F02%2F2109242342133248-0-lp.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1656381685&t=0729cacb65159f78af95f6d9c78bed44'
    console.trace(imgUrl)
    //圖片的繪制 必須在onload之後進行繪制,否則圖片是不會進行渲染的
    imgUrl.onload = function (e) {
    
        drawImg(this);
    };
    // 繪制函數簡單封裝
    function drawImg(img){
    
        /** * @param1 {String:img} 圖片資源地址 * @param2 {Number:0} 相對於圖片的X點比特置 * @param3 {Number:0} 相對於圖片的Y點比特置 * @param4 {Number:400} 切片的切出來的寬度 * @param5 {Number:267} 切片的切出來的高度 * @param6 {Number:100} 相對於畫布的X點比特置 * @param7 {Number:100} 相對於畫布的Y點比特置 * @param8 {Number:140} 切片保存到畫布的寬度 * @param9 {Number:110} 切片保存到畫布的高度 * @desc drawImage繪制的過程中 參數可以是3個 也可以是5個 也可以是9個 但是最少是3個 * @params 3個參數的情况:{當三個參數的時候,說明將圖片直接存放到畫布的某一個比特置- img\x\y} * @params 5個參數的情况:{當五個參數的時候,說明將圖片直接存放到畫布的某一個比特置同時指定圖片的寬高- img\x\y\w\h} */
        ctx.drawImage(img,0,0,400,267,100,100,140,110)
    }
  • 畫布移動和狀態的保存 translate、save、restore
    let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    //使ctx本身右下方移動50
    ctx.translate(50,50)
    ctx.fillStyle = "red" 
    ctx.fillRect(0,0,50,50)
    ctx.fillStyle = "blue"
    ctx.fillRect(50,50,50,50)

-

    let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    //保存當前沒有移動之前的ctx比特置(狀態)
    ctx.save()
    ctx.translate(50,50)
    ctx.fillStyle = "red"
    ctx.fillRect(0,0,50,50)  
    //恢複之前保存下來的ctx的狀態
    ctx.restore()
    ctx.fillStyle = "blue"
    ctx.fillRect(50,50,50,50)

-

  • 說明

save 我想了但是並不知道怎麼描述給你們他的特性,官網是這麼說的,“保存當前Canvas畫布狀態並放在棧的最上面,可以使用restore()方法依次取出”restore 一般是和save配對使用的,目的是將save保存的狀態提取出來,當然save和restore本身的作用不止是這些,官網給的解釋裏面還有一句話的是值得注意的,保存當前Canvas畫布狀態並放在棧的最上面,注意這裏用的是棧,也就是說他符合棧的數據結構特性,也就是先進後出、後進先出,所以這裏我不知道怎麼具體演示,所以給大家畫了一個還相對容易理解的圖下方棧底其實是棧頂,因為不想重複畫圖了,這裏說明一下 save不是所有的屬性都可以保存,他可以保存的屬性有:

查看save可以保存的屬性

在這裏插入圖片描述

  • 畫布屬性的縮放scale

這裏進行特殊說明有兩個原因,第一個是他和css3裏面的scale不同,canvas裏面是兩個參數分別是XY,但是css3裏面只有一個參數,就是整體的縮小,另一個原因在下面Demo裏面

    let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    ctx.fillStyle = "red"
    ctx.fillRect(50, 50, 50, 50)
    ctx.fillStyle = "blue"
    ctx.scale(0.5, 0.5)
    ctx.fillRect(150, 150, 50, 50)

-

  • canvas實現導出
let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    ctx.beginPath()
    ctx.fillStyle = 'red'
    ctx.arc(100, 100, 50, 0, 2 * Math.PI, false)
    ctx.fill()
    //導出base64文件
    // let data = canvas.toDataURL('image/jpeg', 1)
    // console.log(data)
    //導出Blob文件
    let data = canvas.toBlob((v) => {
    
        console.log(v)
    }, 'image/jpeg', 0.7)

自己直接運行一下吧!沒有什麼特殊說明的!

  • 實現一個縮放的圓圈
let canvas = document.getElementById('cas')
    let ctx = canvas.getContext('2d')
    let r = 10
    let stopFlag = setInterval(() => {
    
        r++
        drawArc(r)
        if (r > 50) {
    
            let f = setInterval(() => {
    
                r--
                drawArc(r)
                if (r < 10) {
    
                    clearInterval(f)
                }
            }, 10)
        }
    }, 20)
    //繪制一個圓
    function drawArc(r) {
    
        ctx.beginPath()
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        ctx.strokeStyle = 'red'
        ctx.filter = 'drop-shadow(4px 4px 8px red)'
        ctx.arc(canvas.width / 2, canvas.height / 2, r, 0, 2 * Math.PI, false)
        ctx.stroke()
        ctx.closePath()
    }
  • 實現一個小遊戲
<!-- * @use: 直接運行到html即可 * @description: 產生一個隨機數 進行判斷是否中獎 * @SpecialInstructions: 無 * @Author: clearlove * @Date: 2022-05-28 17:55:04 * @LastEditTime: 2022-05-30 00:04:05 * @FilePath: /universal-background-template/Users/leimingwei/Desktop/開源代碼/canvas/鼠標點擊出現一個炫彩小球.html -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>點擊抽獎</title>
    <style> button {
       margin-bottom: 40px; width: 150px; height: 50px; border-radius: 5px; background-color: burlywood; font-size: 24px; color: #fff; border: none; cursor: pointer; } .result {
       width: 200px; height: 60px; position: relative; user-select: none; } input {
       width: 200px; height: 60px; font-size: 30px; line-height: 60px; text-align: center; position: absolute; user-select: none; border: none; top: 0; left: 0; } canvas {
       position: absolute; top: 0; left: 0; } </style>
</head>

<body>
    <button onclick="beginGame()">開始搖獎</button>
    <div class="result">
        <input disabled id="result" value="中獎了">
        <canvas id="cas" width="200" height="60"></canvas>
    </div>

</body>
<script> function beginGame() {
       //刷新頁面 重置遊戲 location.reload() } // 開始遊戲 function startGame() {
       //將隨機結果存起來 let result = document.getElementById('result') result.value = readomRasult() let canvas = document.getElementById('cas') let ctx = canvas.getContext('2d') // 繪制一個矩形 ctx.fillStyle = '#ccc' ctx.fillRect(0, 0, 200, 60) //將後置產生的元素清除 ctx.globalCompositeOperation = 'destination-out' //添加一個事件,鼠標按下去並滑動 canvas.onmousedown = () => {
       canvas.onmousemove = (event) => {
       ctx.beginPath() ctx.arc(event.offsetX, event.offsetY, 10, 0, 2 * Math.PI, false) ctx.fill() } } } // 產生隨機中獎結果 這裏的條件自己進行更改,提高中獎率 function readomRasult() {
       let res = "" let n = Math.floor(Math.random() * 100) if (n % 3 === 0) {
       res = "中獎了" } else {
       res = "很遺憾" } return res } startGame() </script>
</html>

-

寫到最後

這篇文章有點長,我自己也感覺到了,但是canvas我原本是准備每一個屬性都寫一遍,或者寫一個demo進行演示,不過我想了一下,這個方式並沒有什麼實際意義,因為其實官網給的例子已經很詳細了,所以我想到的方式就是按照我們一些常用的屬性方法進行實現一些比較簡單的demo效果,這樣第一可以練習到canvas的屬性部分,也可以提高我們對canvas的樂趣,上文中的例子很多都是B站出現過我自己寫了一遍的,因為B站上面的例子是比較有代錶性的,希望上面的這些例子可以幫助我們對canvas更加的了解,篇幅很長,看到這裏的相信都是對canvas想學會的,我自己也不是完全對canvas非常的了解,我也是學習的過程中,所以上面的例子或者解釋不保證過完全都是對的,只能說我自己運行的時候效果就是上面的效果,另外就是該文章只是將canvas的基礎用法展示給大家,一些比較複雜的應用,需要大家按照基礎的方法進行組合,希望有不對的地方大家及時指正!

原网站

版权声明
本文为[請打碼]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/176/202206250933310930.html