Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
一篇文章《HTML5(五)——Canvas API》介紹 canvas 繪制基本圖形,這節開始介紹canvas的高級操作。
canvas 轉換常用的幾種方法介紹,如下:
方法 | 描述 |
scale() | 縮放當前繪圖至更大或更小。 |
rotate() | 旋轉當前繪圖。 |
translate() | 重新映射畫布上的 (0,0) 位置。 |
transform() | 替換繪圖的當前轉換矩陣。 |
setTransform() | 將當前轉換重置為單位矩陣。然后運行 transform()。 |
1.1 、scale - 縮放
使用語法:scale(x,y)
x:表示水平方向的縮放倍數
y:表示垂直方向的縮放倍數
eg:canvas 繪制的矩形框放大兩倍,代碼如下:
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
ctx.scale(2,2)
ctx.fillStyle="red"
ctx.fillRect(0,0,200,200)
1.2、translate - 畫布平移
使用語法:translate(x,y)
x:添加到水平坐標上的位置
y:添加到垂直坐標上的位置
設置之后開始繪制的圖片位置從(x,y)算起。
eg:繪制兩個一樣的矩形,一個在平移前繪制,一個在平移后繪制,代碼如下:
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
ctx.fillStyle="red"
ctx.fillRect(0,0,200,200)
//平移
ctx.translate(200,200)
ctx.fillRect(0,0,200,200)
運行結果如圖:
1.3 、rotate - 旋轉
使用語法:rotate(angle)
angle 旋轉弧度,如果想使用角度,可以把角度轉成弧度,公式為:deg * Path.PI/180。
eg:將一個矩形旋轉45度,代碼如下:
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
//旋轉45度
ctx.rotate(45*Math.PI/180)
ctx.fillStyle="red"
ctx.fillRect(0,0,200,200)
運行結果如圖:
根據上述結果我們發現旋轉的時候,默認原點是畫布的起始點,我們想要的旋轉是在矩形框中心為原點的旋轉,此時我們需要借助上translate平移,重置一下原點,修改上述代碼為:
<canvas width="400" height="400" id="canvas"></canvas>
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
ctx.translate(200,200)
ctx.rotate(45*Math.PI/180)
ctx.translate(-200,-200)
ctx.fillStyle="red"
ctx.fillRect(100,100,200,200)
運行結果如圖:
1.4、transform - 矩陣變換
使用語法:transform(a,b,c,d,e,f)
transform可以替代前邊平移、縮放、旋轉三者,如下:
// 平移
translate(x,y) <=> transform(1,0,0,1,x,y) <=> transform(0,1,1,0,x,y)
// 縮放
sacle(x,y) <=> transform(x,0,0,y,0,0)
// 旋轉
rotate(angle) <=> transform(Math.cos(angle*Math.PI/180), Math.sin(angel*Math.PI/180), -Math.sin(angle*Math.PI/180), Math.cos(angle*Math.PI/180))
1.5、setTransform - 矩陣變換
setTransform()方法將變換的矩陣進行重置,它把當前的變換矩陣重置為單位矩陣
使用語法:transform(a,b,c,d,e,f)
各參數說明:水平旋轉、水平傾斜、垂直傾斜、垂直縮放、水平移動、垂直移動
setTransform() 方法把當前的變換矩陣重置為單位矩陣,然后以相同的參數運行 transform()。
drawImage() 在畫布上繪制圖像、畫布或視頻。也能夠繪制圖片的一部分,增加或減少圖像的尺寸。以下是三種常見使用語法:
上述參數表示的意義如下:
參數 | 描述 |
img | 規定要使用的圖像、畫布或視頻。 |
sx | 可選。開始剪切的 x 坐標位置。 |
sy | 可選。開始剪切的 y 坐標位置。 |
swidth | 可選。被剪切圖像的寬度。 |
sheight | 可選。被剪切圖像的高度。 |
x | 在畫布上放置圖像的 x 坐標位置。 |
y | 在畫布上放置圖像的 y 坐標位置。 |
width | 可選。要使用的圖像的寬度。(伸展或縮小圖像) |
height | 可選。要使用的圖像的高度。(伸展或縮小圖像) |
eg:利用語法3,進行繪制圖片的部分內容,實現如下效果:
給上述兔子順便加一個點擊屏幕暫停開始功能,完整代碼如下:
<canvas width="400" height="400" id="canvas"></canvas>
<script>
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
var img = new Image()
let pause = false,frameCounter = 0,i=0;
img.src = "./rotate.png"
img.onload = function(){
requestAnimationFrame(next)
}
function next(){
ctx.clearRect(0,0,canvas.width,canvas.height)
if(frameCounter%5 == 0){ //frameCounter 控制動畫速度
i++
if(i==11)i=0
}
ctx.drawImage(img,
0,i*240,240,240,
0,0,240,240) // 每張圖片寬高都是240,具體參數根據圖片而定
frameCounter ++
if(!pause)requestAnimationFrame(next)
}
window.onclick = function(){
pause = !pause
next()
}
</script>
eg:使用 canvas 畫布處理視頻,使用定時器繪制視頻的當前幀,連續起來就是一個視頻,需要注意的是必須處理暫停和開始播放兩種操作,具體代碼如下:
var v=document.getElementById("video1");
var c=document.getElementById("myCanvas");
ctx=c.getContext('2d');
v.addEventListener('play',function() {var i=window.setInterval(function()
{ctx.drawImage(v,0,0,270,135)},20);},false);
v.addEventListener('pause',function() {window.clearInterval(i);},false);
v.addEventListener('ended',function() {clearInterval(i);},false);
常見的像素級的操作有三種:
3.1、getImageData
使用語法:getImageData( x , y , width , height )
返回一個 imageData 對象,用來描述 canvas 區域隱含的像素數據,這個區域通過像素表示,起點是( x , y ),寬高為 widht 和 height 。
imageData 對象包含三個屬性:
3.2、createImageData
使用語法:
createImageData( width , height )
創建一個空白的 imageData 對象,新對象的默認像素值 transparent black。對于imageData對象中的每個像素值,都存在 rgba 這四方面的信息,即:
新對象默認像素值為(0,0,0,0)。
eg:如果我們想把 imageData 中一個像素變為紅色時,可以改變第一和第四位信息,代碼如下:
var imageData = ctx.createImageData(100,100)
imageData.data[0] = 255
imageData.data[1] = 0
imageData.data[2] = 0
imageData.data[3] = 255
3.1、putImageData
使用語法:
putImageData( imgData , x , y , dirtyX , dirtyY , dirtyWidth ,dirtyHeight );
參數及意義:
參數 | 描述 |
imgData | 規定要放回畫布的 ImageData 對象。 |
x | ImageData 對象左上角的 x 坐標,以像素計。 |
y | ImageData 對象左上角的 y 坐標,以像素計。 |
dirtyX | 可選。水平值(x),以像素計,在畫布上放置圖像的位置。 |
dirtyY | 可選。水平值(y),以像素計,在畫布上放置圖像的位置。 |
dirtyWidth | 可選。在畫布上繪制圖像所使用的寬度。 |
dirtyHeight | 可選。在畫布上繪制圖像所使用的高度。 |
通過 getImageData 復制的指定矩形像素數據,編輯之后,通過 putImageData 方法將圖像數據放回畫布上。
eg:添加濾鏡效果:上述兔子是白色的變換成紅色兔子,這時需要把綠色和藍色都設置成0即可,代碼如下:
TML5 Canvas是HTML5新增的一個元素,它提供了一個可執行JavaScript腳本繪制圖形的區域。Canvas元素通過使用JavaScript API,可以在瀏覽器上繪制圖形、渲染動畫和實現交互效果等。
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Canvas示例</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400"></canvas>
<script>
// 獲取Canvas元素和繪圖上下文
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// 繪制矩形
ctx.fillStyle = "blue";
ctx.fillRect(50, 50, 100, 100);
// 繪制圓形
ctx.beginPath();
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
</script>
</body>
</html>
在上述代碼中,我們首先獲取了Canvas元素和繪圖上下文(Context),然后使用fillRect()方法繪制了一個藍色的矩形,使用arc()方法繪制了一個紅色的圓形。最后,我們使用fill()方法填充了圓形的顏色。
、什么是Canvas?
HTML5 提供Canvas API,其本質上是一個DOM元素,可以看成是瀏覽器提供一塊畫布供我們在上面渲染2D或者3D圖形。由于3D繪制上下文(webgl)目前在很多瀏覽器上兼容性較差,所以我們一般用于繪制2D圖形。
<canvas id="canvas"></canvas>
2、為什么使用Canvas?
Canvas是HTML5引入的標簽,在此之前我們通常會使用SVG來繪制一些圖形,那么兩者之間有什么區別呢?SVG可縮放矢量圖形(Scalable Vector Graphics)是基于可擴展標記語言XML描述的2D圖形的語言,兩者部分區別:
由于Canvas是通過Javascript來完成繪制的,所以可控性很強,我們可以比較精確的控制圖形渲染的每一幀;從另一方面來說,如果在高頻率渲染中要處理過多的DOM元素就意味著性能一定不會太好,渲染速度會下降很多。Canvas的高性能能夠保障復雜場景中圖形的渲染效率,所以目前很多領域都會使用Canvas,例如動畫、游戲圖形、數據可視化、照片處理和實時視頻處理等。
3、Canvas的基本使用
要使用Canvas,我們需要先獲取Canvas元素的引用繼而通過getContext()方法獲取圖形的繪制上下文。
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
獲取到圖形繪制上下文后,我們就能使用CanvasRenderingContext2D接口上的繪圖API了,接下來我們可以了解一些比較常規的使用。
3.1、畫布屬性:
ctx.width = 300
ctx.height = 300
ctx.fillStyle = '#fff'
ctx.strokeStyle = 'blue'
ctx.lineWidth = 5
ctx.globalAlpha = 0.3
ctx.globalCompositeOperation = 'destination-out' // 新老圖形重疊部分變透明
......
3.2、繪制圖形:
ctx.fillStyle = 'red'
ctx.fillRect(100,100,100,100)
ctx.strokeStyle = 'blue'
ctx.strokeRect(200,200,100,100)
ctx.clearRect(125,125,50,50)
ctx.strokeRect(130,130,40,40)
3.3、繪制路徑:
ctx.beginPath()
ctx.moveTo(50,50)
ctx.lineTo(100,100)
ctx.lineTo(100,0)
ctx.fill()
ctx.beginPath()
ctx.moveTo(110,100)
ctx.lineTo(150,100)
ctx.lineTo(150,200)
ctx.lineTo(110,200)
ctx.closePath() // 輪廓圖形不會根據從當前坐標到起始坐標生成輪廓,所以需要閉合路徑
ctx.stroke()
3.4、繪制圓弧:
注意:arc函數中的角度的單位是弧度而不是度,弧度=(Math.PI/180)*度
// 圓左上部分
ctx.beginPath()
ctx.arc(100,100,50,Math.PI,Math.PI*3/2,false)
ctx.strokeStyle = '#ff6700'
ctx.stroke()
// 圓右上部分
ctx.beginPath()
ctx.arc(100,100,50,Math.PI*3/2,0,false)
ctx.strokeStyle = '#6700ff'
ctx.stroke()
// 圓右下部分
ctx.beginPath()
ctx.arc(100,100,50,0,Math.PI/2,false)
ctx.strokeStyle = '#00FFFF'
ctx.stroke()
// 圓左下部分
ctx.beginPath()
ctx.arc(100,100,50,Math.PI/2,Math.PI,false)
ctx.strokeStyle = '#8B008B'
ctx.stroke()
// 兩條切線的交點坐標為(0,0)
ctx.beginPath()
ctx.moveTo(100,0)
ctx.arcTo(0,0,0,100,100)
ctx.fillStyle = 'blue'
ctx.fill()
3.5、漸變對象:
創建好漸變對象之后,可以通過漸變對象上的.addColorStop(offset,color)為每一個漸變階段填充顏色,offset為0-1的偏移值。
const gradient = ctx.createLinearGradient(50, 50, 250, 50)
gradient.addColorStop(0, 'blue')
gradient.addColorStop(0.5, 'green')
gradient.addColorStop(1, 'red')
ctx.fillStyle = gradient
ctx.fillRect(0, 0, 300, 90)
const radialGradient = ctx.createRadialGradient(200,200,100,200,200,50);
radialGradient.addColorStop(0,"yellow");
radialGradient.addColorStop(1,"green");
ctx.fillStyle = radialGradient;
ctx.fillRect(100,100,200,200);
3.6、像素操作:
const div = document.querySelector('div')
let mousedown = false;
function getRandom() {
return Math.round(255 * Math.random());
}
function getColor() {
return `rgb(${getRandom()},${getRandom()},${getRandom()})`;
}
const gradient = ctx.createLinearGradient(0, 0, 300, 300);
gradient.addColorStop(0, getColor());
gradient.addColorStop(0.6, getColor());
gradient.addColorStop(1, getColor());
function clear() {
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.beginPath();
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 300, 300);
function selector(x = 150, y = 150) {
clear();
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.strokeStyle = "#fff";
ctx.stroke();
const { data } = ctx.getImageData(x, y, 1, 1); // 獲取(x,y)點對應的imageData
const color = `rgba(${data[0]},${data[1]},${data[2]},${data[3] / 255})`
div.innerText = `color: ${color}`;
div.style.backgroundColor = color
}
function handleSelector(e) {
const x = e.offsetX;
const y = e.offsetY;
selector(x, y);
}
canvas.addEventListener("mousedown", (e) => {
mousedown = true;
handleSelector(e)
});
canvas.addEventListener("mouseup", () => {
mousedown = false;
});
canvas.addEventListener("mousemove", (e) => {
if (mousedown) {
handleSelector(e)
}
});
selector();
3.7、畫布狀態:
當我們需要通過空間轉換來繪制圖形時,保存與恢復畫布的狀態是很關鍵的,因為我們是在同一塊畫布上繪制圖形,而變換都是基于畫布的,這與我們平時使用到的CSS 2D轉換截然不同,所以我們在下一步繪制時要確認此時畫布的狀態是否是我們的理想狀態。
ctx.save() // 保存畫布初始狀態
ctx.translate(100,100) // 將畫布原點轉移至(100,100)
ctx.fillStyle = 'red'
ctx.fillRect(0,0,50,50)
ctx.restore() // 恢復畫布狀態,此時畫布原點為(0,0)
ctx.fillStyle = 'blue'
ctx.fillRect(0,0,50,50)
3.8、幾何變化:
const colors = ['red','orange','yellow','green','blue','purple'];
ctx.translate(150,150)
for(let i = 0; i < 6; i++) {
ctx.beginPath()
ctx.fillStyle = colors[i]
ctx.moveTo(0,0)
ctx.lineTo(100,0)
ctx.lineTo(100,50)
ctx.rotate(Math.PI/3)
ctx.fill()
}
4、綜合實戰
const p = Math.PI;
function clock() {
const date = new Date();
const hour = date.getHours()
const s = date.getSeconds();
const m = date.getMinutes();
const h = !!(hour % 12) ? hour % 12 : 12;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save(); // 保存畫布初始狀態
ctx.translate(150, 150);
ctx.rotate(-p / 2);
// 輪廓
ctx.beginPath();
ctx.lineWidth = 5;
ctx.strokeStyle = "#76b2ff";
ctx.arc(0, 0, 80, 0, p * 2);
ctx.stroke();
// 圓心
ctx.beginPath();
ctx.arc(0, 0, 2, 0, p * 2);
ctx.fill();
// 分針、秒針刻度
for (let i = 0; i < 60; i++) {
ctx.beginPath();
ctx.rotate(p / 30);
ctx.moveTo(75, 0);
ctx.lineWidth = 4;
ctx.strokeStyle = "#89f086";
ctx.lineTo(80, 0);
ctx.stroke();
}
// 時針刻度
for (let i = 0; i < 12; i++) {
ctx.beginPath()
ctx.rotate(p / 6)
ctx.moveTo(70, 0)
ctx.lineTo(80, 0)
ctx.stroke()
}
ctx.save(); // 保存畫布變換之后的狀態
// 秒針
ctx.beginPath();
ctx.rotate(s * (p / 30));
ctx.lineWidth = 2
ctx.strokeStyle = '#ff6700'
ctx.moveTo(0, 0);
ctx.lineTo(80, 0);
ctx.stroke();
// 恢復之前的狀態再保存,時針、分針、秒針都是基于原點以及畫布方向變換后繪制
ctx.restore();
ctx.save();
// 分針
ctx.beginPath();
ctx.rotate(m * (p / 30));
ctx.lineWidth = 3;
ctx.strokeStyle = '#6700ff'
ctx.moveTo(0, 0);
ctx.lineTo(70, 0);
ctx.stroke();
ctx.restore();
// 時針
ctx.beginPath();
ctx.rotate(h * (p / 6));
ctx.lineWidth = 4;
ctx.moveTo(0, 0);
ctx.lineTo(60, 0);
ctx.stroke();
ctx.restore(); // 恢復畫布最初狀態
document.querySelector('div').innerText = `Now:${h} : ${m} : ${s} ${hour > 12 ? 'pm' : 'am'}`
window.requestAnimationFrame(clock);
}
clock();
5、小結
隨著互聯網的高速發展,用戶對頁面的視覺和交互有著越來越高的要求,傳統的web開發無法得到滿足,利用Canvas強大的繪圖能力,可以讓網頁顯示的內容更加的豐富多彩,也能給用戶帶來更好的視覺體驗。
作者:LLS-FE團隊
來源:微信公眾號:流利說技術團隊
出處:https://mp.weixin.qq.com/s/bvkx3wOeMvIUU64cktX6iA
*請認真填寫需求信息,我們會在24小時內與您取得聯系。