1、什么是Canvas?
html5 提供Canvas API,其本质上是一个DOM元素,可以看成是浏览器提供一块画布供我们在上面渲染2D或者3D图形。由于3D绘制上下文(webgl)目前在很多浏览器上兼容性较差,所以我们一般用于绘制2D图形。
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 = 300ctx.height = 300ctx.fillStyle = '#fff'ctx.strokeStyle = 'blue'ctx.lineWidth = 5ctx.globalAlpha = 0.3ctx.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 = gradientctx.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)点对应的imageDataconst color = `rgba(${data[0]},${data[1]},${data[2]},${data[3] / 255})`div.innerText = `color: ${color}`;div.style.backgroundColor = colorfunction 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 = 2ctx.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