问题
之前基本看完了canvas的API,准备做一个小游戏,emmm,然后今天就遇到了问题,我本来以为是canvas的问题,但是仔细分析之后才发现是JS中的问题:
回顾
关于HTML与JS的关系可以从网页的加载顺序来看,当浏览器打开网页的时候,网页由上至下的解析该网页,然后开始构建DOM树(Document Object Model)(属于JavaScript),也就是说将HTML的每一个标签构建成一个相应的对象,但是DOM整体就是一个对象,每一个标签是其的一个子对象,对比与JSX有些相似,遇到JS或CSS时利用相应引擎进行解析。从这里就可以理解出,在网页完成加载后,每一个标签就成为了DOM这个对象里的一个子对象,我们使用相应的方法,比如Document.getElementById(“xxx”)就可以获取相应的对象,并进行操作。
Image()对象
由上可知,Image()也是一个节点对象,只是是否渲染至页面上的问题。
Image对象的属性
属性 | 描述 | 属性 | 描述 |
---|---|---|---|
align | 设置或返回与内联内容的对齐方式。 | align | 设置或返回与内联内容的对齐方式。 |
alt | 设置或返回无法显示图像时的替代文本。 | alt | 设置或返回无法显示图像时的替代文本。 |
border | 设置或返回图像周围的边框。 | border | 设置或返回图像周围的边框。 |
complete | 返回浏览器是否已完成对图像的加载。 | complete | 返回浏览器是否已完成对图像的加载。 |
height | 设置或返回图像的高度。 | height | 设置或返回图像的高度。 |
hspace | 设置或返回图像左侧和右侧的空白。 | hspace | 设置或返回图像左侧和右侧的空白。 |
id | 设置或返回图像的 id。 | id | 设置或返回图像的 id。 |
isMap | 返回图像是否是服务器端的图像映射。 | isMap | 返回图像是否是服务器端的图像映射。 |
longDesc | 设置或返回指向包含图像描述的文档的 URL。 | longDesc | 设置或返回指向包含图像描述的文档的 URL。 |
lowsrc | 设置或返回指向图像的低分辨率版本的 URL。 | lowsrc | 设置或返回指向图像的低分辨率版本的 URL。 |
name | 设置或返回图像的名称。 | name | 设置或返回图像的名称。 |
src | 设置或返回图像的 URL。 | src | 设置或返回图像的 URL。 |
useMap | 设置或返回客户端图像映射的 usemap 属性的值。 | useMap | 设置或返回客户端图像映射的 usemap 属性的值。 |
vspace | 设置或返回图像的顶部和底部的空白。 | vspace | 设置或返回图像的顶部和底部的空白。 |
width | 设置或返回图像的宽度。 | width | 设置或返回图像的宽度。 |
标准属性
属性 | 描述 |
---|---|
className | 设置或返回元素的 class 属性。 |
title | 设置或返回元素的 title。 |
Image对象的事件句柄
事件句柄 | 描述 |
---|---|
onabort | 当用户放弃图像的装载时调用的事件句柄。 |
onerror | 在装载图像的过程中发生错误时调用的事件句柄。 |
onload | 当图像装载完毕时调用的事件句柄。 |
浅谈图片加载
complete属性:图片显示出来之后其属性由false
变为true
在这个属性上,IE与其他浏览器有所不同,IE是根据图片的src属性是否是一个有效的图片地址值。即,在IE下,只有当image()对象的src值为一个正确的图片地址时,complete值才为true,否则一直为false,但是在其他浏览器下,当image()对象呗定义完成的时候,其complete值就为true,不会检测其src属性的有效性
也就是说:complete这个属性在IE系浏览器与其他浏览器的判断方式不一样,所以对于适配性来说,最好不用
onload()回调事件
onload()是当图像装载完毕后调用的函数。关键是如何才算是装载完毕,经测试,
- 当我们直接在HTML中定义一个标签,并且在其中规定其onload事件,只有当src属性填写正确时,才算是装载完成,否则onload事件一直不触发,直至修改其src值为有效图片地址的时候才算是装载完成。
- 当我们在JS代码中手动生成一个Image()对象时,无论其src值是否被赋值为有效的图片地址,其onload事件总会触发,即当我们手动生成一个 Image 对象时,一旦其被生成,则认定为装载完成,立即调用onload事件。至于原因,我还没弄明白,看来需要深入的去了解一下浏览器机制,才能明白这一点。
Canvas中悟到的Image对象的用发法
ctx.drawImage(img,x,y)中的Img应当是一个Image对象,可以是从DOM树中获取的Image()子对象,也可以是一个自己定义的Image()对象,不管是这两者中的哪一者,再绘制到画板上时,都必须将绘制语句写到该对象的
onload()
函数中,其原因在于,Image()的定义是一个异步执行的操作,也就是说,在执行到let img = new Image()
时,JS引擎不会等待该语句执行完毕在继续执行,而是直接继续执行,在对象定义完成时,就会自动调用其onlaod.onerror,onabort回调事件。那么问题就是,可能在对象还没有定义完成的时候,已经执行到了ctx.drawImage()
这一条语句,所以绘制不出任何效果(而且大部分情况下,JS语句的执行都是快于对象的定义,所以基本上都绘制不出图形)。所以所有的Canvas图形绘制都必须是在onload中进行的。问题又出现了,那如果要就行较大型的图形绘制,我不可能把所有代码都写到一个函数里吧,所以再写多个函数时就要多次调用onload事件,但是一个图片的onload事件只能执行一次。所以我想到了一种错误的方法,在不同寒暑表中多次载入同一张图片,并分别调用其回调函数onload,结果当然是失败了。这种情况下onload事件的顺序是无法确定的,即使你是前后分别执行,因为onload函数是异步进行的,现在惟一的办法就是把这张图片统一载入,然后想办法将下面的代码都建立在onlaod函数执行的前提下。然后我找到了一种方法:
let img = new Image();
img.addEventListener(‘onload’,’start’,’true’);function start(){
draw1();
draw2();
}
fucntion draw1(){
ctx.drawImage();
…
}fucntion draw1(){
ctx.drawImage();
…
}
上面这么写的好处在于,所有的函数后在onload回调函数之后执行了,确保了绘图的正确性。
Last but not least
我在网上搜了很久,在讲解onload()事件时基本就一篇文章,被抄了无数次,但是我试验却发现和文章中的结果完全不一样。文章中写到,即使是在JS代码中定义一个Image()对象时,只要src不赋值,其onload事件就不会触发,但是~像我上面实验的那样,我试验了很多次,都是那样的结果,测试了Chrome,Firefox,IE,Edge,每个浏览器的测试都一样。可能是那篇文章已经是很久之前的了,但是这些同学从未自己试过,直接就抄过去了。所以实践才是真理啊。