Ruby's Louvre

每天学习一点点算法

导航

新的图像替换技术:状态区域法(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";
        }
    }
})();

http://www.denisdeng.com/exzample/state-scope-image-replacement.html

此方法的一些优点

  • 当客户端的电脑不支持javascript与禁止图片显示时,它都能优雅地降级而不致于页面效果有太多的差异
  • 支持半透明或透明的图片
  • 实现非常简单,只要导入我们的脚本以及设置需要图片替换的区域
  • 由于是用非常基础的技术,即使是过气的游览器中也畅通无阻
  • 符合标准,对屏幕阅读器与搜索引擎友好
  • 不需要添加额外的标签
  • 不消耗内存(因为基本不遍历DOM树)
  • 即使是页面加载完毕对DOM进行操作也不会影响它的效果
  • 在加载过程基本不会引发或只有轻微的闪烁现象
  • 文本与图片可以在容器元素设置居中或居左对齐
  • 不要求在服务器端存在一张1*1的gif图片来防止出错
  • 在显示器与打印页上都显示良好
  • 由于是使用CSS background-image属性来设置图片,便于我们使用image sprites技术来减少请求数

附上原作者Paul Young的文章:
http://www.sitepoint.com/article/image-replacement-state-scope/

posted on 2009-08-06 11:12  司徒正美  阅读(3796)  评论(5编辑  收藏  举报