整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          HTML5 Canvas的事件處理(無需循環(huán)重繪和事

          HTML5 Canvas的事件處理(無需循環(huán)重繪和事件冒泡)

          Canvas里,所有圖形都繪制在幀上,繪制方法不會(huì)將繪制好的圖形元素作為一個(gè)返回值輸出,js也無法獲取到已經(jīng)繪制好的圖形元素,在Canvas中繪制的圖形都是一個(gè)整體。

          假設(shè)用Canvas繪制了一個(gè)圖形,要判斷一個(gè)事件是否發(fā)生在該圖形上,有個(gè)isPointInPath方法,但是該方法僅判斷當(dāng)前上下文環(huán)境中的路徑,所以當(dāng)Canvas里已經(jīng)繪制了多個(gè)圖形時(shí),僅能以最后一個(gè)圖形的上下文環(huán)境來判斷事件。這種問題的解決方法是:當(dāng)事件發(fā)生時(shí),重繪所有圖形,每繪制一個(gè)就使用isPointInPath方法,判斷事件坐標(biāo)是否在該圖形覆蓋范圍內(nèi)。當(dāng)圖形過多時(shí),為了在性能和視覺效果上達(dá)到更好的效果,我們可以采取一種數(shù)學(xué)方法。

          多個(gè)矩形框

          如圖,紅色部分是由很多個(gè)矩形框拼接而成的平滑區(qū)域。需求是當(dāng)鼠標(biāo)移至矩形框上時(shí)出現(xiàn)文本提示框來顯示當(dāng)前是第幾個(gè)矩形。下圖是這些矩形框的坐標(biāo):

          坐標(biāo)

          接下來繪制矩形框:

          繪制

          判斷一個(gè)點(diǎn)是否在矩形框內(nèi)的數(shù)學(xué)方法為:假設(shè)點(diǎn)為o,矩形為abcd,如果Soab+Sobc+Socd+Soda=Sabcd,則該點(diǎn)在矩形框內(nèi),否則不在。

          判斷

          涉及到的函數(shù)有:計(jì)算兩點(diǎn)之間距離、計(jì)算三角形面積、判斷點(diǎn)是否在矩形內(nèi)。當(dāng)鼠標(biāo)移動(dòng)的時(shí)候,循環(huán)判斷鼠標(biāo)坐標(biāo)點(diǎn)是否在矩形框內(nèi),在的話就出現(xiàn)提示框。為了減少判斷、提高性能,創(chuàng)建一個(gè)函數(shù)用于計(jì)算矩形框的水平跨度,即X軸范圍,當(dāng)目標(biāo)點(diǎn)不在該范圍內(nèi)就不做是否在矩形框內(nèi)的判斷。

          事件處理程序

          其中minmax是一個(gè)二維數(shù)組,保存著所有矩形框的X軸跨度。

          當(dāng)條件成立時(shí),效果如下,即顯示文本:

          效果


          家好! 歡迎來到本教程,我們將深入了解使用 HTML 畫布和 JavaScript 在代碼中創(chuàng)建有趣的氣泡的世界。 最好的部分? 我們將只使用一點(diǎn) HTML 和所有 JavaScript,而不是 CSS 來實(shí)現(xiàn)所有這一切。

          揭示概念

          今天,我們要掌握以下幾個(gè)概念:

          使用畫布上下文的 arc 方法創(chuàng)建圓。

          利用 requestAnimationFrame 函數(shù)實(shí)現(xiàn)平滑的圓形動(dòng)畫。

          利用 JavaScript 類的強(qiáng)大功能來創(chuàng)建多個(gè)圓圈,而無需重復(fù)代碼。

          向我們的圓圈添加描邊樣式和填充樣式以獲得 3D 氣泡效果。

          你可以跟著我一起看,或者如果你想看源代碼,可以使用最終的codepen

          入門

          首先,我們需要一個(gè) HTML5 Canvas 元素。 Canvas 是創(chuàng)建形狀、圖像和圖形的強(qiáng)大元素。 這就是氣泡將產(chǎn)生的地方。 讓我們來設(shè)置一下 -

          <canvas id="canvas"></canvas>

          為了使用畫布做任何有意義的事情,我們需要訪問它的上下文。 Context 提供了在畫布上渲染對(duì)象和繪制形狀的接口。

          讓我們?cè)L問畫布及其上下文。

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

          我們將設(shè)置畫布以使用整個(gè)窗口的高度和寬度 -

          canvas.width=window.innerWidth;
          canvas.height=window.innerHeight;

          讓我們通過添加一些 css 為畫布提供一個(gè)漂亮的舒緩淺藍(lán)色背景。 這是我們要使用的唯一 CSS。 如果您愿意,也可以使用 JavaScript 來完成此操作。

          #canvas {
            background: #00b4ff;
          }

          是時(shí)候創(chuàng)造泡泡了!

          讓我們進(jìn)入有趣的部分。 我們將通過單擊畫布來創(chuàng)建氣泡。 為了實(shí)現(xiàn)這一點(diǎn),我們首先創(chuàng)建一個(gè)點(diǎn)擊事件處理程序:

          canvas.addEventListener('click', handleDrawCircle);

          由于我們需要知道在畫布上單擊的位置,因此我們將在句柄 DrawCircle 函數(shù)中跟蹤它并使用事件的坐標(biāo) -

          //We are adding x and y here because we will need it later.
          let x, y
          const handleDrawCircle=(event)=> {
            x=event.pageX;
            y=event.pageY;
          
          // Draw a bubble!
            drawCircle(x, y);
          };

          用圓弧法畫圓

          為了創(chuàng)建圓圈,我們將利用畫布上下文中可用的 arc 方法。 Arc 方法接受 x 和 y - 圓心、半徑、起始角和結(jié)束角,對(duì)于我們來說,這將是 0 和 2* Math.PI,因?yàn)槲覀冋趧?chuàng)建一個(gè)完整的圓。

          const drawCircle=(x, y)=> {
            context.beginPath();
            context.arc(x, y, 50, 0, 2 * Math.PI);
            context.strokeStyle='white';
            context.stroke();
          };


          使用 requestAnimationFrame 方法移動(dòng)圓圈

          現(xiàn)在我們有了圓圈,讓我們讓它們移動(dòng),因?yàn)椤?/p>

          GIF



          請(qǐng)記住,當(dāng)我們創(chuàng)建圓時(shí),我們使用了 arc 方法,它接受 x 和 y 坐標(biāo) - 圓的中心。 如果我們快速移動(dòng)圓的 x 和 y 坐標(biāo),就會(huì)給人一種圓在移動(dòng)的印象。 讓我們?cè)囋嚢桑?/span>

          //Define a speed by which to increment to the x and y coordinates
          const dx=Math.random() * 3;
          const dy=Math.random() * 7;//Increment the center of the circle with this speed
          x=x + dx;
          y=y - dy;

          我們可以將其移至函數(shù)內(nèi) -

          let x, y;
          const move=()=> {
            const dx=Math.random() * 3;
            const dy=Math.random() * 7;  x=x + dx;
            y=y - dy;
          };

          為了讓我們的圓圈無縫移動(dòng),我們將創(chuàng)建一個(gè)動(dòng)畫函數(shù)并使用瀏覽器的 requestAnimationFrame 方法來創(chuàng)建一個(gè)移動(dòng)的圓圈。

          const animate=()=> {
            context.clearRect(0, 0, canvas.width, canvas.height);
            move();
              drawCircle(x,y);  requestAnimationFrame(animate);
          };//Don't forget to call animate at the bottom 
          animate();



          創(chuàng)建粒子:引入粒子類

          現(xiàn)在我們已經(jīng)創(chuàng)建了一個(gè)圓圈,是時(shí)候創(chuàng)建多個(gè)圓圈了!

          但在我們創(chuàng)建多個(gè)圓圈之前,讓我們準(zhǔn)備一下我們的代碼。為了避免重復(fù)我們的代碼,我們將使用類并引入 Particle 類。 粒子是我們動(dòng)態(tài)藝術(shù)作品和動(dòng)畫的構(gòu)建塊。 每個(gè)氣泡都是一個(gè)粒子,具有自己的位置、大小、運(yùn)動(dòng)和顏色屬性。 讓我們定義一個(gè) Particle 類來封裝這些屬性:

          class Particle {
            constructor(x=0, y=0) {}
            draw() {
              // Drawing the particle as a colored circle
              // ...
            }  move() {
              // Implementing particle movement
              // ...
            }
          }

          讓我們將一些已設(shè)置的常量移至 Particle 類 -

          class Particle {
            constructor(x=0, y=0) {
              this.x=x;
              this.y=y;
              this.radius=Math.random() * 50;
              this.dx=Math.random() * 3;
              this.dy=Math.random() * 7;
            }
            draw() {
              // Drawing the particle as a colored circle
              // ...
            }  move() {
              // Implementing particle movement
              // ...
            }
          }

          draw 方法將負(fù)責(zé)在畫布上渲染粒子。 我們已經(jīng)在drawCircle中實(shí)現(xiàn)了這個(gè)功能,所以讓我們將它移動(dòng)到我們的類中并將變量更新為類變量

          class Particle {
            constructor(x=0, y=0) {
              this.x=x;
              this.y=y;
              this.radius=Math.random() * 50;
              this.dx=Math.random() * 3;
              this.dy=Math.random() * 7;
              this.color='white';
            }
            draw() {
              context.beginPath();
              context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
              context.strokeStyle=this.color;
              context.stroke();    context.fillStyle=this.color;
              context.fill();
            }  move() {}
          }

          同樣,讓我們?cè)陬愔幸苿?dòng) move 函數(shù) -

          move() {
              this.x=this.x + this.dx;
              this.y=this.y - this.dy;
          }

          現(xiàn)在,我們需要確保在事件處理程序中調(diào)用 Particle 類。

          const handleDrawCircle=(event)=> {
            const x=event.pageX;
            const y=event.pageY;
            const particle=new Particle(x, y);
          };canvas.addEventListener('click', handleDrawCircle);

          由于我們需要在 animate 函數(shù)中訪問該粒子,以便調(diào)用其 move 方法,因此我們將該粒子存儲(chǔ)在一個(gè)名為 molecularArray 的數(shù)組中。 當(dāng)創(chuàng)建大量粒子時(shí),這個(gè)數(shù)組也會(huì)很有幫助。 這是反映這一點(diǎn)的更新代碼 -

          const particleArray=[];
          const handleDrawCircle=(event)=> {
            const x=event.pageX;
            const y=event.pageY;  const particle=new Particle(x, y);
            particleArray.push(particle);
          };canvas.addEventListener('click', handleDrawCircle);

          記得也要更新動(dòng)畫功能 -

          此時(shí),您將在屏幕上看到這個(gè)粒子 -



          驚人的! 現(xiàn)在,到了有趣的部分! 讓我們創(chuàng)建很多圓圈并設(shè)計(jì)它們的樣式,使它們看起來像氣泡。

          為了創(chuàng)建大量氣泡,我們將使用 for 循環(huán)創(chuàng)建粒子并將它們添加到我們?cè)诖颂巹?chuàng)建的粒子數(shù)組中。

          const handleDrawCircle=(event)=> {
            const x=event.pageX;
            const y=event.pageY;
            for (let i=0; i < 50; i++) {
              const particle=new Particle(x, y);
              particleArray.push(particle);
            }
          };canvas.addEventListener('click', handleDrawCircle);

          在動(dòng)畫函數(shù)中,我們將通過清除畫布并在新位置重新繪制粒子來不斷更新畫布。 這會(huì)給人一種圓圈在移動(dòng)的錯(cuò)覺。

          const animate=()=> {
            context.clearRect(0, 0, canvas.width, canvas.height);
            particleArray.forEach((particle)=> {
              particle?.move();
              particle?.draw();
            });  requestAnimationFrame(animate);
          };animate();


          現(xiàn)在我們有了移動(dòng)的氣泡,是時(shí)候給它們添加顏色,使它們看起來像氣泡了!

          我們將通過向氣泡添加漸變填充來實(shí)現(xiàn)此目的。 這可以使用 context.createRadialGradient 方法來完成。

          const gradient=context.createRadialGradient(
            this.x,
            this.y,
            1,
            this.x + 0.5,
            this.y + 0.5,
            this.radius
          );
          gradient.addColorStop(0.3, 'rgba(255, 255, 255, 0.3)');
          gradient.addColorStop(0.95, '#e7feff');context.fillStyle=gradient;



          總結(jié)

          恭喜! 您剛剛僅使用 HTML Canvas 和 JavaScript 創(chuàng)建了一些超級(jí)有趣的東西。 您已經(jīng)學(xué)習(xí)了如何使用 arc 方法、利用 requestAnimationFrame、利用 JavaScript 類的強(qiáng)大功能以及使用漸變?cè)O(shè)計(jì)氣泡以實(shí)現(xiàn) 3D 氣泡效果。

          請(qǐng)隨意嘗試顏色、速度和大小,使您的動(dòng)畫真正獨(dú)一無二。

          請(qǐng)隨意嘗試顏色、速度和大小,使您的動(dòng)畫真正獨(dú)一無二。

          我希望您在學(xué)習(xí)本教程時(shí)能像我在創(chuàng)建它時(shí)一樣獲得樂趣。 現(xiàn)在,輪到你進(jìn)行實(shí)驗(yàn)了。 我很想看看你是否嘗試過這個(gè)以及你創(chuàng)造了什么。 與我分享您的代碼鏈接,我很樂意查看。

          文簡(jiǎn)介

          點(diǎn)贊 + 關(guān)注 + 收藏=學(xué)會(huì)了

          在前端領(lǐng)域,如果只是懂 Vue 或者 React ,未來在職場(chǎng)的競(jìng)爭(zhēng)力可能會(huì)比較弱。

          根據(jù)我多年在家待業(yè)經(jīng)驗(yàn)來看,前端未來在 數(shù)據(jù)可視化AI 這兩個(gè)領(lǐng)域會(huì)比較香,而 Canvas 是數(shù)據(jù)可視化在前端方面的基礎(chǔ)技術(shù)。

          本文就用光的速度將 canvas 給入門了。

          01.gif

          要入門一個(gè)技術(shù),前期最重要是快!所以本文只講入門內(nèi)容,能應(yīng)付簡(jiǎn)單項(xiàng)目。深入的知識(shí)點(diǎn)會(huì)在其他文章講解。

          文章同時(shí)收錄于小程序-互聯(lián)網(wǎng)小兵,不止于前端,收各平臺(tái)優(yōu)質(zhì)熱門的技術(shù)文章(后端、移動(dòng)端、算法、人工智能...),大家支持支持,點(diǎn)擊前往體驗(yàn)!

          Canvas 是什么?

          • Canvas 中文名叫 “畫布”,是 HTML5 新增的一個(gè)標(biāo)簽。
          • Canvas 允許開發(fā)者通過 JS在這個(gè)標(biāo)簽上繪制各種圖案。
          • Canvas 擁有多種繪制路徑、矩形、圓形、字符以及圖片的方法。
          • Canvas 在某些情況下可以 “代替” 圖片。
          • Canvas 可用于動(dòng)畫、游戲、數(shù)據(jù)可視化、圖片編輯器、實(shí)時(shí)視頻處理等領(lǐng)域。

          Canvas 和 SVG 的區(qū)別

          CanvasSVG用JS動(dòng)態(tài)生成元素(一個(gè)HTML元素)用XML描述元素(類似HTML元素那樣,可用多個(gè)元素來描述一個(gè)圖形)位圖(受屏幕分辨率影響)矢量圖(不受屏幕分辨率影響)不支持事件支持事件數(shù)據(jù)發(fā)生變化需要重繪不需要重繪

          就上面的描述而言可能有點(diǎn)難懂,你可以打開 AntV 旗下的圖形編輯引擎做對(duì)比。G6[1] 是使用 canvas 開發(fā)的,X6[2] 是使用 svg 開發(fā)的。

          我的建議是:如果要展示的數(shù)據(jù)量比較大,比如一條數(shù)據(jù)就是一個(gè)元素節(jié)點(diǎn),那使用 canvas 會(huì)比較合適;如果用戶操作的交互比較多,而且對(duì)清晰度有要求(矢量圖),那么使用 svg 會(huì)比較合適。

          起步

          學(xué)習(xí)前端一定要?jiǎng)邮智么a,然后看效果展示。

          起步階段會(huì)用幾句代碼說明 canvas 如何使用,本例會(huì)畫一條直線。

          畫條直線

          1. HTML 中創(chuàng)建 canvas 元素
          2. 通過 js 獲取 canvas 標(biāo)簽
          3. canvas 標(biāo)簽中獲取到繪圖工具
          4. 通過繪圖工具,在 canvas 標(biāo)簽上繪制圖形

          02.png

          <!-- 1、創(chuàng)建 canvas 元素 -->
          <canvas
            id="c"
            width="300"
            height="200"
            style="border: 1px solid #ccc;"
          ></canvas>
          
          <script>
            // 2、獲取 canvas 對(duì)象
            const cnv=document.getElementById('c')
          
            // 3、獲取 canvas 上下文環(huán)境對(duì)象
            const cxt=cnv.getContext('2d')
          
            // 4、繪制圖形
            cxt.moveTo(100, 100) // 起點(diǎn)坐標(biāo) (x, y)
            cxt.lineTo(200, 100) // 終點(diǎn)坐標(biāo) (x, y)
            cxt.stroke() // 將起點(diǎn)和終點(diǎn)連接起來
          </script>
          復(fù)制代碼
          

          moveTolineTostroke 方法暫時(shí)可以不用管,它們的作用是繪制圖形,這些方法在后面會(huì)講到~

          注意點(diǎn)

          1、默認(rèn)寬高

          canvas默認(rèn)的 寬度(300px) 和 高度(150px)

          如果不在 canvas 上設(shè)置寬高,那 canvas 元素的默認(rèn)寬度是300px,默認(rèn)高度是150px。

          2、設(shè)置 canvas 寬高

          canvas 元素提供了 widthheight 兩個(gè)屬性,可設(shè)置它的寬高。

          需要注意的是,這兩個(gè)屬性只需傳入數(shù)值,不需要傳入單位(比如 px 等)。

          <canvas width="600" height="400"></canvas>
          復(fù)制代碼
          

          3、不能通過 CSS 設(shè)置畫布的寬高

          使用 css 設(shè)置 canvas 的寬高,會(huì)出現(xiàn) 內(nèi)容被拉伸 的后果?。。?/span>

          03.png

          <style>
            #c {
              width: 400px;
              height: 400px;
              border: 1px solid #ccc;
            }
          </style>
          
          <canvas id="c"></canvas>
          
          <script>
            // 1、獲取canvas對(duì)象
            const cnv=document.getElementById('c')
          
            // 2、獲取canvas上下文環(huán)境對(duì)象
            const cxt=cnv.getContext('2d')
          
            // 3、繪制圖形
            cxt.moveTo(100, 100) // 起點(diǎn)
            cxt.lineTo(200, 100) // 終點(diǎn)
            cxt.stroke() // 將起點(diǎn)和終點(diǎn)連接起來
          
            console.log(cnv.width) // 獲取 canvas 的寬度,輸出:300
            console.log(cnv.height) // 獲取 canvas 的高度,輸出:150
          </script>
          復(fù)制代碼
          

          canvas 的默認(rèn)寬度是300px,默認(rèn)高度是150px。

          1. 如果使用 css 修改 canvas 的寬高(比如本例變成 400px * 400px),那寬度就由 300px 拉伸到 400px,高度由 150px 拉伸到 400px。
          2. 使用 js 獲取 canvas 的寬高,此時(shí)返回的是 canvas 的默認(rèn)值。

          最后出現(xiàn)的效果如上圖所示。

          4、線條默認(rèn)寬度和顏色

          線條的默認(rèn)寬度是 1px ,默認(rèn)顏色是黑色。

          但由于默認(rèn)情況下 canvas 會(huì)將線條的中心點(diǎn)和像素的底部對(duì)齊,所以會(huì)導(dǎo)致顯示效果是 2px 和非純黑色問題。

          5、IE兼容性高

          暫時(shí)只有 IE 9 以上才支持 canvas 。但好消息是 IE 已經(jīng)有自己的墓碑了。

          如需兼容 IE 7 和 8 ,可以使用 ExplorerCanvas [3]。但即使是使用了 ExplorerCanvas 仍然會(huì)有所限制,比如無法使用 fillText() 方法等。

          基礎(chǔ)圖形

          坐標(biāo)系

          在繪制基礎(chǔ)圖形之前,需要先搞清除 Canvas 使用的坐標(biāo)系。

          Canvas 使用的是 W3C 坐標(biāo)系 ,也就是遵循我們屏幕、報(bào)紙的閱讀習(xí)慣,從上往下,從左往右。

          04.jpg

          W3C 坐標(biāo)系數(shù)學(xué)直角坐標(biāo)系X軸 是一樣的,只是 Y軸 的反向相反。

          W3C 坐標(biāo)系Y軸 正方向向下。

          直線

          一條直線

          最簡(jiǎn)單的起步方式是畫一條直線。這里所說的 “直線” 是幾何學(xué)里的 “線段” 的意思。

          需要用到這3個(gè)方法:

          1. moveTo(x1, y1):起點(diǎn)坐標(biāo) (x, y)
          2. lineTo(x2, y2):下一個(gè)點(diǎn)的坐標(biāo) (x, y)
          3. stroke():將所有坐標(biāo)用一條線連起來

          起步階段可以先這樣理解。

          05.png

          <canvas id="c" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 繪制直線
            cxt.moveTo(50, 100) // 起點(diǎn)坐標(biāo)
            cxt.lineTo(200, 50) // 下一個(gè)點(diǎn)的坐標(biāo)
            cxt.stroke() // 將上面的坐標(biāo)用一條線連接起來
          </script>
          復(fù)制代碼
          

          上面的代碼所呈現(xiàn)的效果,可以看下圖解釋(手不太聰明,畫得不是很標(biāo)準(zhǔn),希望能看懂)

          06.jpg

          多條直線

          如需畫多條直線,可以用會(huì)上面那幾個(gè)方法。

          07.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.moveTo(20, 100)
            cxt.lineTo(200, 100)
            cxt.stroke()
          
            cxt.moveTo(20, 120.5)
            cxt.lineTo(200, 120.5)
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          仔細(xì)觀察一下,為什么兩條線的粗細(xì)不一樣的?

          明明使用的方法都是一樣的,只是第二條直線的 Y軸 的值是有小數(shù)點(diǎn)。

          答:默認(rèn)情況下 canvas 會(huì)將線條的中心點(diǎn)和像素的底部對(duì)齊,所以會(huì)導(dǎo)致顯示效果是 2px 和非純黑色問題。

          08.jpg

          上圖每個(gè)格子代表 1px。

          線的中心點(diǎn)會(huì)和畫布像素點(diǎn)的底部對(duì)齊,所以會(huì)線中間是黑色的,但由于一個(gè)像素就不能再切割了,所以會(huì)有半個(gè)像素被染色,就變成了淺灰色。

          所以如果你設(shè)置的 Y軸 值是一個(gè)整數(shù),就會(huì)出現(xiàn)上面那種情況。

          設(shè)置樣式

          • lineWidth:線的粗細(xì)
          • strokeStyle:線的顏色
          • lineCap:線帽:默認(rèn): butt; 圓形: round; 方形: square

          09.png

          <canvas id="c" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 繪制直線
            cxt.moveTo(50, 50)
            cxt.lineTo(200, 50)
          
            // 修改直線的寬度
            cxt.lineWidth=20
          
            // 修改直線的顏色
            cxt.strokeStyle='pink'
          
            // 修改直線兩端樣式
            cxt.lineCap='round' // 默認(rèn): butt; 圓形: round; 方形: square
          
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          新開路徑

          開辟新路徑的方法:

          • beginPath()

          在繪制多條線段的同時(shí),還要設(shè)置線段樣式,通常需要開辟新路徑。

          要不然樣式之間會(huì)相互污染。

          比如這樣

          10.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 第一條線
            cxt.moveTo(20, 100)
            cxt.lineTo(200, 100)
            cxt.lineWidth=10
            cxt.strokeStyle='pink'
            cxt.stroke()
          
            // 第二條線
            cxt.moveTo(20, 120.5)
            cxt.lineTo(200, 120.5)
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          如果不想相互污染,需要做2件事:

          1. 使用 beginPath() 方法,重新開一個(gè)路徑
          2. 設(shè)置新線段的樣式(必須項(xiàng))

          如果上面2步卻了其中1步都會(huì)有影響。

          只使用beginPath()

          11.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 第一條線
            cxt.moveTo(20, 100)
            cxt.lineTo(200, 100)
            cxt.lineWidth=10
            cxt.strokeStyle='pink'
            cxt.stroke()
          
            // 第二條線
            cxt.beginPath() // 重新開啟一個(gè)路徑
            cxt.moveTo(20, 120.5)
            cxt.lineTo(200, 120.5)
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          第一條線的樣式會(huì)影響之后的線。

          但如果使用了 beginPath() ,后面的線段不會(huì)影響前面的線段。

          12.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 第一條線
            cxt.moveTo(20, 100)
            cxt.lineTo(200, 100)
            cxt.stroke()
          
            // 第二條線
            cxt.beginPath() // 重新開啟一個(gè)路徑
            cxt.moveTo(20, 120.5)
            cxt.lineTo(200, 120.5)
            cxt.lineWidth=4
            cxt.strokeStyle='red'
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          設(shè)置新線段的樣式,沒使用beginPath()的情況

          這個(gè)情況會(huì)反過來,后面的線能影響前面的線。

          13.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 第一條線
            cxt.moveTo(20, 100)
            cxt.lineTo(200, 100)
            cxt.lineWidth=10
            cxt.strokeStyle='pink'
            cxt.stroke()
          
            // 第二條線
            cxt.moveTo(20, 120.5)
            cxt.lineTo(200, 120.5)
            cxt.lineWidth=4
            cxt.strokeStyle='red'
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          正確的做法

          在設(shè)置 beginPath() 的同時(shí),也各自設(shè)置樣式。這樣就能做到相互不影響了。

          14.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.moveTo(20, 100)
            cxt.lineTo(200, 100)
            cxt.lineWidth=10
            cxt.strokeStyle='pink'
            cxt.stroke()
          
            cxt.beginPath() // 重新開啟一個(gè)路徑
            cxt.moveTo(20, 120.5)
            cxt.lineTo(200, 120.5)
            cxt.lineWidth=4
            cxt.strokeStyle='red'
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          折線

          直線 差不多,都是使用 moveTo()lineTo()stroke() 方法可以繪制折線。

          15.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.moveTo(50, 200)
            cxt.lineTo(100, 50)
            cxt.lineTo(200, 200)
            cxt.lineTo(250, 50)
          
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          畫這種折線,最好在草稿紙上畫一個(gè)坐標(biāo)系,自己計(jì)算并描繪一下每個(gè)點(diǎn)大概在什么什么位置,最后在 canvas 中看看效果。

          矩形

          根據(jù)前面的基礎(chǔ),我們可以 使用線段來描繪矩形,但 canvas 也提供了 rect() 等方法可以直接生成矩形。

          使用線段描繪矩形

          可以使用前面畫線段的方法來繪制矩形

          16.png

          canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
           const cnv=document.getElementById('c')
           const cxt=cnv.getContext('2d')
          
           // 繪制矩形
           cxt.moveTo(50, 50)
           cxt.lineTo(200, 50)
           cxt.lineTo(200, 120)
           cxt.lineTo(50, 120)
           cxt.lineTo(50, 50) // 需要閉合,又或者使用 closePath() 方法進(jìn)行閉合,推薦使用 closePath()
          
           cxt.stroke()
          </script>
          復(fù)制代碼
          

          上面的代碼幾個(gè)點(diǎn)分別對(duì)應(yīng)下圖。

          17.jpg

          使用strokeRect()描邊矩形

          • strokeStyle:設(shè)置描邊的屬性(顏色、漸變、圖案)
          • strokeRect(x, y, width, height):描邊矩形(x和y是矩形左上角起點(diǎn);width 和 height 是矩形的寬高)
          • strokeStyle 必須寫在 strokeRect() 前面,不然樣式不生效。

          18.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // strokeStyle 屬性
            // strokeRect(x, y, width, height) 方法
            cxt.strokeStyle='pink'
            cxt.strokeRect(50, 50, 200, 100)
          </script>
          復(fù)制代碼
          

          上面的代碼可以這樣理解

          19.jpg

          使用fillRect()填充矩形

          fillRect()strokeRect() 方法差不多,但 fillRect() 的作用是填充。

          需要注意的是,fillStyle 必須寫在 fillRect() 之前,不然樣式不生效。

          20.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // fillStyle 屬性
            // fillRect(x, y, width, height) 方法
            cxt.fillStyle='pink'
            cxt.fillRect(50, 50, 200, 100) // fillRect(x, y, width, height)
          </script>
          復(fù)制代碼
          

          同時(shí)使用strokeRect()和fillRect()

          同時(shí)使用 strokeRect()fillRect() 會(huì)產(chǎn)生描邊和填充的效果

          21.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.strokeStyle='red'
            cxt.strokeRect(50, 50, 200, 100) // strokeRect(x, y, width, height)
            cxt.fillStyle='yellow'
            cxt.fillRect(50, 50, 200, 100) // fillRect(x, y, width, height)
          </script>
          復(fù)制代碼
          

          使用rect()生成矩形

          rect()fillRect() 、strokeRect() 的用法差不多,唯一的區(qū)別是:

          strokeRect()fillRect() 這兩個(gè)方法調(diào)用后會(huì)立即繪制;rect() 方法被調(diào)用后,不會(huì)立刻繪制矩形,而是需要調(diào)用 stroke()fill() 輔助渲染。

          22.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.strokeStyle='red'
            cxt.fillStyle='pink'
          
            cxt.rect(50, 50, 200, 100) // rect(x, y, width, height)
          
            cxt.stroke()
            cxt.fill()
          </script>
          復(fù)制代碼
          

          等價(jià)公式:

          cxt.strokeStyle='red',
          cxt.rect(50, 50, 200, 100)
          cxt.stroke()
          
          // 等價(jià)于
          cxt.strokeStyle='red'
          cxt.strokerect(50, 50, 200, 100)
          
          
          // -----------------------------
          
          
          cxt.fillStyle='hotpink'
          cxt.rect(50, 50, 200, 100)
          cxt.fill()
          
          // 等價(jià)于
          cxt.fillStyle='yellowgreen'
          cxt.fillRect(50, 50, 200, 100)
          復(fù)制代碼
          

          使用clearRect()清空矩形

          使用 clearRect() 方法可以清空指定區(qū)域。

          clearRect(x, y, width, height)
          復(fù)制代碼
          

          其語法和創(chuàng)建 cxt.rect() 差不多。

          23.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.fillStyle='pink' // 設(shè)置填充顏色
            cxt.fillRect(50, 50, 200, 200) // 填充矩形
          
            cxt.clearRect(60, 60, 180, 90) // 清空矩形
          </script>
          復(fù)制代碼
          

          清空畫布

          canvas 畫布元素是矩形,所以可以通過下面的代碼把整個(gè)畫布清空掉。

          // 省略部分代碼
          
          cxt.clearRect(0, 0, cnv.width, cnv.height)
          復(fù)制代碼
          

          要清空的區(qū)域:從畫布左上角開始,直到畫布的寬和畫布的高為止。

          \

          多邊形

          Canvas 要畫多邊形,需要使用 moveTo()lineTo()closePath() 。

          三角形

          雖然三角形是常見圖形,但 canvas 并沒有提供類似 rect() 的方法來繪制三角形。

          需要確定三角形3個(gè)點(diǎn)的坐標(biāo)位置,然后使用 stroke() 或者 fill() 方法生成三角形。

          24.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
          
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.moveTo(50, 50)
            cxt.lineTo(200, 50)
            cxt.lineTo(200, 200)
          
            // 注意點(diǎn):如果使用 lineTo 閉合圖形,是不能很好閉合拐角位的。
            cxt.lineTo(50, 50) // 閉合
          
            cxt.stroke()
          
          </script>
          復(fù)制代碼
          

          注意,默認(rèn)情況下不會(huì)自動(dòng)從最后一個(gè)點(diǎn)連接到起點(diǎn)。最后一步需要設(shè)置一下 cxt.lineTo(50, 50) ,讓它與 cxt.moveTo(50, 50) 一樣。這樣可以讓路徑回到起點(diǎn),形成一個(gè)閉合效果。

          但這樣做其實(shí)是有點(diǎn)問題的,而且也比較麻煩,要記住起始點(diǎn)坐標(biāo)。

          上面的閉合操作,如果遇到設(shè)置了 lineWidth 或者 lineJoin 就會(huì)有問題,比如:

          25.png

          // 省略部分代碼
          cxt.lineWidth=20
          復(fù)制代碼
          

          當(dāng)線段變粗后,起始點(diǎn)和結(jié)束點(diǎn)的鏈接處,拐角就出現(xiàn)“不正?!爆F(xiàn)象。

          如果需要真正閉合,可以使用 closePath() 方法。

          26.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.moveTo(50, 50)
            cxt.lineTo(200, 50)
            cxt.lineTo(200, 200)
            // 手動(dòng)閉合
            cxt.closePath()
          
            cxt.lineJoin='miter' // 線條連接的樣式。miter: 默認(rèn); bevel: 斜面; round: 圓角
            cxt.lineWidth=20
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          使用 cxt.closePath() 可以自動(dòng)將終點(diǎn)和起始點(diǎn)連接起來,此時(shí)看上去就正常多了。

          菱形

          有一組鄰邊相等的平行四邊形是菱形

          27.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.moveTo(150, 50)
            cxt.lineTo(250, 100)
            cxt.lineTo(150, 150)
            cxt.lineTo(50, 100)
            cxt.closePath()
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          要繪制直線類型的圖形,在草稿紙上標(biāo)記出起始點(diǎn)和每個(gè)拐角的點(diǎn),然后再連線即可。相對(duì)曲線圖形來說,直線圖形是比較容易的。

          圓形

          繪制圓形的方法是 arc()。

          語法:

          arc(x, y, r, sAngle, eAngle,counterclockwise)
          復(fù)制代碼
          
          • xy: 圓心坐標(biāo)
          • r: 半徑
          • sAngle: 開始角度
          • eAngle: 結(jié)束角度
          • counterclockwise: 繪制方向(true: 逆時(shí)針; false: 順時(shí)針),默認(rèn) false

          28.jpg

          開始角度和結(jié)束角度,都是以弧度為單位。例如 180°就寫成 Math.PI ,360°寫成 Math.PI * 2 ,以此類推。

          在實(shí)際開發(fā)中,為了讓自己或者別的開發(fā)者更容易看懂弧度的數(shù)值,1°應(yīng)該寫成 Math.PI / 180。

          • 100°: 100 * Math.PI / 180
          • 110°: 110 * Math.PI / 180
          • 241°: 241 * Math.PI / 180

          注意:繪制圓形之前,必須先調(diào)用 beginPath() 方法!!!在繪制完成之后,還需要調(diào)用 closePath() 方法?。。?/strong>

          29.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.beginPath()
            cxt.arc(150, 150, 80, 0, 360 * Math.PI / 180)
            cxt.closePath()
          
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          半圓

          如果使用 arc() 方法畫圓時(shí),沒做到剛好繞完一周(360°)就直接閉合路徑,就會(huì)出現(xiàn)半圓的狀態(tài)。

          30.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.beginPath()
            cxt.arc(150, 150, 100, 0, 180 * Math.PI / 180) // 順時(shí)針
            cxt.closePath()
          
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          上面的代碼中,cxt.arc 最后一個(gè)參數(shù)沒傳,默認(rèn)是 false ,所以是順時(shí)針繪制。

          31.jpg

          如果希望半圓的弧面在上方,可以將 cxt.arc 最后一個(gè)參數(shù)設(shè)置成 true

          32.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.beginPath()
            cxt.arc(150, 150, 100, 0, 180 * Math.PI / 180, true)
            cxt.closePath()
          
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          弧線

          使用 arc() 方法畫半圓時(shí),如果最后不調(diào)用 closePath() 方法,就不會(huì)出現(xiàn)閉合路徑。也就是說,那是一條弧線。

          canvas 中,畫弧線有2中方法:arc()arcTo() 。

          arc() 畫弧線

          如果想畫一條 0° ~ 30° 的弧線,可以這樣寫

          33.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.beginPath()
            cxt.arc(150, 150, 100, 0, 30 * Math.PI / 180)
          
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          原理如下圖所示,紅線代表畫出來的那條弧線。

          34.jpg

          arcTo() 畫弧線

          arcTo() 的使用方法會(huì)更加復(fù)雜,如果初學(xué)看不太懂的話可以先跳過,看完后面的再回來補(bǔ)補(bǔ)。

          語法:

          arcTo(cx, cy, x2, y2, radius)
          復(fù)制代碼
          
          • cx: 兩切線交點(diǎn)的橫坐標(biāo)
          • cy: 兩切線交點(diǎn)的縱坐標(biāo)
          • x2: 結(jié)束點(diǎn)的橫坐標(biāo)
          • y2: 結(jié)束點(diǎn)的縱坐標(biāo)
          • radius: 半徑

          其中,(cx, cy) 也叫控制點(diǎn),(x2, y2) 也叫結(jié)束點(diǎn)。

          是不是有點(diǎn)奇怪,為什么沒有 x1y1 ?

          (x1, y1) 是開始點(diǎn),通常是由 moveTo() 或者 lineTo() 提供。

          arcTo() 方法利用 開始點(diǎn)、控制點(diǎn)和結(jié)束點(diǎn)形成的家教,繪制一段與家教的兩邊相切并且半徑為 radius 的圓弧。

          35.jpg

          舉個(gè)例子

          36.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.moveTo(40, 40)
            cxt.arcTo(120, 40, 120, 80, 80)
          
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          基礎(chǔ)樣式

          前面學(xué)完基礎(chǔ)圖形,接下來可以開始了解一下如何設(shè)置元素的基礎(chǔ)樣式。

          描邊 stroke()

          前面的案例中,其實(shí)已經(jīng)知道使用 stroke() 方法進(jìn)行描邊了。這里就不再多講這個(gè)方法。

          線條寬度 lineWidth

          lineWidth 默認(rèn)值是 1 ,默認(rèn)單位是 px。

          語法:

          lineWidth=線寬
          復(fù)制代碼
          

          37.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 線寬 10
            cxt.beginPath()
            cxt.moveTo(50, 50)
            cxt.lineTo(250, 50)
            cxt.lineWidth=10 // 設(shè)置線寬
            cxt.stroke()
          
            // 線寬 20
            cxt.beginPath()
            cxt.moveTo(50, 150)
            cxt.lineTo(250, 150)
            cxt.lineWidth=20 // 設(shè)置線寬
            cxt.stroke()
          
            // 線寬 30
            cxt.beginPath()
            cxt.moveTo(50, 250)
            cxt.lineTo(250, 250)
            cxt.lineWidth=30 // 設(shè)置線寬
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          線條顏色 strokeStyle

          使用 strokeStyle 可以設(shè)置線條顏色

          語法:

          strokeStyle=顏色值
          復(fù)制代碼
          

          38.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.moveTo(50, 50)
            cxt.lineTo(250, 50)
            cxt.lineWidth=20
            cxt.strokeStyle='pink' // 設(shè)置顏色
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          為了展示方便,我將 lineWidth 設(shè)為 20。

          線帽 lineCap

          線帽指的是線段的開始和結(jié)尾處的樣式,使用 lineCap 可以設(shè)置

          語法:

          lineCap='屬性值'
          復(fù)制代碼
          

          屬性值包括:

          • butt: 默認(rèn)值,無線帽
          • square: 方形線帽
          • round: 圓形線帽

          39.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 設(shè)置線寬,方便演示
            cxt.lineWidth=16
          
            // 默認(rèn)線帽 butt
            cxt.beginPath()
            cxt.moveTo(50, 60)
            cxt.lineTo(250, 60)
            cxt.stroke()
          
          
            // 方形線帽 square
            cxt.beginPath()
            cxt.lineCap='square'
            cxt.moveTo(50, 150)
            cxt.lineTo(250, 150)
            cxt.stroke()
          
          
            // 圓形線帽 round
            cxt.beginPath()
            cxt.lineCap='round'
            cxt.moveTo(50, 250)
            cxt.lineTo(250, 250)
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          使用 squareround 的話,會(huì)使線條變得稍微長(zhǎng)一點(diǎn)點(diǎn),這是給線條增加線帽的部分,這個(gè)長(zhǎng)度在日常開發(fā)中需要注意。

          線帽只對(duì)線條的開始和結(jié)尾處產(chǎn)生作用,對(duì)拐角不會(huì)產(chǎn)生任何作用。

          拐角樣式 lineJoin

          如果需要設(shè)置拐角樣式,可以使用 lineJoin 。

          語法:

          lineJoin='屬性值'
          復(fù)制代碼
          

          屬性值包括:

          • miter: 默認(rèn)值,尖角
          • round: 圓角
          • bevel: 斜角

          40.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
            
            cxt.lineWidth=20
          
            // 默認(rèn),尖角
            cxt.moveTo(50, 40)
            cxt.lineTo(200, 40)
            cxt.lineTo(200, 90)
            cxt.stroke()
          
            // 斜角 bevel
            cxt.beginPath()
            cxt.moveTo(50, 140)
            cxt.lineTo(200, 140)
            cxt.lineTo(200, 190)
            cxt.lineJoin='bevel'
            cxt.stroke()
          
            // 圓角 round
            cxt.beginPath()
            cxt.moveTo(50, 240)
            cxt.lineTo(200, 240)
            cxt.lineTo(200, 290)
            cxt.lineJoin='round'
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          虛線 setLineDash()

          使用 setLineDash() 方法可以將描邊設(shè)置成虛線。

          語法:

          setLineDash([])
          復(fù)制代碼
          

          需要傳入一個(gè)數(shù)組,且元素是數(shù)值型。

          虛線分3種情況

          1. 只傳1個(gè)值
          2. 有2個(gè)值
          3. 有3個(gè)以上的值

          41.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.lineWidth=20
            cxt.strokeStyle='pink'
          
            cxt.moveTo(50, 50)
            cxt.lineTo(200, 50)
            cxt.setLineDash([10]) // 只傳1個(gè)參數(shù),實(shí)線與空白都是 10px
            cxt.stroke()
          
          
            cxt.beginPath()
            cxt.moveTo(50, 100)
            cxt.lineTo(200, 100)
            cxt.setLineDash([10, 20]) // 2個(gè)參數(shù),此時(shí),實(shí)線是 10px, 空白 20px
            cxt.stroke()
          
          
            cxt.beginPath()
            cxt.moveTo(50, 150)
            cxt.lineTo(200, 150)
            cxt.setLineDash([10, 20, 5]) // 傳3個(gè)以上的參數(shù),此例:10px實(shí)線,20px空白,5px實(shí)線,10px空白,20px實(shí)線,5px空白 ……
          
            cxt.stroke()
          </script>
          復(fù)制代碼
          

          此外,還可以始終 cxt.getLineDash() 獲取虛線不重復(fù)的距離;

          cxt.lineDashOffset 設(shè)置虛線的偏移位。

          填充

          使用 fill() 可以填充圖形,根據(jù)前面的例子應(yīng)該掌握了如何使用 fill()

          42.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.fillStyle='pink'
          
            cxt.rect(50, 50, 200, 100)
          
            cxt.fill()
          </script>
          復(fù)制代碼
          

          可以使用 fillStyle 設(shè)置填充顏色,默認(rèn)是黑色。

          非零環(huán)繞填充

          在使用 fill() 方法填充時(shí),需要注意一個(gè)規(guī)則:非零環(huán)繞填充

          在使用 moveTolineTo 描述圖形時(shí),如果是按順時(shí)針繪制,計(jì)數(shù)器會(huì)加1;如果是逆時(shí)針,計(jì)數(shù)器會(huì)減1。

          當(dāng)圖形所處的位置,計(jì)數(shù)器的結(jié)果為0時(shí),它就不會(huì)被填充。

          這樣說有點(diǎn)復(fù)雜,先看看例子

          43.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 外層矩形
            cxt.moveTo(50, 50)
            cxt.lineTo(250, 50)
            cxt.lineTo(250, 250)
            cxt.lineTo(50, 250)
            cxt.closePath()
          
            // 內(nèi)層矩形
            cxt.moveTo(200, 100)
            cxt.lineTo(100, 100)
            cxt.lineTo(100, 200)
            cxt.lineTo(200, 200)
            cxt.closePath()
            cxt.fill()
          </script>
          復(fù)制代碼
          

          請(qǐng)看看上面的代碼,我畫了2個(gè)矩形,它們都沒有用 beginPath() 方法開辟新路徑。

          44.png

          內(nèi)層矩形是逆時(shí)針繪制的,所以內(nèi)層的值是 -1 ,它又經(jīng)過外層矩形,而外層矩形是順時(shí)針繪制,所以經(jīng)過外層時(shí)值 +1,最終內(nèi)層的值為 0 ,所以不會(huì)被填充。

          文本

          Canvas 提供了一些操作文本的方法。

          為了方便演示,我們先了解一下在 Canvas 中如何給本文設(shè)置樣式。

          樣式 font

          CSS 設(shè)置 font 差不多,Canvas 也可以通過 font 設(shè)置樣式。

          語法:

          cxt.font='font-style font-variant font-weight font-size/line-height font-family'
          復(fù)制代碼
          

          如果需要設(shè)置字號(hào) font-size,需要同事設(shè)置 font-family

          cxt.font='30px 宋體'
          復(fù)制代碼
          

          描邊 strokeText()

          使用 strokeText() 方法進(jìn)行文本描邊

          語法:

          strokeText(text, x, y, maxWidth)
          復(fù)制代碼
          
          • text: 字符串,要繪制的內(nèi)容
          • x: 橫坐標(biāo),文本左邊要對(duì)齊的坐標(biāo)(默認(rèn)左對(duì)齊)
          • y: 縱坐標(biāo),文本底邊要對(duì)齊的坐標(biāo)
          • maxWidth: 可選參數(shù),表示文本渲染的最大寬度(px),如果文本超出 maxWidth 設(shè)置的值,文本會(huì)被壓縮。

          45.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.font='60px Arial' // 將字號(hào)設(shè)置成 60px,方便觀察
            cxt.strokeText('雷猴', 30, 90)
          </script>
          復(fù)制代碼
          

          設(shè)置描邊顏色 strokeStyle

          使用 strokeStyle 設(shè)置描邊顏色。

          46.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.font='60px Arial' // 將字號(hào)設(shè)置成 60px,方便觀察
            cxt.strokeStyle='pink' // 設(shè)置文本描邊顏色
            cxt.strokeText('雷猴', 30, 90)
          </script>
          復(fù)制代碼
          

          填充 fillText

          使用 fillText() 可填充文本。

          語法和 strokeText() 一樣。

          fillText(text, x, y, maxWidth)
          復(fù)制代碼
          

          47.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.font='60px Arial'
            cxt.fillText('雷猴', 30, 90)
          </script>
          復(fù)制代碼
          

          設(shè)置填充顏色 fillStyle

          使用 fillStyle 可以設(shè)置文本填充顏色。

          48.png

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            cxt.font='60px Arial'
            cxt.fillStyle='pink'
            cxt.fillText('雷猴', 30, 90)
          </script>
          復(fù)制代碼
          

          獲取文本長(zhǎng)度 measureText()

          measureText().width 方法可以獲取文本的長(zhǎng)度,單位是 px

          <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            let text='雷猴'
            cxt.font='bold 40px Arial'
            cxt.fillText(text, 40, 80)
          
            console.log(cxt.measureText(text).width) // 80
          </script>
          復(fù)制代碼
          

          水平對(duì)齊方式 textAlign

          使用 textAlign 屬性可以設(shè)置文字的水平對(duì)齊方式,一共有5個(gè)值可選

          • start: 默認(rèn)。在指定位置的橫坐標(biāo)開始。
          • end: 在指定坐標(biāo)的橫坐標(biāo)結(jié)束。
          • left: 左對(duì)齊。
          • right: 右對(duì)齊。
          • center: 居中對(duì)齊。

          49.png

          紅線是輔助參考線。

          <canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 豎向的輔助線(參考線,在畫布中間)
            cxt.moveTo(200, 0)
            cxt.lineTo(200, 400)
            cxt.strokeStyle='red'
            cxt.stroke()
          
            cxt.font='30px Arial'
          
            // 橫坐標(biāo)開始位對(duì)齊
            cxt.textAlign='start' // 默認(rèn)值,
            cxt.fillText('雷猴 start', 200, 40)
          
            // 橫坐標(biāo)結(jié)束位對(duì)齊
            cxt.textAlign='end' // 結(jié)束對(duì)齊
            cxt.fillText('雷猴 end', 200, 100)
          
            // 左對(duì)齊
            cxt.textAlign='left' // 左對(duì)齊
            cxt.fillText('雷猴 left', 200, 160)
          
            // 右對(duì)齊
            cxt.textAlign='right' // 右對(duì)齊
            cxt.fillText('雷猴 right', 200, 220)
          
            // 居中對(duì)齊
            cxt.textAlign='center' // 右對(duì)齊
            cxt.fillText('雷猴 center', 200, 280)
          </script>
          復(fù)制代碼
          

          從上面的例子看,startleft 的效果好像是一樣的,endright 也好像是一樣的。

          在大多數(shù)情況下,它們的確一樣。但在某些國(guó)家或者某些場(chǎng)合,閱讀文字的習(xí)慣是 從右往左 時(shí),start 就和 right 一樣了,endleft 也一樣。這是需要注意的地方。

          垂直對(duì)齊方式 textBaseline

          使用 textBaseline 屬性可以設(shè)置文字的垂直對(duì)齊方式。

          在使用 textBaseline 前,需要自行了解 css 的文本基線。

          50.png

          用一張網(wǎng)圖解釋一下基線

          textBaseline 可選屬性:

          • alphabetic: 默認(rèn)。文本基線是普通的字母基線。
          • top: 文本基線是 em 方框的頂端。
          • bottom: 文本基線是 em 方框的底端。
          • middle: 文本基線是 em 方框的正中。
          • hanging: 文本基線是懸掛基線。

          51.png

          紅線是輔助參考線。

          <canvas id="c" width="800" height="300" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 橫向的輔助線(參考線,在畫布中間)
            cxt.moveTo(0, 150)
            cxt.lineTo(800, 150)
            cxt.strokeStyle='red'
            cxt.stroke()
          
            cxt.font='20px Arial'
          
            // 默認(rèn) alphabetic
            cxt.textBaseline='alphabetic'
            cxt.fillText('雷猴 alphabetic', 10, 150)
          
            // 默認(rèn) top
            cxt.textBaseline='top'
            cxt.fillText('雷猴 top', 200, 150)
          
            // 默認(rèn) bottom
            cxt.textBaseline='bottom'
            cxt.fillText('雷猴 bottom', 320, 150)
          
            // 默認(rèn) middle
            cxt.textBaseline='middle'
            cxt.fillText('雷猴 middle', 480, 150)
          
            // 默認(rèn) hanging
            cxt.textBaseline='hanging'
            cxt.fillText('雷猴 hanging', 640, 150)
          </script>
          復(fù)制代碼
          

          注意:在繪制文字的時(shí)候,默認(rèn)是以文字的左下角作為參考點(diǎn)進(jìn)行繪制

          圖片

          Canvas 中可以使用 drawImage() 方法繪制圖片。

          渲染圖片

          渲染圖片的方式有2中,一種是在JS里加載圖片再渲染,另一種是把DOM里的圖片拿到 canvas 里渲染。

          渲染的語法:

          drawImage(image, dx, dy)
          復(fù)制代碼
          
          • image: 要渲染的圖片對(duì)象。
          • dx: 圖片左上角的橫坐標(biāo)位置。
          • dy: 圖片左上角的縱坐標(biāo)位置。

          JS版

          JS 里加載圖片并渲染,有以下幾個(gè)步驟:

          1. 創(chuàng)建 Image 對(duì)象
          2. 引入圖片
          3. 等待圖片加載完成
          4. 使用 drawImage() 方法渲染圖片

          52.png

          <canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            // 1 創(chuàng)建 Image 對(duì)象
            const image=new Image()
          
            // 2 引入圖片
            image.src='./images/dog.jpg'
          
            // 3 等待圖片加載完成
            image.onload=()=> {
              // 4 使用 drawImage() 方法渲染圖片
              cxt.drawImage(image, 30, 30)
            }
          </script>
          復(fù)制代碼
          

          DOM版

          53.png

          <style>
            #dogImg {
              display: none;
            }
          </style>
          
          <img src="./images/dog.jpg" id="dogImg"/>
          <canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            const image=document.getElementById('dogImg')
          
            cxt.drawImage(image, 70, 70)
          </script>
          復(fù)制代碼
          

          因?yàn)閳D片是從 DOM 里獲取到的,所以一般來說,只要在 window.onload 這個(gè)生命周期內(nèi)使用 drawImage 都可以正常渲染圖片。

          本例使用了 css 的方式,把圖片的 display 設(shè)置成 none 。因?yàn)槲也幌氡?<img> 影響到本例講解。

          實(shí)際開發(fā)過程中按照實(shí)際情況設(shè)置即可。

          設(shè)置圖片寬高

          前面的例子都是直接加載圖片,圖片默認(rèn)的寬高是多少就加載多少。

          如果需要指定圖片寬高,可以在前面的基礎(chǔ)上再添加兩個(gè)參數(shù):

          drawImage(image, dx, dy, dw, dh)
          復(fù)制代碼
          

          image、 dx、 dy 的用法和前面一樣。

          dw 用來定義圖片的寬度,dy 定義圖片的高度。

          54.png

          <canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            const image=new Image()
            image.src='./images/dog.jpg'
          
            image.onload=()=> {
              cxt.drawImage(image, 30, 30, 100, 100)
            }
          </script>
          復(fù)制代碼
          

          我把圖片的尺寸設(shè)為 100px * 100px,圖片看上去比之前就小了很多。

          截取圖片

          截圖圖片同樣使用drawImage() 方法,只不過傳入的參數(shù)數(shù)量比之前都多,而且順序也有點(diǎn)不一樣了。

          drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
          復(fù)制代碼
          

          以上參數(shù)缺一不可

          • image: 圖片對(duì)象
          • sx: 開始截取的橫坐標(biāo)
          • sy: 開始截取的縱坐標(biāo)
          • sw: 截取的寬度
          • sh: 截取的高度
          • dx: 圖片左上角的橫坐標(biāo)位置
          • dy: 圖片左上角的縱坐標(biāo)位置
          • dw: 圖片寬度
          • dh: 圖片高度

          55.png

          <canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>
          
          <script>
            const cnv=document.getElementById('c')
            const cxt=cnv.getContext('2d')
          
            const image=new Image()
            image.src='./images/dog.jpg'
          
            image.onload=()=> {
              cxt.drawImage(image, 0, 0, 100, 100, 30, 30, 200, 200)
            }
          </script>
          復(fù)制代碼
          

          總結(jié)

          本文主要講解了在 Canvas 中繪制一些基礎(chǔ)圖形,還有一些基礎(chǔ)樣式設(shè)置。

          還有更多高級(jí)的玩法會(huì)在之后的文章中講到,比如漸變、投影、濾鏡等等。

          德育處主任,https://juejin.cn/post/7116784455561248775


          主站蜘蛛池模板: 少妇人妻精品一区二区三区| 亚洲国产一区视频| 免费无码AV一区二区| 日本一道一区二区免费看| 国产亚洲综合精品一区二区三区| 国产丝袜视频一区二区三区| 国产一区二区不卡老阿姨| 国产一区二区三区乱码| 日韩视频免费一区二区三区| 日韩爆乳一区二区无码| 中文乱码精品一区二区三区| 国产一区高清视频| 91久久精品无码一区二区毛片| 无码AV一区二区三区无码| 日韩美女视频一区| 国产一区在线播放| 一区二区三区久久精品| 久久久久人妻精品一区三寸| 久久毛片一区二区| 久热国产精品视频一区二区三区 | 农村人乱弄一区二区| 精品人妻少妇一区二区三区在线| 午夜无码一区二区三区在线观看 | 色窝窝无码一区二区三区成人网站 | 一区二区高清视频在线观看| 日本免费一区二区三区最新vr| 无码少妇一区二区浪潮免费| 久久久一区二区三区| 精品永久久福利一区二区| 精品福利一区二区三| 国产精品成人国产乱一区| 制服丝袜一区二区三区| 中文字幕人妻AV一区二区| 精品视频一区二区三区在线播放| 无码人妻精品一区二区蜜桃AV| 国产综合一区二区在线观看| 国产一区二区三区在线2021 | 久久免费区一区二区三波多野| 在线精品日韩一区二区三区| 人妻无码一区二区三区| 无码国产精品一区二区免费式影视|