新的图像替换技术:状态区域法(The State Scope Method)
感谢蓝色理想的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"; } } })();
此方法的一些优点
- 当客户端的电脑不支持javascript与禁止图片显示时,它都能优雅地降级而不致于页面效果有太多的差异
- 支持半透明或透明的图片
- 实现非常简单,只要导入我们的脚本以及设置需要图片替换的区域
- 由于是用非常基础的技术,即使是过气的游览器中也畅通无阻
- 符合标准,对屏幕阅读器与搜索引擎友好
- 不需要添加额外的标签
- 不消耗内存(因为基本不遍历DOM树)
- 即使是页面加载完毕对DOM进行操作也不会影响它的效果
- 在加载过程基本不会引发或只有轻微的闪烁现象
- 文本与图片可以在容器元素设置居中或居左对齐
- 不要求在服务器端存在一张1*1的gif图片来防止出错
- 在显示器与打印页上都显示良好
- 由于是使用CSS background-image属性来设置图片,便于我们使用image sprites技术来减少请求数
附上原作者Paul Young的文章:
http://www.sitepoint.com/article/image-replacement-state-scope/