`
deng131
  • 浏览: 662932 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

图像替换技术(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/
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics