Canvas 從入門到勸朋友放棄(圖解版)?

本文簡介

點贊 + 關注 + 收藏 = 學會了


在前端領域世分,如果只是懂 Vue 或者 React ,未來在職場的競爭力可能會比較弱狭魂。

根據(jù)我多年在家待業(yè)經(jīng)驗來看罚攀,前端未來在 數(shù)據(jù)可視化AI 這兩個領域會比較香,而 Canvas 是數(shù)據(jù)可視化在前端方面的基礎技術雌澄。

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

file

要入門一個技術杯瞻,前期最重要是快镐牺!所以本文只講入門內(nèi)容,能應付簡單項目魁莉。深入的知識點會在其他文章講解睬涧。



Canvas 是什么?

  • Canvas 中文名叫 “畫布”旗唁,是 HTML5 新增的一個標簽畦浓。
  • Canvas 允許開發(fā)者通過 JS在這個標簽上繪制各種圖案。
  • Canvas 擁有多種繪制路徑检疫、矩形讶请、圓形、字符以及圖片的方法屎媳。
  • Canvas 在某些情況下可以 “代替” 圖片夺溢。
  • Canvas 可用于動畫论巍、游戲、數(shù)據(jù)可視化风响、圖片編輯器嘉汰、實時視頻處理等領域。



Canvas 和 SVG 的區(qū)別

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

就上面的描述而言可能有點難懂鞋怀,你可以打開 AntV 旗下的圖形編輯引擎做對比。G6 是使用 canvas 開發(fā)的持搜,X6 是使用 svg 開發(fā)的密似。


我的建議是:如果要展示的數(shù)據(jù)量比較大,比如一條數(shù)據(jù)就是一個元素節(jié)點朵诫,那使用 canvas 會比較合適辛友;如果用戶操作的交互比較多,而且對清晰度有要求(矢量圖)剪返,那么使用 svg 會比較合適废累。



起步

學習前端一定要動手敲代碼,然后看效果展示脱盲。

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


畫條直線

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


file
<!-- 1掖看、創(chuàng)建 canvas 元素 -->
<canvas
  id="c"
  width="300"
  height="200"
  style="border: 1px solid #ccc;"
></canvas>

<script>
  // 2、獲取 canvas 對象
  const cnv = document.getElementById('c')

  // 3面哥、獲取 canvas 上下文環(huán)境對象
  const cxt = cnv.getContext('2d')

  // 4哎壳、繪制圖形
  cxt.moveTo(100, 100) // 起點坐標 (x, y)
  cxt.lineTo(200, 100) // 終點坐標 (x, y)
  cxt.stroke() // 將起點和終點連接起來
</script>

moveTolineTostroke 方法暫時可以不用管尚卫,它們的作用是繪制圖形归榕,這些方法在后面會講到~


注意點

1、默認寬高

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

如果不在 canvas 上設置寬高吱涉,那 canvas 元素的默認寬度是300px刹泄,默認高度是150px。


2怎爵、設置 canvas 寬高

canvas 元素提供了 widthheight 兩個屬性特石,可設置它的寬高。

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

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


3、不能通過 CSS 設置畫布的寬高

使用 css 設置 canvas 的寬高乞旦,會出現(xiàn) 內(nèi)容被拉伸 的后果T裟隆!兰粉!

file
<style>
  #c {
    width: 400px;
    height: 400px;
    border: 1px solid #ccc;
  }
</style>

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

<script>
  // 1故痊、獲取canvas對象
  const cnv = document.getElementById('c')

  // 2、獲取canvas上下文環(huán)境對象
  const cxt = cnv.getContext('2d')

  // 3玖姑、繪制圖形
  cxt.moveTo(100, 100) // 起點
  cxt.lineTo(200, 100) // 終點
  cxt.stroke() // 將起點和終點連接起來

  console.log(cnv.width) // 獲取 canvas 的寬度愕秫,輸出:300
  console.log(cnv.height) // 獲取 canvas 的高度,輸出:150
</script>

canvas 的默認寬度是300px焰络,默認高度是150px戴甩。

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

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


4、線條默認寬度和顏色

線條的默認寬度是 1px 描馅,默認顏色是黑色遭居。

但由于默認情況下 canvas 會將線條的中心點和像素的底部對齊茫舶,所以會導致顯示效果是 2px 和非純黑色問題氛驮。


5箭窜、IE兼容性高

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

如需兼容 IE 7 和 8 岂膳,可以使用 ExplorerCanvas 。但即使是使用了 ExplorerCanvas 仍然會有所限制磅网,比如無法使用 fillText() 方法等闷营。



基礎圖形

坐標系

在繪制基礎圖形之前,需要先搞清除 Canvas 使用的坐標系知市。

Canvas 使用的是 W3C 坐標系 ,也就是遵循我們屏幕速蕊、報紙的閱讀習慣嫂丙,從上往下,從左往右规哲。

file

W3C 坐標系數(shù)學直角坐標系X軸 是一樣的跟啤,只是 Y軸 的反向相反。

W3C 坐標系Y軸 正方向向下。


直線

一條直線

最簡單的起步方式是畫一條直線隅肥。這里所說的 “直線” 是幾何學里的 “線段” 的意思竿奏。

需要用到這3個方法:

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


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

file
<canvas id="c" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const cxt = cnv.getContext('2d')

  // 繪制直線
  cxt.moveTo(50, 100) // 起點坐標
  cxt.lineTo(200, 50) // 下一個點的坐標
  cxt.stroke() // 將上面的坐標用一條線連接起來
</script>

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

file


多條直線

如需畫多條直線秃症,可以用會上面那幾個方法候址。

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


仔細觀察一下,為什么兩條線的粗細不一樣的种柑?

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

答:默認情況下 canvas 會將線條的中心點和像素的底部對齊聚请,所以會導致顯示效果是 2px 和非純黑色問題荠雕。

file

上圖每個格子代表 1px

線的中心點會和畫布像素點的底部對齊驶赏,所以會線中間是黑色的炸卑,但由于一個像素就不能再切割了,所以會有半個像素被染色母市,就變成了淺灰色矾兜。

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


設置樣式

  • lineWidth:線的粗細
  • strokeStyle:線的顏色
  • lineCap:線帽:默認: butt; 圓形: round; 方形: square


file
<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' // 默認: butt; 圓形: round; 方形: square

  cxt.stroke()
</script>


新開路徑

開辟新路徑的方法:

  • beginPath()


在繪制多條線段的同時椅寺,還要設置線段樣式,通常需要開辟新路徑蒋失。

要不然樣式之間會相互污染返帕。

比如這樣

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


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

  1. 使用 beginPath() 方法篙挽,重新開一個路徑
  2. 設置新線段的樣式(必須項)


如果上面2步卻了其中1步都會有影響荆萤。

只使用 beginPath()

file
<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() // 重新開啟一個路徑
  cxt.moveTo(20, 120.5)
  cxt.lineTo(200, 120.5)
  cxt.stroke()
</script>

第一條線的樣式會影響之后的線。

但如果使用了 beginPath() 铣卡,后面的線段不會影響前面的線段链韭。

file
<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() // 重新開啟一個路徑
  cxt.moveTo(20, 120.5)
  cxt.lineTo(200, 120.5)
  cxt.lineWidth = 4
  cxt.strokeStyle = 'red'
  cxt.stroke()
</script>


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

這個情況會反過來煮落,后面的線能影響前面的線敞峭。

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


正確的做法

在設置 beginPath() 的同時,也各自設置樣式蝉仇。這樣就能做到相互不影響了旋讹。

file
<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() // 重新開啟一個路徑
  cxt.moveTo(20, 120.5)
  cxt.lineTo(200, 120.5)
  cxt.lineWidth = 4
  cxt.strokeStyle = 'red'
  cxt.stroke()
</script>


折線

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

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

畫這種折線睦疫,最好在草稿紙上畫一個坐標系,自己計算并描繪一下每個點大概在什么什么位置鞭呕,最后在 canvas 中看看效果蛤育。


矩形

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


使用線段描繪矩形

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

file
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() 方法進行閉合进宝,推薦使用 closePath()

 cxt.stroke()
</script>


上面的代碼幾個點分別對應下圖刻坊。

file


使用 strokeRect() 描邊矩形

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


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

上面的代碼可以這樣理解

file


使用 fillRect() 填充矩形

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

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

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


同時使用 strokeRect()fillRect()

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

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


使用 rect() 生成矩形

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

strokeRect()fillRect() 這兩個方法調用后會立即繪制;rect() 方法被調用后瞧掺,不會立刻繪制矩形耕餐,而是需要調用 stroke()fill() 輔助渲染。


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


等價公式:

cxt.strokeStyle = 'red',
cxt.rect(50, 50, 200, 100)
cxt.stroke()

// 等價于
cxt.strokeStyle = 'red'
cxt.strokerect(50, 50, 200, 100)


// -----------------------------


cxt.fillStyle = 'hotpink'
cxt.rect(50, 50, 200, 100)
cxt.fill()

// 等價于
cxt.fillStyle = 'yellowgreen'
cxt.fillRect(50, 50, 200, 100)


使用 clearRect() 清空矩形

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

clearRect(x, y, width, height)

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


file
<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.fillRect(50, 50, 200, 200) // 填充矩形

  cxt.clearRect(60, 60, 180, 90) // 清空矩形
</script>


清空畫布

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

// 省略部分代碼

cxt.clearRect(0, 0, cnv.width, cnv.height)

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


多邊形

Canvas 要畫多邊形壹蔓,需要使用 moveTo() 亚隅、 lineTo()closePath()


三角形

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

需要確定三角形3個點的坐標位置,然后使用 stroke() 或者 fill() 方法生成三角形偏螺。


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

  // 注意點:如果使用 lineTo 閉合圖形行疏,是不能很好閉合拐角位的。
  cxt.lineTo(50, 50) // 閉合

  cxt.stroke()

</script>

注意套像,默認情況下不會自動從最后一個點連接到起點酿联。最后一步需要設置一下 cxt.lineTo(50, 50) ,讓它與 cxt.moveTo(50, 50) 一樣夺巩。這樣可以讓路徑回到起點贞让,形成一個閉合效果。

但這樣做其實是有點問題的柳譬,而且也比較麻煩喳张,要記住起始點坐標。

上面的閉合操作美澳,如果遇到設置了 lineWidth 或者 lineJoin 就會有問題销部,比如:

file
// 省略部分代碼
cxt.lineWidth = 20

當線段變粗后,起始點和結束點的鏈接處制跟,拐角就出現(xiàn)“不正尘俗”現(xiàn)象。


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

file
<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)
  // 手動閉合
  cxt.closePath()

  cxt.lineJoin = 'miter' // 線條連接的樣式。miter: 默認; bevel: 斜面; round: 圓角
  cxt.lineWidth = 20
  cxt.stroke()
</script>

使用 cxt.closePath() 可以自動將終點和起始點連接起來聊记,此時看上去就正常多了撒妈。


菱形

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

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


要繪制直線類型的圖形,在草稿紙上標記出起始點和每個拐角的點甥雕,然后再連線即可踩身。相對曲線圖形來說,直線圖形是比較容易的社露。


圓形

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

語法:

arc(x, y, r, sAngle, eAngle,counterclockwise)
  • xy: 圓心坐標
  • r: 半徑
  • sAngle: 開始角度
  • eAngle: 結束角度
  • counterclockwise: 繪制方向(true: 逆時針; false: 順時針)峭弟,默認 false


file

開始角度和結束角度附鸽,都是以弧度為單位。例如 180°就寫成 Math.PI 瞒瘸,360°寫成 Math.PI * 2 坷备,以此類推。

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

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


注意:繪制圓形之前,必須先調用 beginPath() 方法>癸M薰摺! 在繪制完成之后肥败,還需要調用 closePath() 方法V呵场!馒稍!


file
<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)
  cxt.closePath()

  cxt.stroke()
</script>


半圓

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

file
<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) // 順時針
  cxt.closePath()

  cxt.stroke()
</script>

上面的代碼中证膨,cxt.arc 最后一個參數(shù)沒傳,默認是 false 佛舱,所以是順時針繪制椎例。

file


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

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


弧線

使用 arc() 方法畫半圓時请祖,如果最后不調用 closePath() 方法订歪,就不會出現(xiàn)閉合路徑。也就是說肆捕,那是一條弧線刷晋。

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


arc() 畫弧線

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

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


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

file


arcTo() 畫弧線

arcTo() 的使用方法會更加復雜捏悬,如果初學看不太懂的話可以先跳過,看完后面的再回來補補润梯。

語法:

arcTo(cx, cy, x2, y2, radius)
  • cx: 兩切線交點的橫坐標
  • cy: 兩切線交點的縱坐標
  • x2: 結束點的橫坐標
  • y2: 結束點的縱坐標
  • radius: 半徑

其中过牙,(cx, cy) 也叫控制點,(x2, y2) 也叫結束點纺铭。

是不是有點奇怪寇钉,為什么沒有 x1y1

(x1, y1) 是開始點舶赔,通常是由 moveTo() 或者 lineTo() 提供扫倡。


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

file


舉個例子

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



基礎樣式

前面學完基礎圖形疚鲤,接下來可以開始了解一下如何設置元素的基礎樣式。


描邊 stroke()

前面的案例中征懈,其實已經(jīng)知道使用 stroke() 方法進行描邊了石咬。這里就不再多講這個方法。


線條寬度 lineWidth

lineWidth 默認值是 1 卖哎,默認單位是 px

語法:

lineWidth = 線寬


file
<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 // 設置線寬
  cxt.stroke()

  // 線寬 20
  cxt.beginPath()
  cxt.moveTo(50, 150)
  cxt.lineTo(250, 150)
  cxt.lineWidth = 20 // 設置線寬
  cxt.stroke()

  // 線寬 30
  cxt.beginPath()
  cxt.moveTo(50, 250)
  cxt.lineTo(250, 250)
  cxt.lineWidth = 30 // 設置線寬
  cxt.stroke()
</script>


線條顏色 strokeStyle

使用 strokeStyle 可以設置線條顏色

語法:

strokeStyle = 顏色值


file
<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' // 設置顏色
  cxt.stroke()
</script>

為了展示方便删性,我將 lineWidth 設為 20亏娜。


線帽 lineCap

線帽指的是線段的開始和結尾處的樣式,使用 lineCap 可以設置

語法:

lineCap = '屬性值'


屬性值包括:

  • butt: 默認值蹬挺,無線帽
  • square: 方形線帽
  • round: 圓形線帽


file
<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 = 16

  // 默認線帽 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>


使用 squareround 的話,會使線條變得稍微長一點點巴帮,這是給線條增加線帽的部分溯泣,這個長度在日常開發(fā)中需要注意。

線帽只對線條的開始和結尾處產(chǎn)生作用榕茧,對拐角不會產(chǎn)生任何作用垃沦。


拐角樣式 lineJoin

如果需要設置拐角樣式,可以使用 lineJoin 用押。

語法:

lineJoin = '屬性值'


屬性值包括:

  • miter: 默認值肢簿,尖角
  • round: 圓角
  • bevel: 斜角


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


虛線 setLineDash()

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

語法:

setLineDash([])

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


虛線分3種情況

  1. 只傳1個值
  2. 有2個值
  3. 有3個以上的值


file
<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個參數(shù)缎讼,實線與空白都是 10px
  cxt.stroke()


  cxt.beginPath()
  cxt.moveTo(50, 100)
  cxt.lineTo(200, 100)
  cxt.setLineDash([10, 20]) // 2個參數(shù)收夸,此時,實線是 10px, 空白 20px
  cxt.stroke()


  cxt.beginPath()
  cxt.moveTo(50, 150)
  cxt.lineTo(200, 150)
  cxt.setLineDash([10, 20, 5]) // 傳3個以上的參數(shù)血崭,此例:10px實線卧惜,20px空白,5px實線功氨,10px空白序苏,20px實線,5px空白 ……

  cxt.stroke()
</script>


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

cxt.lineDashOffset 設置虛線的偏移位。


填充

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


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


可以使用 fillStyle 設置填充顏色匈睁,默認是黑色监透。


非零環(huán)繞填充

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

在使用 moveTolineTo 描述圖形時胀蛮,如果是按順時針繪制,計數(shù)器會加1糯钙;如果是逆時針粪狼,計數(shù)器會減1。

當圖形所處的位置任岸,計數(shù)器的結果為0時再榄,它就不會被填充。


這樣說有點復雜享潜,先看看例子

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

請看看上面的代碼困鸥,我畫了2個矩形,它們都沒有用 beginPath() 方法開辟新路徑剑按。

file

內(nèi)層矩形是逆時針繪制的疾就,所以內(nèi)層的值是 -1 ,它又經(jīng)過外層矩形艺蝴,而外層矩形是順時針繪制猬腰,所以經(jīng)過外層時值 +1,最終內(nèi)層的值為 0 吴趴,所以不會被填充漆诽。



文本

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

為了方便演示锣枝,我們先了解一下在 Canvas 中如何給本文設置樣式厢拭。


樣式 font

CSS 設置 font 差不多,Canvas 也可以通過 font 設置樣式撇叁。

語法:

cxt.font = 'font-style font-variant font-weight font-size/line-height font-family'


如果需要設置字號 font-size供鸠,需要同事設置 font-family

cxt.font = '30px 宋體'


描邊 strokeText()

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

語法:

strokeText(text, x, y, maxWidth)
  • text: 字符串陨闹,要繪制的內(nèi)容
  • x: 橫坐標楞捂,文本左邊要對齊的坐標(默認左對齊)
  • y: 縱坐標,文本底邊要對齊的坐標
  • maxWidth: 可選參數(shù)趋厉,表示文本渲染的最大寬度(px)寨闹,如果文本超出 maxWidth 設置的值,文本會被壓縮君账。
file
<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' // 將字號設置成 60px繁堡,方便觀察
  cxt.strokeText('雷猴', 30, 90)
</script>


設置描邊顏色 strokeStyle

使用 strokeStyle 設置描邊顏色。


file
<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' // 將字號設置成 60px,方便觀察
  cxt.strokeStyle = 'pink' // 設置文本描邊顏色
  cxt.strokeText('雷猴', 30, 90)
</script>


填充 fillText

使用 fillText() 可填充文本椭蹄。

語法和 strokeText() 一樣闻牡。

fillText(text, x, y, maxWidth)


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


設置填充顏色 fillStyle

使用 fillStyle 可以設置文本填充顏色。


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


獲取文本長度 measureText()

measureText().width 方法可以獲取文本的長度绳矩,單位是 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>


水平對齊方式 textAlign

使用 textAlign 屬性可以設置文字的水平對齊方式,一共有5個值可選

  • start: 默認翼馆。在指定位置的橫坐標開始割以。
  • end: 在指定坐標的橫坐標結束。
  • left: 左對齊应媚。
  • right: 右對齊拳球。
  • center: 居中對齊。


file

紅線是輔助參考線珍特。

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

  // 橫坐標開始位對齊
  cxt.textAlign = 'start' // 默認值,
  cxt.fillText('雷猴 start', 200, 40)

  // 橫坐標結束位對齊
  cxt.textAlign = 'end' // 結束對齊
  cxt.fillText('雷猴 end', 200, 100)

  // 左對齊
  cxt.textAlign = 'left' // 左對齊
  cxt.fillText('雷猴 left', 200, 160)

  // 右對齊
  cxt.textAlign = 'right' // 右對齊
  cxt.fillText('雷猴 right', 200, 220)

  // 居中對齊
  cxt.textAlign = 'center' // 右對齊
  cxt.fillText('雷猴 center', 200, 280)
</script>


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

在大多數(shù)情況下酬姆,它們的確一樣嗜桌。但在某些國家或者某些場合,閱讀文字的習慣是 從右往左 時辞色,start 就和 right 一樣了骨宠,endleft 也一樣。這是需要注意的地方相满。


垂直對齊方式 textBaseline

使用 textBaseline 屬性可以設置文字的垂直對齊方式层亿。

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

file

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


textBaseline 可選屬性:

  • alphabetic: 默認匿又。文本基線是普通的字母基線。
  • top: 文本基線是 em 方框的頂端建蹄。
  • bottom: 文本基線是 em 方框的底端碌更。
  • middle: 文本基線是 em 方框的正中幻馁。
  • hanging: 文本基線是懸掛基線猪勇。


file

紅線是輔助參考線创南。

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

  // 默認 alphabetic
  cxt.textBaseline = 'alphabetic'
  cxt.fillText('雷猴 alphabetic', 10, 150)

  // 默認 top
  cxt.textBaseline = 'top'
  cxt.fillText('雷猴 top', 200, 150)

  // 默認 bottom
  cxt.textBaseline = 'bottom'
  cxt.fillText('雷猴 bottom', 320, 150)

  // 默認 middle
  cxt.textBaseline = 'middle'
  cxt.fillText('雷猴 middle', 480, 150)

  // 默認 hanging
  cxt.textBaseline = 'hanging'
  cxt.fillText('雷猴 hanging', 640, 150)
</script>


注意:在繪制文字的時候铲咨,默認是以文字的左下角作為參考點進行繪制



圖片

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


渲染圖片

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

渲染的語法:

drawImage(image, dx, dy)
  • image: 要渲染的圖片對象快压。
  • dx: 圖片左上角的橫坐標位置圆仔。
  • dy: 圖片左上角的縱坐標位置。


JS版

JS 里加載圖片并渲染蔫劣,有以下幾個步驟:

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


file
<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 對象
  const image = new Image()

  // 2 引入圖片
  image.src = './images/dog.jpg'

  // 3 等待圖片加載完成
  image.onload = () => {
    // 4 使用 drawImage() 方法渲染圖片
    cxt.drawImage(image, 30, 30)
  }
</script>


DOM版

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


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


本例使用了 css 的方式歪沃,把圖片的 display 設置成 none 。因為我不想被 <img> 影響到本例講解嫌松。

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


設置圖片寬高

前面的例子都是直接加載圖片,圖片默認的寬高是多少就加載多少萎羔。

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

drawImage(image, dx, dy, dw, dh)

image、 dx贾陷、 dy 的用法和前面一樣缘眶。

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


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

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


截取圖片

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

drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)

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

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


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



總結

本文主要講解了在 Canvas 中繪制一些基礎圖形冈爹,還有一些基礎樣式設置涌攻。

還有更多高級的玩法會在之后的文章中講到,比如漸變犯助、投影癣漆、濾鏡等等。



代碼倉庫

?雷猴 Canvas



推薦閱讀

??《Fabric.js 從入門到膨脹》

??《『Three.js』起飛剂买!》

??《console.log也能插圖;菟!瞬哼!》

??《純css實現(xiàn)117個Loading效果》

??《視差特效的原理和實現(xiàn)方法》

??《這18個網(wǎng)站能讓你的頁面背景炫酷起來》


點贊 + 關注 + 收藏 = 學會了
點贊 + 關注 + 收藏 = 學會了

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末婚肆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坐慰,更是在濱河造成了極大的恐慌较性,老刑警劉巖用僧,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赞咙,居然都是意外死亡责循,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門攀操,熙熙樓的掌柜王于貴愁眉苦臉地迎上來院仿,“玉大人,你說我怎么就攤上這事速和〈醯妫” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵颠放,是天一觀的道長排惨。 經(jīng)常有香客問我,道長碰凶,這世上最難降的妖魔是什么暮芭? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮欲低,結果婚禮上谴麦,老公的妹妹穿的比我還像新娘。我一直安慰自己伸头,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布舷蟀。 她就那樣靜靜地躺著恤磷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪野宜。 梳的紋絲不亂的頭發(fā)上扫步,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音匈子,去河邊找鬼河胎。 笑死,一個胖子當著我的面吹牛虎敦,可吹牛的內(nèi)容都是我干的游岳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼其徙,長吁一口氣:“原來是場噩夢啊……” “哼胚迫!你這毒婦竟也來了?” 一聲冷哼從身側響起唾那,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤访锻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體期犬,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡河哑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了龟虎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片璃谨。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖遣总,靈堂內(nèi)的尸體忽然破棺而出睬罗,到底是詐尸還是另有隱情,我是刑警寧澤旭斥,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布容达,位于F島的核電站,受9級特大地震影響垂券,放射性物質發(fā)生泄漏花盐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一菇爪、第九天 我趴在偏房一處隱蔽的房頂上張望算芯。 院中可真熱鬧,春花似錦凳宙、人聲如沸熙揍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽届囚。三九已至,卻和暖如春是尖,著一層夾襖步出監(jiān)牢的瞬間意系,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工饺汹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛔添,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓兜辞,卻偏偏與公主長得像迎瞧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逸吵,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容