图像替换技术(The State Scope Method)
转自:http://www.cnblogs.com/rubylouvre/archive/2009/08/06/1539977.html
感谢蓝色理想的dishuipiaoxiang 的译文,让我了解到这种崭新的图片替换方法。注意,是图片替换而是图片轮换。相信每一个WEB设计师都要经常用到它!当我们要用到一些特别的字体做LOGO,商标与Banner时,为了解决用户机不存在这种字体时就只有用图片替代或者使用sIFR方案(@face与eot字体都不靠谱),当然前者是比后者常用得多,也简单得多。而图片替换大法分很多种,如直接隐藏文字法,margin移位法,文本缩进法,容器零高度零宽度法……等等。下面,是我根据原译者的文章结合我的理解,重新讲述如何使用此方法。
新的方法,这种被原作者Paul Young称之为The State Scope Method的图片替换技术,思路非常独特,是把整个文档当成一个状态机,通过监视它的状态来绑定或删除它上面的某个类,而这个类携带着显示某个区域的背景图片的信息。换言之,这个类是后期添加上去的,但相对一般的JS动态添加,它却又早得多了,因为它是绑定在最顶层的元素html上的!可能在这里,许多人都被搞晕,包括原译者,所以他给出的示例才运行不了。这涉及比较深层次的编程理念,原作者对此也大论了一番设计模式……不过没关系,我们可以细细分析。
h1 {
width: 100px;
height: 50px;
}
@media screen {
.images-on h1 {
text-indent: -10000px;
background-image: url(image.png);
overflow: hidden;
}
}
第一条CSS规则总是执行的,h1就是我们所说的要添加背景图片的区域。(原译者翻译The State Scope Method为“状态域法”,虽然看起来比较有味,但不知所云,为了见名达义,我译作“状态区域法”,状态是指html元素的状态,区域是指添加背景图片的区域,这样称呼是不是易懂些呢!)
第二条是选择执行,它看起来有点复杂,整个包围在@media screen块中,它是用来保证图像替换只发生在屏幕阅读器中,而不是在打印状态下执行。如果不这样处理,页面打印时,多数用户将看到一个很大的空隙而不是有意义的文本。不过如果我们不打印,它就可有可无了,原作者Paul Young给出的示范页就没有@media screen块了。抛开@media screen不谈,我们发现里面是个后代选择器(Descendant selectors),亦有人称之为包含选择符,于是这些背景显示信息是否执行处理,就关键在于它(h1)的祖先(.images-on)是否存在了!我们可以通过addClass与removeClass为html动态添加或删除.images-on类。
下面是核心代码:
var hasClass = function(ele,cls) {
return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
}
var addClass = function(ele,cls) {
if (!this.hasClass(ele,cls)) ele.className += " "+cls;
}
var removeClass = function(ele,cls) {
if (hasClass(ele,cls)) {
var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
ele.className=ele.className.replace(reg,' ');
}
}
/**
*@scope是某个区域中设置如何实现图片替换的类,是要绑定于html元素上
*@on为html元素的状态,用于动态绑定或删除上面的scope类
*/
document.enableStateScope = function(scope, on) {
var de = document.documentElement;
on ? addClass(de,scope) : removeClass(de,scope);
};
那么剩下的问题就是如何监视html元素的状态或判定html元素的状态。这个实现实在很巧妙,它通过检测Image对象是在发生onerror事件来为on赋true值或false值(根据javascript的事件传播机制,子元素的大多数低级事件会冒泡到上一级元素直至最顶层元素,如果该元素也有处理此事件的能力就执行此事件)。很明显,onerror是个很原始的事件,一处发生错误,整个文档就会报错。根据我们上面的提法,Image对象 onerror的状态就是html元素的状态,并且判断html元素的状态,远比通过遍历DOM树后才能定位到背景图片所在的元素,再进行判定要快!
既然是检测Image对象,那么我们首先要知道此元素是否存在,但我们不是检测它是否存在于服务器端,那会导致一次额外的http请求。作者创建了一个巧妙的方法。
在大多数浏览器中,Image对象可以实例化并会自动追加一个无效的URL(http://0),通过它我们就很容易判断这图片是否可用。因为如果是这样,就会触发onerror事件,那么我们就把on设置为false,否则为true。这此,我们可以在JS中,动态创建一个Image对象。
var img = new Image();
但是,有两个游览器对此方法并不兼容。在Gecko内核浏览器中(如FF),不论Image是否可用,总是会激发onerror事件,因此我们原来的判定方案就行不通了。幸好,我们找到另一个方法。我们可能为html元素添加一个无效的背景图片,然后通过getComputedStyle方法获得其 style.backgroundImage值,如果图片不可用,则此值为 none或者url(invalid-url:)。这时,我们就可以放心给on设置为false了!
if (img.style.MozBinding != null){ /*判断是否为火狐*/
/*强制设置图片的Url为 http://0 */
img.style.backgroundImage = "url(" + document.location.protocol + "//0)";
/*获取样式表应用到页面元素的最终结果值*/
var bg = window.getComputedStyle(img, '').backgroundImage;
if (bg != "none" && bg != "url(invalid-url:)" || document.URL.substr(0, 2) == "fi"){
/**
*如果图片的Url值不为 none与url(invalid-url:),或者地址栏不为file:///
*那么设置on为true
*/
document.enableStateScope("images-on", true);
}
}
另外一个富有挑战性的浏览器是safari,如果请求是一个无效的URL,safari的状态栏将出现错误提示,但页面布局不受任何影响。如果用户的状态栏处于开启状态,报错将一直持续,这很不专业,为此,作者提出了另外一种可行的方案。通过base64编码动态生成一个1*1的gif图片,来阻止一直报错。如果图片不可用,它的宽度将为零,我们可能用它作为我们判定的标准。
if (img.style.MozBinding != null) { /*判断是否为火狐*/
/************略************/
}else {
img.style.cssText = "-webkit-opacity:0";
if (img.style.webkitOpacity == 0) { /*判断是否为safari*/
img.onload = function(){
/*如果图片的宽大于零,证明图片可用,我们把on设置为true,否则为false*/
document.enableStateScope("images-on", img.width > 0);
}
/*动态生成gif图片,预防因为图片不存在,一直报错!*/
img.src = "data:image/gif;base64,"
+ "R0lGODlhAQABAIAAAP///wAAACH5BAE"
+ "AAAAALAAAAAABAAEAAAICRAEAOw==";
}
}
最后,对于其它浏览器,在开始初始化Image对象时,仅需检测onerror事件是否发生。
if (img.style.MozBinding != null) {
/************略************/
}else {
if (img.style.webkitOpacity == 0){
/************ 略************/
}else{
img.onerror = function(e) {
document.enableStateScope("images-on", true);
}
/*取消onerror 事件 */
img.src = "about:blank";
}
}
下面给出完整方法,利用闭包保持enableStateScope方法一直存在下去!
(function(){
d=document;e=d.documentElement;c="images-on";i=new Image();t=i.style;s=d.enableStateScope=function(s,o){
if(o)e.className+=" "+s;else e.className=e.className.replace(new RegExp("\\b"+s+"\\b"),"");
};if(t.MozBinding!=null){
t.backgroundImage="url("+d.location.protocol+"//0)";b=window.getComputedStyle(i,'').backgroundImage;if(b!="none"&&b!="url(invalid-url:)"||d.URL.substr(0,2)=="fi")s(c,true);
}else{
t.cssText="-webkit-opacity:0";if(t.webkitOpacity==0){
i.onload=function(){
s(c,i.width>0);
};i.src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
}else{
i.onerror=function(){
s(c,true);
};i.src="about:blank";
}
}
})();
<!doctype html> <html dir="ltr" lang="zh-CN"> <head> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <style type="text/css"> </style> <script type="text/javascript">/*<![CDATA[*/ var hasClass = function(ele,cls) { return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)')); } var addClass = function(ele,cls) { if (!this.hasClass(ele,cls)) ele.className += " "+cls; } var removeClass = function(ele,cls) { if (hasClass(ele,cls)) { var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)'); ele.className=ele.className.replace(reg,' '); } } // Don't copy and paste this code, use the minified script document.enableStateScope = function(scope, on) { var de = document.documentElement; on ? addClass(de,scope) : removeClass(de,scope); }; (function(){ var de = document.documentElement; var img = new Image(); // 针对于 Gecko内核游览器的处理 if (img.style.MozBinding != null){ img.style.backgroundImage = "url(" + document.location.protocol + "//0)"; var bg = window.getComputedStyle(img, '').backgroundImage; //如果images为off,则在FF2以及其旧版本中,bg的值为 "none" //在FF3中,bg的值为"url(invalid-url:)" if (bg != "none" && bg != "url(invalid-url:)" || document.URL.substr(0, 2) == "fi"){ document.enableStateScope("images-on", true); } }else{ //针对于Safari游览器(包括 iPhone)的处理 img.style.cssText = "-webkit-opacity:0"; if (img.style.webkitOpacity == 0){ img.onload = function(){ document.enableStateScope("images-on", img.width > 0); } // Source the image to a 43-byte 1x1 pixel GIF image encoded as a data URI. img.src = "data:image/gif;base64," + "R0lGODlhAQABAIAAAP///wAAACH5BAE" + "AAAAALAAAAAABAAEAAAICRAEAOw=="; }else{// Handling for everything else img.onerror = function(e){ document.enableStateScope("images-on", true); } img.src = "about:blank"; } } } )(); //]]> </script> <script type="text/javascript">//<![CDATA window.onload = function(){ document.enableStateScope("images-on", false); } // Toggles the images-on state scope on and off, // and displays the appropriate message function toggle(on){ document.enableStateScope("images-on", on); document.getElementById(on ? "stateScopeOn" : "stateScopeOff").style.display = "block"; document.getElementById(on ? "stateScopeOff" : "stateScopeOn").style.display = "none"; } //]]> </script> <style type="text/css"> .width{ width: 800px; margin: auto; text-align: left; } .header H1{ margin-top: 10px; margin-bottom: 25px; color: white; line-height: 1; top: -35px; font-size: 9pt; text-transform: uppercase; } .header H1 .statescope{ color: #ABDDA9; letter-spacing: -2px; text-transform: none; font-size: 35pt; top: 0.52em; } .images-on .header H1{ /*利用images-on来监视H1的样式*/ text-indent: -12345px; overflow: hidden; background:url(http://images.cnblogs.com/cnblogs_com/rubylouvre/199042/o_aggregated.png) no-repeat; width: 297px; height: 66px; top: 0; } /*Toggle Switch*/ #stateScopeOff, #stateScopeOn{ position: absolute; top: -1px; right: 25px; background-color: white; padding: 9px 15px; border: 1px solid #79B17C; /*[e]1px solid @00*/ font-size: 10pt; z-index: 1; } #stateScopeOn{ display: none; } </style> </head> <body> <div class="width"> <div class="header"> <h1>The <span class="statescope">State Scope</span></h1> </div> </div> <div id="stateScopeOff">现在images-on这个类 <strong>是不存在的。</strong><a href="javascript:toggle(true)">是否添加它?</a></div> <div id="stateScopeOn">现在images-on这个类 <strong>已经存在。</strong><a href="javascript:toggle(false)">是否删掉它?</a></div> </body> </html>
此方法的一些优点
* 当客户端的电脑不支持javascript与禁止图片显示时,它都能优雅地降级而不致于页面效果有太多的差异
* 支持半透明或透明的图片
* 实现非常简单,只要导入我们的脚本以及设置需要图片替换的区域
* 由于是用非常基础的技术,即使是过去的游览器中也畅通无阻
* 符合标准,对屏幕阅读器与搜索引擎友好
* 不需要添加额外的标签
* 不消耗内存(因为基本不遍历DOM树)
* 即使是页面加载完毕对DOM进行操作也不会影响它的效果
* 在加载过程基本不会引发或只有轻微的闪烁现象
* 文本与图片可以在容器元素设置居中或居左对齐
* 不要求在服务器端存在一张1*1的gif图片来防止出错
* 在显示器与打印页上都显示良好
* 由于是使用CSS background-image属性来设置图片,便于我们使用image sprites技术来减少请求数
原作者Paul Young:http://www.sitepoint.com/article/image-replacement-state-scope/