意见箱
恒创运营部门将仔细参阅您的意见和建议,必要时将通过预留邮箱与您保持联络。感谢您的支持!
意见/建议
提交建议

对于“前端canvas骚操作”的一些理解

来源:恒创科技 编辑:恒创科技编辑部
2024-02-03 14:57:59


canvas ,作为HTML5的「画布」,canvas强大的功能将一直伴随H5走下去。

前端使用canvas一般有两种方式:


对于“前端canvas骚操作”的一些理解

<canvas class="canvas" width="" height=""></canvas>

然后通过JavaScript去操控一些属性比如
文字:

ctx.fillText("一段文字",左上角X坐标值,左上角Y坐标值);    //填充
//或
ctx.strokeText("一段文字",左上角X坐标值,左上角Y坐标值); //镂空

图片:

ctx.drawImage(img对象,左上角X坐标,左上角Y坐标);

线条:

ctx.moveTo(0,0);   //移动到【画布的】(0,0)坐标处“落笔”
ctx.lineTo(100,100); //从画布的(0,0)处“画”到(100,100)处

或图形:

ctx.arc(左上角X,左上角Y,圆心坐标,弧度,false顺时针/true逆时针);
ctx.stroke();
//
ctx.strokeRect(左上角X,左上角Y,,);

当然,上面这些都有一个“前提”:

//获取canvas元素
var canvas=document.querySelector('.canvas');
//获取“上下文”对象
var ctx=canvas.getContext("2d");

这第二种方式就是【JavaScript动态生成canvas并控制】了:

var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');

(这种方式倒是经常用在“需要转为图片再追加到页面上”的场景下)
这些基础内容就不再多说。
这里要说的是【图片和canvas的“爱恨情仇”】,各位瓜子备好:

上面说到img在canvas的使用方法:​​ctx.drawImage(img对象,左上角X坐标,左上角Y坐标);​​ img对象哪里来的呢?通常是我们自行创建的:

var img=new Image()
img.src=xxx;

对了,这里说的是img对象 !在实际使用中,你可能经常能看到下面这样的代码(你可能已经非常熟悉):

var img=new Image()
var imgt=document.querySelector('img')
img.src=imgt.getAttribute("src")

这样就行了吗?就可以操作了吗?

这里其实有一个“致命的错误”:img对象是需要【等待加载】的 —— 请想一下,在网页优化中,是不是经常用「 ​​document.readyState == "complete"​​​ 、​​window.addEventListener('DOMContentLoaded',function(){})​​​ 」之流来处理“游离于文档流之外的”图片加载请求?
所以我们在处理JavaScript中的图片对象时,也要这么做:

img.onload=function(){
}

然后再在其内部实现对图片的操作,尤其是canvas操作

笔者在做一个图片处理的工具时就遇到了这样的问题:因为图片合成时以一张图片为“原始图”,另一张图片做“背景”,那么我们就要让最后的图片和“第一张图片”的大小保持一致:这就需要获取到“原始图的宽高”了:

cid=document.querySelector('img[id="iim"]').offsetWidth
chd=document.querySelector('img[id="iim"]').offsetHeight

(img[id=“iim”]是其中一个js创建的图片的id,用于区分其它图片)
但这时笔者遇到了当时的一个难题:在加载图片后直接获取宽高在生成的canvas里时而能取到时而没有值!
当时笔者以为是其它原因,就加了个默认替换值;但是后来偶尔写到其它项目时发现了一点:我没有考虑图片加载 —— 如你所想:它是个异步的过程!
于是,我将上面这段代码“抽离”出主要逻辑,单独拿到onload中去:

imgt.onload=function(){
// console.log(imgt)
cid=document.querySelector('img[id="iim"]').offsetWidth
chd=document.querySelector('img[id="iim"]').offsetHeight
}

这样果然就没有再错过!

canvas操作还有一个比较【蜜汁操作】的点:canvas像素级操作!

let imageData = ctx.getImageData(左上角X坐标,左上角Y坐标,,)
ctx.putImageData(imageData,左上角X坐标,左上角Y坐标)

get和put的基础是“存在canvas画布” —— 你可以通过像素级操作新的API:​​ctx.createImageData(宽,高)​​ 或者仍然通过drawImage()来完成。

哦,这里说到像素级操作可并不是“无的放矢”:它里面有一个非常重要的操作 —— 图片透明度处理Alpha
我们应该知道:一个像素占4个单位——在imageData(的data)中,这四个单位分别被称之为

rgbA:Alpha,透明度,0—255,0为全透明,255为不透明

这里要吐槽一句:本来写这个的博客就不多,其中一些博主还在用0-1表示透明度A。当时被坑惨了。。。

比如笔者在项目中就这样用的:

contextt.drawImage(fimg2, 0, 0, vid, vhd);
let imageData = contextt.getImageData(0, 0, vid, vhd)
, data = imageData.data
//对像素集合中的单个像素进行循环,每个像素是由4个通道组成,所以是 i=i+4
for(let i = 0; i < data.length; i+=4) {
//let r = data[i]
// , g = data[i+1]
// , b = data[i+2]
data[i+3] = 80
}
contextt.putImageData(imageData, 0, 0)

如上面我们得到canvas-RGBA的值后其实还可以进行一些“特殊操作”,比如将png图的白色背景转为透明:

//在for循环内
if([r,g,b].every(v => v < 256 && v > 245)) data[i+3] = 0

哦对了,getImageData 似乎还经常玩“跨域”。

HTML5为一些元素提供了支持CORS(跨域资源共享)的属性,这些元素包括​​<img>​​​,​​<video>​​​,​​<script>​​等,而提供的属性名就是crossOrigin属性。

所以,canvas中遇到“跨域问题”时我们可以在img.onload前加一行代码:

img.crossOrigin = '';

或者是在ajax请求中的跨域,按照张鑫旭大大所说:“不是直接通过new Image(),而是借助ajax和URL.createObjectURL()方法曲线救国。”

//ajax的回调中
let url=URL.createObjectURL(response)
var img=new Image()
img.onload=function(){
}
img.src=url

本文和笔者之前写的另一篇文章:​​H5 canvas基础​​ 完全不冲突,可放心食用。


做的一个小工具:​​在线图片处理工具​​​ 已开源到 ​​GitHub​​ 上(欢迎访问和star!)

说说这个工具
其中“图片合成”部分实现本来想用CSS3的​​​cross-fade()​​ 配合CSS3变量,通过JS每次将图片“替换”到对应位置,开始迷之自信感觉这个思路实现简单一些,但是前两天无论如何也没有实现(最坑的是也不报错断点也没看出来有啥问题。。。)。后来于是还是采用了上个版本的“input控制上传、图片追加进显示框以及canvas多次写入”的方式。

想要过段时间把这个搞成一个插件,有哪位dalao愿意加入一起继续扩展维护重构这个项目的或者有这部分解决方案的欢迎告知!


上一篇: 让iframe为项目增加更多可能性 下一篇: 手机怎么远程登录云服务器?