一篇文章《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小时内与您取得联系。