天天看點

canvas基礎[一]探究出國中數學知識

canvas基礎[一]探究出國中數學知識

何時用SVG何時用canvas

SVG

矢量圖,視覺清晰,檔案小

<svg viewBox="0 0 100 100">
  
  <circle cx="50" cy="50" r="50" />
  
  <style>
    circle { fill: blue; animation: pulse 2s alternate infinite; }
    @keyframes pulse {
      100% {
        r: 30;
      }
    }
  </style>
  
  <script>
    document.querySelector('circle').addEventListener('click', e => {
      e.target.style.fill = "red";
    });
  </script>
  
</svg>
           

關鍵可以放在一起玩

Canvas

是javascript繪圖API

大佬提出來的想法是:

SVG是預設選擇,畫布是備份,簡單的說當你不能使用SVG時候才使用canvas

canvas 元素

參考資料

<canvas id="tutorial" width="150" height="150"></canvas>
           

渲染上下文

var canvas = document.getElementById('tutorial');
var ctx = canvas.getContext('2d');
           

編寫一個基本骨架

<style>
    #canvas{
      border:1px solid #ccc;
    }
  </style>
  <canvas id="canvas" width="150" height="150"></canvas>
           

繪制矩形

fillRect(x, y, width, height)

繪制一個填充的矩形

strokeRect(x, y, width, height)

繪制一個矩形的邊框

clearRect(x, y, width, height)

清除指定矩形區域,讓清除部分完全透明。

案例

canvas基礎[一]探究出國中數學知識
let canvas = document.querySelector('#canvas')
  let ctx = canvas.getContext('2d')
  // 填充
  ctx.fillRect(10,10,80,80)
  // 删除部分
  ctx.clearRect(10,10,20,20)
  // 填充邊框的矩形
  ctx.strokeRect(10,10,10,10)
           

繪制路徑

  • 建立路徑起始點
  • 畫圖指令繪制路徑
  • 路徑閉合
  • 路徑生成後,通過描邊或填充路徑來渲染圖形

deginpath()

建立一條路徑

closePath()

閉合路徑

stroke()

通過線條繪制圖形輪廓

fill()

通過填充路徑繪制成實心的圖形

案例

繪制一個三角形
let ctx = canvas.getContext('2d')
  ctx.beginPath()
  ctx.moveTo(50, 50)// 點
  ctx.lineTo(50, 100)// 直線
  ctx.lineTo(130, 100)// 
  ctx.fill()
           
canvas基礎[一]探究出國中數學知識

MoveTo(x,y)

将筆觸移動到指定的坐标x以及y上

lineTo(x, y)

繪制一條從目前位置到指定x以及y位置的直線。

// 描邊三角形
  ctx.beginPath()
  ctx.moveTo(50, 50)// 點
  ctx.lineTo(50, 100)// 直線
  ctx.lineTo(130, 100)//
  ctx.closePath()
  ctx.stroke()
           
canvas基礎[一]探究出國中數學知識

lineWidth

行寬

strokeStyle

邊框的顔色

ctx.lineWidth=5
ctx.strokeStyle='red'
           

lineCap

線頭

  • butt 預設
  • round 半圓形
  • square 移動到末端
canvas基礎[一]探究出國中數學知識
context.lineCap = 'butt';     
	 context.lineCap = 'round';
	 context.lineCap = 'square';
           

lineJoin

線連接配接

  • bevel 斜角
  • round 圓角
  • square 預設
canvas基礎[一]探究出國中數學知識
ctx.lineJoin = "bevel";
ctx.lineJoin = "round";
預設  "square"
           

圓弧

arc()

度數轉為弧度公式

度數*Math.PI/180

方法

arc(x,y,radius,startAngle,endAngle,direction)

畫一個以

(x,y)

為圓心的以

radius

為半徑的圓弧(圓),從

startAngle

開始到

endAngle

結束,

direction

方向

true

順時針,

false

逆時針,預設順時針

true

canvas基礎[一]探究出國中數學知識

用弧度畫一個圓

let ctx = canvas.getContext('2d')
  ctx.beginPath()
  ctx.moveTo(250, 250)// 點
  ctx.arc(250,250,100,0,2 * Math.PI,)
  ctx.closePath()
  ctx.stroke()
           
canvas基礎[一]探究出國中數學知識

我們要記住開始的弧度和結束的弧度記住上面的公式,一個圓是

2*Math.PI

是以半圓是

Math.PI

ctx.arc(250,250,100,1/3*Math.PI,2 * Math.PI,)
           

開始位置是1/3,結束位置是終點位置

canvas基礎[一]探究出國中數學知識

arcTo

arcTo(x1,y1,x2,y2,radius)

畫曲線,要想明白它們之間的關系需要畫輔助線

let x0 = 100,
    y0 = 100,
    x1 = 400,
    y1 = 100,
    x2 = 350,
    y2 = 150;

  ctx.beginPath();
  ctx.moveTo(x0, y0);
  ctx.strokeStyle = "#f00";
  ctx.lineWidth = 2;
  ctx.arcTo(x1, y1, x2, y2, 20);
  ctx.stroke();

  ctx.beginPath();
  ctx.strokeStyle = "rgba(0,0,0,0.5)";
  ctx.lineWidth = 1;
  ctx.moveTo(x0, y0);
  ctx.lineTo(x1, y1);
  ctx.fillText('x1,y1', x1 + 10, y1 + 10)
  ctx.lineTo(x2, y2);
  ctx.fillText('x2,y2', x2 + 10, y2)
  ctx.stroke();
           
canvas基礎[一]探究出國中數學知識

說明一下,

x0,y0

起點坐标,

x1,y1

第一個點坐标,

x2,y2

第二個坐标

arcTo

的規律: 他其實是通過起點,第1點,第2點的兩條直線,組成了一個夾角,而這兩條線,也是參數圓的切線。其中圓的半徑決定了圓會在什麼位置與線條發生切邊。

讓我們把球球變大吧!

ctx.arcTo(x1,y1,x2,y2,50)

; //半徑改成50

canvas基礎[一]探究出國中數學知識

我們發現他們還是相切的,因為切線可以無限延長

為了友善計算,我先把兩條線的夾角改成90度。

var x0=100,

y0=400,

x1 = 500,

y1 = 400,

x2 = 500,

y2 = 450;

更改後就是90度張開了喲!我們保持球的半徑不變。重新整理後:

canvas基礎[一]探究出國中數學知識

我們把y2變大,也就是延長了一條切線,把他變成550,重新整理後:

canvas基礎[一]探究出國中數學知識

切線是延長了,但arcTo畫出的紅線沒有任何變化。

寫一個可行的案例吧

  1. 繪制一個背景網格
    // 繪制網格 grid
      for (let x = 0.5; x < 500; x += 10) {
        ctx.moveTo(x, 0);
        ctx.lineTo(x, 500)
      }
      for (let y = 0; y < 500; y += 10) {
        ctx.moveTo(0, y)
        ctx.lineTo(500, y)
      }
      ctx.strokeStyle = '#eee';
      ctx.stroke();
               
  2. 畫兩條直線相交
    // lines
      ctx.strokeStyle = 'gray';
      ctx.lineWidth = 1;
      ctx.beginPath()
      ctx.moveTo(51, 24)
      ctx.lineTo(314, 540)
      ctx.moveTo(477, 34)
      ctx.lineTo(86, 484)
      ctx.stroke();
               
  3. 繪制兩條線上的點

    問題來了兩點确定一條直線怎麼知道線上的點的位置關系

    兩點式公式

    (y-y2)/(y1-y2) = (x-x2)/(x1-x2)

    canvas基礎[一]探究出國中數學知識
  4. 求兩條直線上面的交點
    canvas基礎[一]探究出國中數學知識
    function segmentsIntr(a, b, c, d){
    
      //線段ab的法線N1
      let nx1 = (b.y - a.y), ny1 = (a.x - b.x);
    
      //線段cd的法線N2
      let nx2 = (d.y - c.y), ny2 = (c.x - d.x);
    
      //兩條法線做叉乘, 如果結果為0, 說明線段ab和線段cd平行或共線,不相交
      let denominator = nx1*ny2 - ny1*nx2;
      if (denominator==0) {
        return false;
      }
    
      //在法線N2上的投影
      let distC_N2=nx2 * c.x + ny2 * c.y;
      let distA_N2=nx2 * a.x + ny2 * a.y-distC_N2;
      let distB_N2=nx2 * b.x + ny2 * b.y-distC_N2;
    
      // 點a投影和點b投影在點c投影同側 (對點線上段上的情況,本例當作不相交處理);
      if ( distA_N2*distB_N2>=0  ) {
        return false;
      }
    
      //
      //判斷點c點d 和線段ab的關系, 原理同上
      //
      //在法線N1上的投影
      let distA_N1=nx1 * a.x + ny1 * a.y;
      let distC_N1=nx1 * c.x + ny1 * c.y-distA_N1;
      let distD_N1=nx1 * d.x + ny1 * d.y-distA_N1;
      if ( distC_N1*distD_N1>=0  ) {
        return false;
      }
    
      //計算交點坐标
      let fraction= distA_N2 / denominator;
      let dx= fraction * ny1,
        dy= -fraction * nx1;
      return { x: a.x + dx , y: a.y + dy };
    }
    
    console.log(segmentsIntr({x: 51, y: 24}, {x: 314, y: 540}, {x: 477, y: 34}, {x: 86, y: 484}));
               
    上demo代碼
    // 兩點式公式
      // (y-y2)/(y1-y2) = (x-x2)/(x1-x2)。
      // 我們設y=200,可以求出x=140.7
      ctx.beginPath()
      ctx.moveTo(140.7,200)
      ctx.arc(140.7,200,5,0,2*Math.PI)
      // 設x=350,求右邊直線的y點 180.16
      ctx.moveTo(350,180.16)
      ctx.arc(350,180.16,5,0,2*Math.PI)
      // 求原點坐标
      ctx.moveTo(211.713,339.3166)
      ctx.arc(211.713,339.3166,5,0,2*Math.PI)
      ctx.fillStyle = 'red';
      ctx.fill();
               
    canvas基礎[一]探究出國中數學知識
  5. 标記點的位置
    ctx.font='14px Arial'
      ctx.beginPath()
      ctx.fillText("(x0,y0)",140.7+5,200+5)
      ctx.fillText("(x1,y1)",350+5,180.16+5)
      ctx.fillText("(x2,y2)",211.713+5,339.3166+5)
               
  6. arcTo

    曲線
    // 編寫arcTo
      ctx.beginPath()
      ctx.lineWidth=3;
      ctx.moveTo(140.7,200)
      ctx.arcTo(211.713,339.3166,350,180.16,100)
      ctx.stroke()
               
    canvas基礎[一]探究出國中數學知識
  7. 問題又來了,我該怎麼求這個切點的坐标呢

    唉,我這種菜雞都忘記啦...

    我想出來的方法手動移動,我就不寫了,都忘光了

    全部代碼集合

    let canvas = document.querySelector('#canvas')
      let ctx = canvas.getContext('2d');
      // 繪制網格 grid
      for (let x = 0.5; x < 500; x += 10) {
        ctx.moveTo(x, 0);
        ctx.lineTo(x, 500)
      }
      for (let y = 0; y < 500; y += 10) {
        ctx.moveTo(0, y)
        ctx.lineTo(500, y)
      }
      ctx.strokeStyle = '#eee';
      ctx.stroke();
      // lines
      ctx.strokeStyle = 'gray';
      ctx.lineWidth = 1;
      ctx.beginPath()
      ctx.moveTo(51, 24)
      ctx.lineTo(314, 540)
      // k=(y2-y1)/(x2-x1)
    
      ctx.moveTo(477, 34)
      ctx.lineTo(86, 484)
      ctx.stroke();
      // 原點
      // 問題來了兩點确定一條直線怎麼知道線上的點的位置關系
      // 兩點式公式
      // (y-y2)/(y1-y2) = (x-x2)/(x1-x2)。
      // 我們設y=200,可以求出x=140.7
      ctx.beginPath()
      ctx.moveTo(140.7,200)
      ctx.arc(140.7,200,5,0,2*Math.PI)
      // 設x=350,求右邊直線的y點 180.16
      ctx.moveTo(350,180.16)
      ctx.arc(350,180.16,5,0,2*Math.PI)
      // 求原點坐标
      ctx.moveTo(211.713,339.3166)
      ctx.arc(211.713,339.3166,5,0,2*Math.PI)
      ctx.fillStyle = 'red';
      ctx.fill();
      // 标記點的坐标
      ctx.font='14px Arial'
      ctx.beginPath()
      ctx.fillText("(x0,y0)",140.7+5,200+5)
      ctx.fillText("(x1,y1)",211.713+5,339.3166+5)
      ctx.fillText("(x2,y2)",350+5,180.16+5)
      // 編寫arcTo
      ctx.beginPath()
      ctx.lineWidth=3;
      ctx.moveTo(140.7,200)
      ctx.arcTo(211.713,339.3166,350,180.16,100)
      ctx.stroke()
               

    See the Pen ExyOEBr by 973782523

    (@973782523) on CodePen.

    這種輔助線有點複雜.那我們可以用簡單點的直線輔助線

    相信大家已經很熟練了,直接上代碼吧

    ctx.strokeStyle = '#eee';
      ctx.stroke();
      // lines
      ctx.strokeStyle = 'gray';
      ctx.lineWidth = 1;
      ctx.beginPath()
      ctx.moveTo(81, 24)
      ctx.lineTo(81, 400)
      ctx.moveTo(400, 300)
      ctx.lineTo(40, 300)
      ctx.stroke();
      // 原點
      ctx.beginPath()
      ctx.moveTo(81, 200)
      ctx.arc(81, 200, 5, 0, 2 * Math.PI)
    
      ctx.moveTo(220, 300)
      ctx.arc(220, 300, 5, 0, 2 * Math.PI)
      // 求原點坐标
      ctx.moveTo(81, 300)
      ctx.arc(81, 300, 5, 0, 2 * Math.PI)
      ctx.fillStyle = 'red';
      ctx.fill();
      // 标記點的坐标
      ctx.font = '14px Arial'
      ctx.beginPath()
      ctx.fillText("(x0,y0)", 81 + 5, 200 + 5)
      ctx.fillText("(x1,y1)", 81 + 5, 300 + 5)
      ctx.fillText("(x2,y2)", 220 + 5, 300 + 5)
      // 編寫arcTo
      ctx.beginPath()
      ctx.lineWidth = 3;
      ctx.moveTo(81, 200)
      ctx.arcTo(81, 300, 220, 300, 100)
      ctx.stroke()
               
    canvas基礎[一]探究出國中數學知識