Canvas从入门到实战
1、什么是Canvas?
HTML5 提供Canvas API,其本质上是一个DOM元素,可以看成是浏览器提供一块画布供我们在上面渲染2D或者3D图形。由于3D绘制上下文(webgl)目前在很多浏览器上兼容性较差,所以我们一般用于绘制2D图形。
2、为什么使用Canvas?
Canvas是HTML5引入的标签,在此之前我们通常会使用SVG来绘制一些图形,那么两者之间有什么区别呢?SVG可缩放矢量图形(Scalable Vector Graphics)是基于可扩展标记语言XML描述的2D图形的语言,两者部分区别:
SVG 图像是使用各种元素创建的,这些元素分别应用于矢量图像的结构、绘制与布局;而Canvas本身并不描述图像,而是通过Javascript完成绘制; 如上所述,SVG本身是DOM元素,每一个描述元素也是DOM元素,浏览器在进行渲染时需要进行大量计算以处理每一个元素;而在渲染Canvas的过程中,浏览器只需要渲染一张画布,其余的是通过Javascript引擎执行逻辑来绘制; SVG(矢量图)不依赖分辨率,放大不会失真;而Canvas(位图)依赖分辨率,放大会失真;
由于Canvas是通过Javascript来完成绘制的,所以可控性很强,我们可以比较精确的控制图形渲染的每一帧;从另一方面来说,如果在高频率渲染中要处理过多的DOM元素就意味着性能一定不会太好,渲染速度会下降很多。Canvas的高性能能够保障复杂场景中图形的渲染效率,所以目前很多领域都会使用Canvas,例如动画、游戏图形、数据可视化、照片处理和实时视频处理等。
3、Canvas的基本使用
要使用Canvas,我们需要先获取Canvas元素的引用继而通过getContext()方法获取图形的绘制上下文。 const canvas = document.getElementById("canvas") const ctx = canvas.getContext("2d")
获取到图形绘制上下文后,我们就能使用CanvasRenderingContext2D接口上的绘图API了,接下来我们可以了解一些比较常规的使用。
3.1、画布属性:
width、height:画布的宽度以及高度,默认大小为300x150; fillStyle:填充图形的样式,值可以是color string、CanvasGradient对象; strokeStyle:轮廓图形的样式,值可以是color string、CanvasGradient对象; lineWidth:绘制线条的宽度; globalAlpha:画布的透明度,0-1的偏移值; globalCompositeOperation:画布中新老图形重叠时的渲染方式,默认为source-over,新图形覆盖老图形; ...... 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、绘制图形:
.fillRect(x,y,width,height):绘制一个填充的矩形,矩形左上角的坐标为(x,y),高宽分别为width、height; .strokeRect(x,y,width,height):绘制一个矩形边框,矩形左上角的坐标为(x,y),高宽分别为width、height; .clearRect(x,y,width,height): 清除指定矩形区域,让清除部分完全透明; 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、绘制路径:
.beginPath():开始一段路径的绘制 ; .closePath(): 从起始点到当前点,结束路径的绘制,非必需; .fill():根据路径生成填充图形; .stroke():通过路径生成轮廓图形; .moveTo(x,y):声明一段路径的起始点; .lineTo(x,y):绘制一条从当前坐标到(x,y)的线; 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(x,y,radius,startAngle,endAngle,anticlockwise):画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针,false)来生成; arcTo(x1,y1,x2,y2,radius):根据给定的两条切线中的一组切点坐标生成半径为radius的圆弧;
注意: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、渐变对象:
.createLinearGradient(x1, y1, x2, y2): 创建一个沿参数坐标指定的直线的渐变,开始坐标为(x1,y1),结束坐标为(x2,y2); .createRadialGradient(x1, y1, r1, x2, y2, r2):创建 根据参数确定两个圆的坐标的放射性渐变,开始圆形圆心为(x1,y1),半径为r1;结束圆形圆心为(x2,y2),半径为r2;
创建好渐变对象之后,可以通过渐变对象上的 .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、像素操作:
.drawImage(image,x,y,width,height):image可以是image对象、canvas元素、video元素; .getImageData(x,y,width,height):获取坐标为(x,y)一定区域内图像的像素数据; const p = document.querySelector("p") 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})` p.innerText = `color: ${color}`; p.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、画布状态:
.save():将当前画布的状态推入到栈中,例如fillStyle、2D转换等; .restore():将栈顶元素弹出,恢复上一次推入栈中画布的状态;
当我们需要通过空间转换来绘制图形时,保存与恢复画布的状态是很关键的,因为我们是在同一块画布上绘制图形,而变换都是基于画布的,这与我们平时使用到的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、几何变化:
.translate(x,y):画布默认的原点是(0,0),此方法可以切换原点到(x,y)而不需要手动更改绘制图形的坐标; .rotate(angle):将画布旋转一定的角度,angle单位为弧度; .scale(sx,sy):sx为水平方向的缩放比例,sy为竖直方向的缩放比例; .transform(a,b,c,d,e,f):依次为水平缩放、垂直倾斜、水平倾斜、垂直缩放、水平移动、垂直移动;
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("p").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
WebRTC音频引擎实现分析WebRTC的音频引擎作为两大基础多媒体引擎之一,实现了音频数据的采集前处理编码发送接收解码混音后处理播放等一系列处理流程。本文在深入分析WebRTC源代码的基础上,学习并总结其音
感染之后咳嗽不止,这些止咳小妙招总有适合你的一食疗法1梨子汤,梨子红枣冰糖加枸杞(可不加)加适量水煮沸。2盐蒸橙子,将橙子切开口,用牙签戳数洞,撒上少许盐,上锅蒸熟。二耳穴压豆取穴鼻咽喉气管肺肾脾肾上腺皮质下咳喘点。每次取一
男子在梵净山金顶摩崖刻4字被判赔12万,二审维持原判新时代推动法治进程,2022年度十大案件候选案例中,包括一起针对生态破坏的公益诉讼案。在这起案件中,当事人在梵净山景区文物上刻字,被当地检察机关提起公益诉讼。一段拍摄于2021年7
绿色林海的别样风景闪耀在庐山之巅的火焰蓝今年的庐山有一道别致的风景,在茫茫的绿色林海中,一群阳光帅气的蓝朋友在庐山周边执行森林防火执勤任务。2022年12月10日,内蒙古森林消防总队派出了内蒙古大兴安岭森林消防支队300
15条精品线路发布!你去过哪些?为贯彻落实大运河文化带建设工作部署,积极促进水利风景资源保护利用,助力全省幸福河湖建设,近日,江苏省水利厅遴选推出故道千里远,故事千年传等15条水利风景区精品线路,串联全省76家特
临汾黄河一号旅游公路主线实现贯通1月3日,山西晚报记者从临汾市交通运输局了解到随着黄河一号旅游公路主线吉县境内大宁界至姚家畔段冯家庄大桥近日完成架设,该市黄河一号旅游公路主线实现全线贯通。临汾市黄河一号旅游公路主
江苏出土汉代双龙墓,开棺后竟冒出一条大白腿,专家看后毛骨悚然江苏连云港惊现千年不腐女尸,专家挖到一半时仓皇而逃一座古墓诉说千年传说,一段传说传承古今历史。2002年的炎炎盛夏,江苏连云港一村庄正在修建高速公路,工人们挖到了一块木板,细细一看
元旦经济堂食小高峰出现,部分餐厅客流同比政策优化前增长1300随着全国防疫政策的优化以及稳增长促消费等政策信号释放,2023年元旦假期内,不少餐饮品牌的客流增长超预期,全国多店恢复排长队的热闹场景。不断刷新的叫号提示是餐饮消费加速回暖的缩影。
又要囤药?这种药品家里有孩子的禁用,小心吃了之后不长个冬日生活打卡季随着疫情的精准调控实施,国人们已经感受到了它的威力。尤其是在彻底放开之后,更是让越来越多的人都确诊了阳性。感染后,会出现发烧咳嗽等症状,而随着病毒的变异,新的症状也陆
揭秘!人工授精实验室如何挑选精兵强将人工授精是一种人工辅助生殖技术,是将优化后的精子注射入女性的生殖道内或宫腔内,达到受孕的目的。人工授精主要适用于男方勃起性功能障碍严重早泄轻中度的少弱精子症免疫性不育女方宫颈因素及
怀孕5周的媳妇阳了后胎停流产了中心妇产医院,张明宇陪着媳妇王敏来到计划生育科做孕检,看看孩子的发育状态怎么样。两人高高兴兴地一边办理手续,一边打趣孩子会像谁。随后王敏进入候诊区等待,张明宇则满怀希望的在外面等候