推荐JS图片预览,总结JavaScript图片预览效果的做法(附源码下载)(3)
【nsIDOMFile接口】
ff从3.0(或更早)开始,就不能通过value获取file的完整路径,也不支持直接用本地路径显示图片。
不过欣喜的是,它同时也提供了nsIDOMFile接口,能更好地获取文件数据。
在ff的file控件有一个FileList对象,包含了带nsIDOMFile接口的File对象。
ps:FileList对象貌似是一个NodeList对象,但目前只能用第一个,可能是为了将来实现一个file控件选择多个文件的功能预留的。
这个File对象有三个获取文件数据的方法:
getAsText:获取文件的文本数据,可以通过参数设置编码;
getAsDataURL:获取文件的Data URI数据;
getAsBinary:获取文件的二进制数据。
其中getAsDataURL获得的Data URI数据可以用于显示图片,_domfileData中就是用它来获取数据的。
File对象还有支持两个属性:fileName(文件名,不包括路径)和fileSize(文件大小)。
相关具体说明参考mozilla的File和nsIDOMFile。
【Data URI 和 MHTML】
上面已经多次提到Data URI,详细介绍请看秦歌的“Data URI 和 MHTML”。
Data URI的主要作用是以字符代替数据,从而把文件“嵌”在代码里。
除了ie,其他浏览器基本都很好的支持了Data URI。
ie8也有限度地支持,详细参考msdn的data Protocol。
由于opera,safari和chrome需要remote模式的浏览器都支持Data URI,所以程序返回的是Data URI形式的数据。
相比返回路径的方法,返回Data URI不需要创建文件,还少一次HTTP请求。
ps:ie8只支持32k的Data URI数据,在ie8使用时要注意数据大小。
在filter模式需要一个透明图片来去掉img默认显示的小图标,一般的方法需要一个图片文件。
为了“省下”这个文件,可以使用Data URI来做一个1*1的透明图片:
支持Data URI的情况下,只要把img的src设置为这个值就可以显示一个透明图片了。
虽然ie6/7不支持Data URI,还可以用mhtml来代替。
在ImagePreviewd.js开头有一段注释了的代码:
--_CLOUDGAMER
Content-Location:blankImage
Content-Transfer-Encoding:base64
R0lGODlhAQABAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAABAAEAAAICVAEAOw==
这是mhtml记录数据的形式,调用时要按以下格式设置img的src:
mhtml:文件完整路径!blankImage
其中blankImage表示要获取的数据在文件对应的Content-Location。
问题在于如何获得script(js文件)的完整路径(包含http开头的路径)。
首先要在脚本运行时获取,当前运行的script肯定是document.scripts的最后一个:
document.scripts[document.scripts.length - 1]
ps:document.scripts详细参考msdn的scripts Collection,ff不支持,可以用getElementsByTagName("script")兼容。
接着可以利用getAttribute从src获取script的完整路径:
ie6/7的getAttribute支持第二个参数,设为4表示返回完整路径的url地址,详细参考msdn的getAttribute Method。
结合Data URI 和 MHTML可以这样得到透明图片数据:
"mhtml:" + document.scripts[document.scripts.length - 1].getAttribute("src", 4) + "!blankImage" :
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
使用时要注意:
脚本必须单独另存为一个文件,作为mhtml需要的文件路径。
要自动获取完整路径需要用script标签链接文件。
【超空间】
程序还有一个dispose方法用于销毁程序。
包括这几个部分:
_upload上传文件对象:它本身已经有一个dispose方法来销毁程序;
_preload预载图片对象:先清除它的onload/onerror事件再移除元素;
file和img属性:直接设为null,由于不是程序创建的元素,留给使用者来移除。
说到移除元素,顺便说一下超空间(DOM hyperspace),这是从“ppk谈javascript”中看到的。
大概指的是当元素不在dom里面,而js又有关联时,元素并不会消失,而是保存在一个称为“超空间”的地方。
详细参考书的DOM 超空间部分。
书中还说可以根据是否有parentNode来判断元素是否在超空间,但测试以下代码:
<script>
var elm = document.createElement("div");
alert(elm.parentNode);
document.body.removeChild(document.body.appendChild(elm));
alert(elm.parentNode);
</script>
第一次parentNode都是null,没有问题,按理第二次也应该是null,但ie却是一个object。
经测试,这个object的nodeType是11,也就是一个碎片对象(FRAGMENT)。
而且各个被removeChild移除的元素的parentNode都不相同,即会生成不同的碎片对象。
这种情况算不算在“超空间”呢,不过书中也只是说“一般来说”,也不用太考究。
那么用innerHTML清除呢?再测试以下代码:
<script>
var elm = document.getElementById("test");
document.body.innerHTML = "";
alert(elm.parentNode);
</script>
结果在ie也是null了,看来removeChild和innerHTML在清除元素时产生了不同的结果。
那个碎片对象貌似没什么用(难道为了保证有parentNode?),那是不是innerHTML就一定比removeChild好呢?
再测试以下代码:
<style>div{border:1px solid #000; height:20px;}</style>
<span><div id="test1">test1</div></span>
<span><div id="test2">test2</div></span>
</body>
<script>
var div1 = document.getElementById("test1"), parent1 = div1.parentNode;
parent1.removeChild(div1);
alert(div1.tagName + ":" + div1.innerHTML);
parent1.appendChild(div1);
var div2 = document.getElementById("test2"), parent2 = div2.parentNode;
parent2.innerHTML = "";
alert(div2.tagName + ":" + div2.innerHTML);
parent2.appendChild(div2);
</script>
当使用removeChild时,移除元素的结构并没有发生变化,各个浏览器的效果都一样。
而使用innerHTML清除时,其他浏览器的效果跟removeChild一样,但在ie被移除的元素就只剩下一个“外壳”了。
个人推测,ie在使用innerHTML时,被移除的元素会变成一个个单独的元素,失去了彼此的联系。
形象点说就是removeChild是直接掰断树枝,还能继续嫁接使用,而innerHTML是把需要的树叶节点取下来,再把树枝烧掉。
ps:仅仅是推测,谁有官方资料请告诉我。
那么removeChild的好处是移除的元素能再次使用,兼容性好,不好的地方是ie会产生一个没用的碎片对象。
而innerHTML的好处是不会产生多余的碎片对象,方便高效,但在ie被移除的元素基本不能再用,有兼容性问题。
那就可以根据需要使用不同的方法了,至于防止内存泄漏用那个好,感觉是innerHTML,但没有更深入研究的话还说不清楚。