canvas基礎[一]探究出初中數學知識
阿新 • • 發佈:2020-11-11
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201111132557114-2012236835.png)
# 何時用SVG何時用canvas
## SVG
向量圖,視覺清晰,檔案小
```js
```
關鍵可以放在一起玩
## Canvas
是javascript繪圖API
大佬提出來的想法是:
**SVG是預設選擇,畫布是備份,簡單的說當你不能使用SVG時候才使用canvas**
# canvas 元素
[參考資料](https://www.w3resource.com/html5-canvas/html5-canvas-lines.php)
```js
```
## 渲染上下文
```js
var canvas = document.getElementById('tutorial');
var ctx = canvas.getContext('2d');
```
編寫一個基本骨架
```js
```
## 繪製矩形
`fillRect(x, y, width, height)`
繪製一個填充的矩形
`strokeRect(x, y, width, height)`
繪製一個矩形的邊框
`clearRect(x, y, width, height)`
清除指定矩形區域,讓清除部分完全透明。
案例
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201110154610546-2055945596.png)
```js
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()`
通過填充路徑繪製成實心的圖形
案例
```js
繪製一個三角形
let ctx = canvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(50, 50)// 點
ctx.lineTo(50, 100)// 直線
ctx.lineTo(130, 100)//
ctx.fill()
```
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201110155832783-1412058091.png)
`MoveTo(x,y)`
將筆觸移動到指定的座標x以及y上
`lineTo(x, y)`
繪製一條從當前位置到指定x以及y位置的直線。
```js
// 描邊三角形
ctx.beginPath()
ctx.moveTo(50, 50)// 點
ctx.lineTo(50, 100)// 直線
ctx.lineTo(130, 100)//
ctx.closePath()
ctx.stroke()
```
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201110160408674-975175414.png)
`lineWidth` 行寬
`strokeStyle` 邊框的顏色
```js
ctx.lineWidth=5
ctx.strokeStyle='red'
```
`lineCap` 線頭
* butt 預設
* round 半圓形
* square 移動到末端
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201110163305240-174875056.png)
```js
context.lineCap = 'butt';
context.lineCap = 'round';
context.lineCap = 'square';
```
`lineJoin` 線連線
* bevel 斜角
* round 圓角
* square 預設
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201110163832635-606510118.png)
```js
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`
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201110164624810-1711242104.png)
用弧度畫一個圓
```js
let ctx = canvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(250, 250)// 點
ctx.arc(250,250,100,0,2 * Math.PI,)
ctx.closePath()
ctx.stroke()
```
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201110170210241-304859466.png)
我們要記住開始的弧度和結束的弧度記住上面的公式,一個圓是`2*Math.PI`
所以半圓是`Math.PI`
```js
ctx.arc(250,250,100,1/3*Math.PI,2 * Math.PI,)
```
開始位置是1/3,結束位置是終點位置
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201110170841356-36409504.png)
## arcTo
`arcTo(x1,y1,x2,y2,radius)`
畫曲線,要想明白它們之間的關係需要畫輔助線
```js
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();
```
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201110183447674-1657795950.png)
說明一下,`x0,y0` 起點座標,`x1,y1` 第一個點座標,`x2,y2` 第二個座標
`arcTo`的規律: 他其實是通過起點,第1點,第2點的兩條直線,組成了一個夾角,而這兩條線,也是引數圓的**切線**。其中圓的半徑決定了圓會在什麼位置與線條發生切邊。
讓我們把球球變大吧!
`ctx.arcTo(x1,y1,x2,y2,50)`; //半徑改成50
![canvas arcTo](http://ww3.sinaimg.cn/mw690/620b4e99tw1dzk4ogsd49j.jpg)
我們發現他們還是相切的,因為切線可以無限延長
為了方便計算,我先把兩條線的夾角改成90度。
var x0=100,
y0=400,
x1 = 500,
y1 = 400,
x2 = 500,
y2 = 450;
更改後就是90度張開了喲!我們保持球的半徑不變。重新整理後:
![canvas arcTo](http://ww2.sinaimg.cn/mw690/620b4e99jw1dzk6acv0f7j.jpg)
我們把y2變大,也就是延長了一條切線,把他變成550,重新整理後:
![canvas arcTo](http://ww4.sinaimg.cn/mw690/620b4e99jw1dzk6adh3mxj.jpg)
切線是延長了,但arcTo畫出的紅線沒有任何變化。
寫一個可行的案例吧
1. 繪製一個背景網格
```js
// 繪製網格 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. 畫兩條直線相交
```js
// 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)`
>
> ![img](https://bkimg.cdn.bcebos.com/formula/e700500f8ed4190729a6a19c70b70e8f.svg)
4. 求兩條直線上面的交點
> ![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201111104521632-249710393.png)
>
> ```js
> 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程式碼
```js
// 兩點式公式
// (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();
```
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201111104700130-1793448343.png)
5. 標記點的位置
```js
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` 曲線
```js
// 編寫arcTo
ctx.beginPath()
ctx.lineWidth=3;
ctx.moveTo(140.7,200)
ctx.arcTo(211.713,339.3166,350,180.16,100)
ctx.stroke()
```
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201111111230954-1023792098.png)
7. 問題又來了,我該怎麼求這個切點的座標呢
唉,我這種菜雞都忘記啦...
我想出來的方法手動移動,我就不寫了,都忘光了
全部程式碼集合
```js
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()
```
這種輔助線有點複雜.那我們可以用簡單點的直線輔助線
相信大家已經很熟練了,直接上程式碼吧
```js
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()
```
![](https://img2020.cnblogs.com/blog/1308525/202011/1308525-20201111131719709-1160251418.png)