本文簡介
點贊 + 關(guān)注 + 收藏 = 學(xué)會了
接著 《Canvas 從入門到勸朋友放棄(圖解版)》 坡脐,本文繼續(xù)補充 canvas
基礎(chǔ)知識點。
這次我不手繪了房揭!
本文會涉及到 canvas
的知識包括:變形备闲、像素控制、漸變捅暴、陰影恬砂、路徑
變形
這里說的變形是基于畫布,全局進行變形蓬痒。
變形主要包括:平移 translate
泻骤、縮放 scale
、旋轉(zhuǎn)操作 rotate
乳幸。
除了對應(yīng)的方法外瞪讼,還可以使用 transform
和 setTransform
對上面三種操作進行配置,這稱為“變換矩陣”粹断。
在學(xué)習(xí)“變形”之前符欠,需要了解 W3C坐標(biāo)系
:
箭頭所指是各軸自己的正方向,x軸越往右(正方向)值越大瓶埋,y軸越往下(正方向)值越大希柿。
平移
使用 translate()
方法可以實現(xiàn)平移效果(位移)。
translate(x, y)
接收2個參數(shù)养筒,第一個參數(shù)代表x軸方向位移距離曾撤,第二個參數(shù)代表y軸方向位移距離。
正數(shù)代表向正方向位移晕粪,負數(shù)代表向反方向位移挤悉。
演示平移效果之前,我先創(chuàng)建一個矩形巫湘,長寬都是100装悲,位置在畫布的原點 (0, 0) 昏鹃,也就是緊貼畫布的左上角。
<canvas id="c" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
// 緊貼原點的矩形诀诊,默認是黑色[]
ctx.fillRect(0, 0, 100, 100)
</script>
如果此時在 fillRect
之前設(shè)置 translate
就可以實現(xiàn)整個畫布位移的效果洞渤。
// 省略部分代碼
// 平移,往右平移10属瓣,往下平移20
ctx.translate(10, 20)
// 渲染矩形
ctx.fillRect(0, 0, 100, 100)
從上圖可以看出载迄,矩形距離畫布頂部的距離是20,距離畫布左側(cè)的距離是10抡蛙。
注意:平移 translate()
要寫在 “繪制操作(本例是 fillRect
)” 之前才有效护昧。
如果在使用 translate
的前后都有渲染操作,畫布會多次渲染粗截,并不會自動清屏捏卓。
比如這樣
<canvas id="c" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.fillRect(0, 0, 100, 100)
ctx.translate(10, 20)
ctx.fillRect(0, 0, 100, 100)
</script>
再做個明顯點的效果,每秒平移一次
<canvas id="c" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.fillRect(0, 0, 100, 100)
setInterval(() => {
ctx.translate(10, 20)
ctx.fillRect(0, 0, 100, 100)
}, 1000)
</script>
可以看出慈格,每次使用 translate()
平移畫布,都會基于上一次畫布所在的位置進行平移遥金。
上圖效果是 canvas
的默認效果浴捆,所以在執(zhí)行 translate
之前可以執(zhí)行 “清屏操作”。
清屏
<canvas id="c" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.fillRect(0, 0, 100, 100)
setInterval(() => {
ctx.clearRect(0, 0, context.width, context.height)
ctx.translate(10, 20)
ctx.fillRect(0, 0, 100, 100)
}, 1000)
</script>
縮放
縮放畫布用到的方法是 scale(x, y)
稿械,接收2個參數(shù)选泻,第一個參數(shù)是x軸方向的縮放,第二個參數(shù)是y軸方向的縮放美莫。
當(dāng) x
或者 y
的值是 0 ~ 1
時代表縮小页眯,比如取值為 0.5 時,表示比原本縮小一半厢呵;值為2時窝撵,比原本放大一倍。
<canvas id="c" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.font = '60px Arial'
ctx.strokeStyle = 'hotpink'
ctx.strokeText('雷猴', 40, 100)
// 縮小
ctx.scale(0.5, 0.5)
// 重新渲染
ctx.strokeText('雷猴', 40, 100)
</script>
scale()
方法同樣會保留原本已經(jīng)渲染的內(nèi)容襟铭。
如果不需要保留原本內(nèi)容碌奉,可以使用 “清屏操作”。
注意:scale()
會以上一次縮放為基準(zhǔn)進行下一次縮放寒砖。
副作用:
其實從上面的例子就可以看出 scale()
存在一點副作用的赐劣,從圖中可以看出,縮放后文本的左上角坐標(biāo)發(fā)生了“位移”哩都,文本描邊粗細也發(fā)生了變化魁兼。
雖然說是副作用,但也很容易理解漠嵌,整塊畫布縮放了咐汞,對應(yīng)的坐標(biāo)比例其實也跟著縮放嘛盖呼。
旋轉(zhuǎn)
使用 rotate(angle)
方法可以旋轉(zhuǎn)畫布,但默認的旋轉(zhuǎn)原點是畫布的左上角碉考,也就是 (0, 0)
坐標(biāo)塌计。
我計算旋轉(zhuǎn)角度通常是用 角度 * Math.PI / 180
的方式表示。
雖然這樣書寫代碼看上去很長侯谁,但習(xí)慣后就比較直觀的看出要旋轉(zhuǎn)多少度锌仅。
rotate(angle)
中的參數(shù) angle
代表角度,angle
的取值范圍是 -Math.PI * 2 ~ Math.pi * 2
墙贱。
當(dāng)旋轉(zhuǎn)角度小于 0 時热芹,畫布逆時針旋轉(zhuǎn);反之順時針旋轉(zhuǎn)惨撇。
<canvas id="c" height="300" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.font = '60px Arial'
ctx.strokeStyle = 'pink'
ctx.strokeText('雷猴', 40, 100)
// 旋轉(zhuǎn) 45°
ctx.rotate(45 * Math.PI / 180)
// 重新渲染
ctx.strokeText('雷猴', 40, 100)
</script>
修改原點
如果需要修改旋轉(zhuǎn)中心伊脓,可以使用 translate()
方法平移畫布,通過計算移動到指定位置魁衙。
<canvas id="c" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.font = '60px Arial'
ctx.strokeStyle = 'pink'
ctx.strokeText('雷猴', 40, 100)
// 設(shè)置旋轉(zhuǎn)中心
ctx.translate(90, -50)
// 旋轉(zhuǎn)
ctx.rotate(45 * Math.PI / 180)
// 重新渲染
ctx.strokeText('雷猴', 40, 100)
</script>
變換矩陣
變換矩陣常用方法有 transform()
和 setTransform()
兩個方法报腔。
變換矩陣是一個稍微進階一點的知識了,別怕剖淀!
前面的 平移 translate
纯蛾、縮放 scale
、旋轉(zhuǎn)操作 rotate
可以說都是 transform()
的 “語法糖”纵隔。
變換矩陣已經(jīng)涉及到一點數(shù)學(xué)知識了翻诉,但本文不會講到這些知識,只會講講 transform()
是怎么用的捌刮。
transform
transform()
一個方法就可以實現(xiàn) 平移碰煌、縮放、旋轉(zhuǎn) 三種功能绅作,它接收6個參數(shù)芦圾。
transform(a, b, c, d, e, f)
-
a
: 水平縮放(x軸方向),默認值是 1俄认; -
b
: 水平傾斜(x軸方向)堕扶,默認值是 0兴溜; -
c
: 垂直傾斜(y軸方向)呀舔,默認值是 0怕午; -
d
: 垂直縮放(y軸方向)发皿,默認值是 1谷朝; -
e
: 水平移動(x軸方向)葫哗,默認值是 0括享; -
f
: 垂直移動(y軸方向)扣汪,默認值是 0;
這默認值看上去很亂科平,但如果這樣排列一下是不是就比較容易理解了:
隨便修改幾個值試試效果:
<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
// 變換矩陣
ctx.transform(1, 1, 1, 2, 30, 40)
// 繪制矩形
ctx.fillRect(10, 10, 100, 100)
</script>
setTransform
setTransform(a, b, c, d, e, f)
同樣接收6個參數(shù)褥紫,和 transform()
一樣
<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
// 變換矩陣
ctx.setTransform(2, 1, 1, 2, 20, 10)
// 繪制矩形
ctx.fillRect(10, 10, 100, 100)
</script>
transform 和 setTransform 的區(qū)別
transform()
每次執(zhí)行都會參考上一次變換后的結(jié)果
比如下面這個多次執(zhí)行的情況:
<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.fillStyle = 'rgba(10, 10, 10, 0.2)'
ctx.fillRect(10, 10, 100, 100)
ctx.transform(1, 0, 0, 1, 10, 10)
ctx.fillRect(10, 10, 100, 100)
ctx.transform(1, 0, 0, 1, 10, 10)
ctx.fillRect(10, 10, 100, 100)
ctx.transform(1, 0, 0, 1, 10, 10)
ctx.fillRect(10, 10, 100, 100)
ctx.transform(1, 0, 0, 1, 10, 10)
ctx.fillRect(10, 10, 100, 100)
</script>
而 setTransform()
每次調(diào)用都會基于最原始是狀態(tài)進行變換。
<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.fillStyle = 'rgba(10, 10, 10, 0.2)'
ctx.fillRect(10, 10, 100, 100)
ctx.setTransform(1, 0, 0, 1, 10, 10)
ctx.fillRect(10, 10, 100, 100)
ctx.setTransform(1, 0, 0, 1, 10, 10)
ctx.fillRect(10, 10, 100, 100)
ctx.setTransform(1, 0, 0, 1, 10, 10)
ctx.fillRect(10, 10, 100, 100)
ctx.setTransform(1, 0, 0, 1, 10, 10)
ctx.fillRect(10, 10, 100, 100)
</script>
不管改變多少次瞪慧,setTransform()
都會參考原始狀態(tài)進行變換髓考。
控制像素
位圖是由像素點組成的,canvas
提供了幾個 api
可以操作圖片中的像素弃酌。
很多工具網(wǎng)站也常用接下來說到的幾個 api
做圖片濾鏡氨菇。
需要注意的是,canvas
提供的操作像素的方法妓湘,必須使用服務(wù)器才能運行起來查蓉,不然沒有效果的。
可以搭建本地服務(wù)器運行本文案例榜贴,方法有很多種豌研。
比如你使用 Vue
或者 React
的腳手架搭建的項目,運行后就能跑起本文所有案例唬党。
又或者使用 http-server
啟動本地服務(wù)鹃共。
getImageData()
首先要介紹的是 getImageData()
方法,這個方法可以獲取指定區(qū)域內(nèi)的所有像素驶拱。
getImageData(x, y, width, height)
接收4個參數(shù)及汉,這4個參數(shù)表示選區(qū)范圍。
x
和 y
代表選區(qū)的左上角坐標(biāo)屯烦,width
表示選區(qū)寬度,height
表示選區(qū)高度房铭。
還是舉例說明比較清楚驻龟。下圖渲染到畫布上的是我的貓Bubble。
<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
const img = new Image() // 創(chuàng)建圖片對象
img.src = './bubble.jpg' // 加載本地圖片
// 圖片加載完成后在執(zhí)行其他操作
img.onload = () => {
// 渲染圖片
ctx.drawImage(img, 0, 0)
// 獲取圖片信息
const imageData = ctx.getImageData(0, 0, img.width, img.height)
console.log(imageData)
}
</script>
打印出來的信息可以點開大圖看看
-
data
: 圖片像素數(shù)據(jù)集缸匪,以數(shù)組的形式存放翁狐,這是本文要講的重點,需要關(guān)注凌蔬! -
colorSpace
: 圖片使用的色彩標(biāo)準(zhǔn)露懒,這個屬性在Chrome
里有打印出來,Firefox
里沒打印砂心。不重要~ -
height
: 圖片高度 -
width
: 圖片寬度
通過 getImageData()
獲取到的信息中懈词,需要重點關(guān)注的是 data
,它是一個一維數(shù)組辩诞,仔細觀察發(fā)現(xiàn)里面的值沒一個是大于255的坎弯,也不會小于0。
其實 data
屬性里記錄了圖片每個像素的 rgba
值分別是多少。
-
r
代表紅色 -
g
代表綠色 -
b
代表藍色 -
a
透明度
這個和 CSS
里的 rgba
是同一個意思抠忘。
data
里撩炊,4個元素記錄1個像素的信息。也就是說崎脉,1個像素是由 r
拧咳、g
、b
囚灼、a
4個元素組成骆膝。而且每個元素的取值范圍是 0 - 255 的整數(shù)。
data: **[r1, g1, b1, a1, r2, g2, b2, a2, ......]**
像素點 | 值 | 顏色通道 |
---|---|---|
imgData.data[0] |
49 | 紅色 r |
imgData.data[1] |
47 | 綠色 g |
imgData.data[2] |
51 | 藍色 b |
imgData.data[3] |
255 | 透明度 a |
…… | …… | …… |
imgData.data[n-4] |
206 | 紅色 r |
imgData.data[n-2] |
200 | 綠色 g |
imgData.data[n-3] |
200 | 藍色 b |
imgData.data[n-1] |
255 | 透明度 a |
如果一張圖只有10個像素啦撮,通過 getImageData()
獲取到的 data
信息中就有40個元素谭网。
putImageData()
putImageData(imageData, x, y)
可以將 ImageData
對象的數(shù)據(jù)(圖片像素數(shù)據(jù))繪制到畫布上。
putImageData(imgData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
也可以接收更多參數(shù)赃春。
-
imageData
: 規(guī)定要放回畫布的ImageData
對象 -
x
:ImageData
對象左上角的 x 坐標(biāo)愉择,以像素計 -
y
:ImageData
對象左上角的 y 坐標(biāo),以像素計 -
dirtyX
: 可選织中。水平值(x)锥涕,以像素計,在畫布上放置圖像的位置 -
dirtyY
: 可選狭吼。水平值(y)层坠,以像素計,在畫布上放置圖像的位置 -
dirtyWidth
: 可選刁笙。在畫布上繪制圖像所使用的寬度 -
dirtyHeight
: 可選破花。在畫布上繪制圖像所使用的高度
比如,我要將圖片復(fù)制到另一個位置
<canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
const img = new Image() // 創(chuàng)建圖片對象
img.src = './bubble.jpg' // 加載本地圖片
// 圖片加載完成后在執(zhí)行其他操作
img.onload = () => {
// 渲染圖片
ctx.drawImage(img, 0, 0)
// 獲取圖片信息
const imageData = ctx.getImageData(0, 0, img.width, img.height)
// 將圖片對象輸出到 (100, 100) 的位置上
ctx.putImageData(imageData, 100, 100)
}
</script>
可以實現(xiàn)復(fù)制的效果疲吸。
透明
知道前面兩個 api
就可以實現(xiàn)透明效果了座每。
前面講到,通過 getImageData()
獲取的是一個數(shù)組類型的數(shù)據(jù)摘悴,每4個元素代表1個像素峭梳,就是rgba
,而 a
表示透明通道蹂喻,所以只需修改每組像素的最后1個元素的值葱椭,就能修改圖片的不透明度。
<canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
const img = new Image() // 創(chuàng)建圖片對象
img.src = './bubble.jpg' // 加載本地圖片
// 圖片加載完成后在執(zhí)行其他操作
img.onload = () => {
// 渲染圖片
ctx.drawImage(img, 0, 0)
// 獲取圖片信息
const imageData = ctx.getImageData(0, 0, img.width, img.height)
for (let i = 0; i < imageData.data.length; i += 4) {
imageData.data[i + 3] = imageData.data[i + 3] * 0.5
}
// 將圖片對象輸出到 (100, 100) 的位置上
ctx.putImageData(imageData, 100, 100)
}
</script>
濾鏡
要做不同的濾鏡效果口四,其實就是通過不同的算法去操作每個像素的值孵运,我在 《Canvas 10款基礎(chǔ)濾鏡(原理篇)》 講到相關(guān)知識,有興趣的工友可以點進去看看
漸變
在 css
和 svg
里都有漸變蔓彩,canvas
肯定也不會缺失這個能力啦掐松。
canvas
提供了 線性漸變 createLinearGradient
和 徑向漸變 createRadialGradient
踱侣。
線性漸變 createLinearGradient
在 canvas
中使用線性漸變步驟如下:
- 創(chuàng)建線性漸變對象:
createLinearGradient(x1, y1, x2, y2)
- 添加漸變顏色:
addColorStop(stop, color)
- 設(shè)置填充色或描邊顏色:
fillStyle
或strokeStyle
createLinearGradient(x1, y1, x2, y2)
在 createLinearGradient(x1, y1, x2, y2)
中,x1, y1
表示漸變的起始位置大磺,x2, y2
表示漸變的結(jié)束位置抡句。
比如水平方向的從左往右的線性漸變,此時的 y1
和 y2
的值是一樣的杠愧。
兩個點就可以確定一個漸變方向待榔。
addColorStop(stop, color)
addColorStop(stop, color)
方法可以添加漸變色。
第一個參數(shù) stop
表示漸變色位置的偏移量流济,取值范圍是 0 ~ 1锐锣。
第二個參數(shù) color
表示顏色。
填充漸變
實際編碼演示一下
<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
// 1. 創(chuàng)建線性漸變對象
const lgrd = ctx.createLinearGradient(10, 10, 200, 10)
// 2. 添加漸變顏色
lgrd.addColorStop(0, 'pink')
lgrd.addColorStop(1, 'yellow')
// 設(shè)置填充色
ctx.fillStyle = lgrd
// 創(chuàng)建矩形绳瘟,填充
ctx.fillRect(10, 10, 200, 200)
</script>
如果想修改漸變的方向雕憔,只需在使用 createLinearGradient()
時設(shè)置好起點和終點坐標(biāo)即可。
除了填充色糖声,描邊漸變和文本漸變同樣可以做到斤彼。
描邊漸變
<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
const lgrd = ctx.createLinearGradient(10, 10, 200, 10)
lgrd.addColorStop(0, 'pink')
lgrd.addColorStop(1, 'yellow')
ctx.strokeStyle = lgrd
ctx.lineWidth = 10
ctx.strokeRect(10, 10, 200, 200)
</script>
文本漸變
<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
const lgrd = ctx.createLinearGradient(10, 10, 200, 10)
lgrd.addColorStop(0, 'pink')
lgrd.addColorStop(1, 'yellow')
const text = '雷猴'
ctx.font = 'bold 100px 黑體'
ctx.fillStyle = lgrd
ctx.fillText(text, 10, 100)
</script>
多色線性漸變
在 0 ~ 1 的范圍內(nèi),addColorStop
可以設(shè)置多個顏色在不同的位置上蘸泻。
// 省略部分代碼
lgrd.addColorStop(0, '#2a9d8f') // 綠色
lgrd.addColorStop(0.5, '#e9c46a') // 黃色
lgrd.addColorStop(1, '#f4a261') // 橙色
徑向漸變 createRadialGradient
徑向漸變是從一個點到另一個點擴散出去的漸變琉苇,是圓形(橢圓也可以)漸變。
直接看效果
<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
const rgrd = ctx.createRadialGradient(70, 70, 0, 70, 70, 60)
rgrd.addColorStop(0, 'yellow')
rgrd.addColorStop(1, 'pink')
ctx.fillStyle = rgrd
ctx.fillRect(10, 10, 120, 120)
</script>
用 createRadialGradient
可以創(chuàng)建一個徑向漸變的對象悦施。使用步驟和 createLinearGradient
一樣并扇,但參數(shù)不同。
createRadialGradient(x1, y1, r1, x2, y2, r2)
-
x1, y1
: 漸變開始的圓心坐標(biāo) -
r1
: 漸變開始的圓心半徑 -
x2, y2
: 漸變結(jié)束的圓心坐標(biāo) -
r2
: 漸變結(jié)束的圓心半徑
同樣使用 addColorStop
設(shè)置漸變顏色抡诞,同樣支持多色漸變穷蛹。
漸變的注意事項
漸變的定位坐標(biāo)是參照畫布的,超出定位的部分會使用最臨近的那個顏色昼汗。
我用線性漸變舉例肴熏。
<canvas id="c" width="600" height="600" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
const lgrd = ctx.createLinearGradient(200, 0, 400, 400)
lgrd.addColorStop(0, 'pink')
lgrd.addColorStop(1, 'yellow')
ctx.fillStyle = lgrd
ctx.fillRect(10, 10, 160, 160)
ctx.fillRect(220, 10, 160, 160)
ctx.fillRect(430, 10, 160, 160)
ctx.fillRect(10, 210, 160, 160)
ctx.fillRect(220, 210, 160, 160)
ctx.fillRect(430, 210, 160, 160)
ctx.fillRect(10, 430, 160, 160)
ctx.fillRect(220, 430, 160, 160)
ctx.fillRect(430, 430, 160, 160)
</script>
上面的例子中,我只創(chuàng)建了一個漸變乔遮,然后創(chuàng)建了9個正方形。
此時正方形的填充色取決于出現(xiàn)在畫布中的位置取刃。
可以修改一下 createLinearGradient()
的定位數(shù)據(jù)對照理解蹋肮。
// 省略部分代碼
const lgrd = ctx.createLinearGradient(200, 0, 400, 400)
如果想每個圖形都有自己的漸變色,這需要定制化配置璧疗,每個創(chuàng)建每個圖形之前都單獨創(chuàng)建一個漸變色坯辩。
<canvas id="c" width="600" height="600" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
// 粉 - 黃 漸變
const lgrd1 = ctx.createLinearGradient(10, 10, 160, 160)
lgrd1.addColorStop(0, 'pink')
lgrd1.addColorStop(1, 'yellow')
ctx.fillStyle = lgrd1
ctx.fillRect(10, 10, 160, 160)
// 橘黃 - 藍紫 漸變
const lgrd2 = ctx.createLinearGradient(210, 10, 380, 160)
lgrd2.addColorStop(0, 'bisque')
lgrd2.addColorStop(1, 'blueviolet')
ctx.fillStyle = lgrd2
ctx.fillRect(220, 10, 160, 160)
</script>
所以不管是填充色還是秒變顏色,每個元素最好都自己重新設(shè)定一下崩侠。不然可能會出現(xiàn)意想不到的效果~
陰影
陰影在前端也是很常用的特效漆魔。 依稀記得當(dāng)年還用 。png
做陰影效果
在 canvas
中,和陰影相關(guān)的屬性主要有以下4個:
-
shadowOffsetX
: 設(shè)置或返回陰影與形狀的水平距離改抡。默認值是0矢炼。大于0時向正方向偏移。 -
shadowOffsetY
: 設(shè)置或返回陰影與形狀的垂直距離阿纤。默認值是0句灌。大于0時向正方向偏移。 -
shadowColor
: 設(shè)置或返回用于陰影的顏色欠拾。 默認黑色胰锌。 -
shadowBlur
: 設(shè)置或返回用于陰影的模糊級別。 默認值是0藐窄,數(shù)值越大模糊度越強资昧。
相信使用過 css
陰影屬性的工友,理解起 canvas
陰影也會非常輕松荆忍。
<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.shadowOffsetX = 10 // x軸偏移量
ctx.shadowOffsetY = 10 // y軸偏移量
ctx.shadowColor = '#f38181' // 陰影顏色
ctx.shadowBlur = 10 // 陰影模糊度格带,默認0
ctx.fillStyle = '#fce38a' // 填充色
ctx.fillRect(30, 30, 200, 100)
console.log(ctx.shadowOffsetX) // 輸出陰影x軸方向的偏移量:10
</script>
除了圖形外,文本和圖片都可以設(shè)置陰影效果东揣。
<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.shadowOffsetX = 10 // x軸偏移量
ctx.shadowOffsetY = 10 // y軸偏移量
ctx.shadowColor = '#b83b5e' // 陰影顏色
ctx.shadowBlur = 10 // 陰影模糊度践惑,默認0
const text = '雷猴'
ctx.font = 'bold 100px 黑體'
ctx.fillStyle = '#fce38a'
ctx.fillText(text, 10, 100)
</script>
路徑
在 Canvas 從入門到勸朋友放棄(圖解版) —— 新開路徑 中我講到 新開路徑 和 關(guān)閉路徑 的用法,本節(jié)會在上篇的基礎(chǔ)上豐富更多使用細節(jié)嘶卧。
本節(jié)要講的是
-
beginPath()
: 新開路徑 -
closePath()
: 關(guān)閉路徑 -
isPointInPath()
: 判斷某個點是否在當(dāng)前路徑內(nèi)
beginPath()
beginPath()
方法是用來開辟一條新的路徑尔觉,這個方法會將當(dāng)前路徑之中的所有子路徑都清除掉,以此來重置當(dāng)前路徑芥吟。
如果你的畫布上有幾個基礎(chǔ)圖形(直線侦铜、多邊形、圓形钟鸵、弧钉稍、貝塞爾曲線),如果樣式相互之間受到影響棺耍,那你可以立刻想想在繪制新圖形之前是不是忘了使用 beginPath()
贡未。
先舉幾個例子說明一下。
污染:顏色蒙袍、線條粗細受到污染
后面的樣式覆蓋了前面的樣式俊卤。
<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
// 第一條線,粉色
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.strokeStyle = 'pink' // 粉色描邊
ctx.stroke()
// 第二條線害幅,紅色
ctx.moveTo(50, 80)
ctx.lineTo(150, 80)
ctx.strokeStyle = 'red' // 紅色描邊
ctx.lineWidth = 10 // 表面粗細
ctx.stroke()
</script>
污染:圖形路徑污染
比如畫布上有一條直線和一個圓形消恍,不使用 beginPath()
開辟新路徑的話,它們可能會“打架”以现。
<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
// 第一條線狠怨,粉色
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.strokeStyle = 'pink' // 粉色描邊
ctx.stroke()
// 圓形
ctx.arc(150, 120, 40, 0, 360 * Math.PI / 180)
ctx.lineWidth = 4
ctx.stroke()
</script>
明明直線和圓形是沒有交集的约啊,為什么會有一條傾斜的線把兩個元素連接起來?
解決辦法
除了上面兩種情況外佣赖,可能還有其他更加奇怪的情況(像極喝醉了假酒)恰矩,都可以先考慮是不是要使用 beginPath()
。
比如這樣做茵汰。
<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
// 第一條線枢里,粉色
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.strokeStyle = 'pink' // 粉色描邊
ctx.lineWidth = 10
ctx.stroke()
// 圓形
ctx.beginPath() // 開辟新的路徑
ctx.arc(150, 120, 40, 0, 360 * Math.PI / 180)
ctx.strokeStyle = 'skyblue' // 藍色描邊
ctx.lineWidth = 4
ctx.stroke()
</script>
在使用 arc
或者 moveTo
方法之前加上一句 ctx.beginPath()
就可以有效解決以上問題。
這個例子中蹂午,如果沒用 ctx.beginPath()
栏豺,canvas
就會以為 線 和 圓形 都屬于同一個路徑,所以在畫圓形時豆胸,下筆的時候就會把線的“結(jié)束點”和圓的“起點”相連起來奥洼。
stroke()
和 fill()
都是以最近的 beginPath()
后面所定義的狀態(tài)樣式為基礎(chǔ)進行繪制的。
注意事項
前面的樣式會覆蓋后面元素的默認樣式晚胡,即使使用了 beginPath()
灵奖。
<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
// 第一條線,粉色
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.strokeStyle = 'pink' // 粉色描邊
ctx.lineWidth = 10 // 表面粗細
ctx.stroke()
// 第二條線估盘,紅色
ctx.beginPath()
ctx.moveTo(50, 80)
ctx.lineTo(150, 80)
ctx.stroke()
</script>
第一條先設(shè)置了 strokeStyle
和 lineWidth
瓷患,第二條線并沒有設(shè)置這兩個屬性,即使在繪制第二條線的開始時使用了 ctx.beginPath()
遣妥,第二條線也會使用第一條線的 strokeStyle
和 lineWidth
擅编。除非第二條線自己也有設(shè)置這兩個屬性,不然就會沿用之前的配置項箫踩。
"特殊情況"
還要補充一個 “特殊情況”爱态。
<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
<script>
const cnv = document.getElementById('c')
const ctx = cnv.getContext('2d')
// 第一條線,粉色
ctx.moveTo(50, 30)
ctx.lineTo(150, 30)
ctx.strokeStyle = 'pink' // 粉色描邊
ctx.lineWidth = 10 // 描邊粗細
ctx.stroke()
// 矩形
ctx.strokeStyle = 'red' // 紅色描邊
ctx.strokeRect(50, 50, 200, 100)
</script>
這個例子中境钟,繪制矩形 rect
前并沒有用 beginPath()
锦担,但矩形的紅色描邊并沒有影響直線的粉色描邊。
其實還不止 strokeRect()
慨削,還有 fillRect()
洞渔、strokeText()
、fillText()
都不會影響其他圖形缚态,這些方法都只會繪制圖形磁椒,不會影響原本路徑。
closePath()
closePath()
方法可以關(guān)閉當(dāng)前路徑猿规,它可以顯示封閉某段開放的路徑衷快。這個方法常用于關(guān)閉圓弧路徑或者由圓弧宙橱、線段創(chuàng)建出來的開放路徑姨俩。
closePath()
是關(guān)閉路徑蘸拔,并不是結(jié)束路徑。
關(guān)閉路徑环葵,指的是連接起點與終點调窍,也就是能夠自動封閉圖形。
結(jié)束路徑张遭,指的是開始新的路徑邓萨。
基礎(chǔ)用法
舉個例子會更直觀
<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.lineTo(150, 140)
ctx.stroke()
</script>
上面的代碼通過 moveTo
和 lineTo
畫了3個點,使用 stroke()
方法把這3個點連起來菊卷,就形成了上圖效果缔恳。
但如果此時在 stroke()
前使用 closePath()
方法,最終出來的路徑將自動閉合(將起點和終點連接起來)洁闰。
<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>
<script>
const context = document.getElementById('c')
const ctx = context.getContext('2d')
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.lineTo(150, 140)
ctx.closePath() // 關(guān)閉路徑
ctx.stroke()
</script>
注意事項
看到上面的例子后歉甚,可能有些工友會覺得使用 ctx.lineTo(50, 40)
連接回起點也有同樣效果。
// 省略部分代碼
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.lineTo(150, 140)
ctx.lineTo(50, 40)
ctx.stroke()
確實在描邊為1像素時看上去效果是差不多的扑眉,但如果此時將 lineWidth
的值設(shè)置得大一點纸泄,就能看到明顯區(qū)別。
// 省略部分代碼
ctx.lineWidth = 10
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.lineTo(150, 140)
ctx.lineTo(50, 40) // 連接回起點
ctx.stroke()
如果用 closePath()
自動閉合路徑的話腰素,會出現(xiàn)以下效果
// 省略部分代碼
ctx.lineWidth = 10
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.lineTo(150, 140)
ctx.closePath() // 關(guān)閉路徑
ctx.stroke()
本文到此就完結(jié)了聘裁,但 canvas
的知識點還沒完,還有很多很多弓千,根本學(xué)不完的那種衡便。
接下來 本專欄 的文章會偏向于 知識點 + 案例 的方式講解 canvas
。
代碼倉庫
推薦閱讀
??《SVG 從入門到后悔砰诵,怎么不早點學(xué)起來(圖解版)》
點贊 + 關(guān)注 + 收藏 = 學(xué)會了
代碼倉庫