css3 button maker

Demo:

站在别人的肩上

基于button maker

改进:

  • 增加text-shadow、box-shadow、border等css属性
  • 增加:active伪类支持
  • 增加IE hack,实现IE优雅降级
  • 生成样式时,剔除不需要的代码,如:hover时样式和正常状态一样、:active时和:hover时样式一样,这些情况下样式的重复定义都是没有意义的,剔除之。
  • 通过fixed定位,代替js位置计算
  • 增加中文字体集
  • 0px转化为0

需要改进的:

  • 目前只支持两种色值的渐变,3种较为理想,并且不支持自定义渐变的方向
  • IE最好有更少的降级
  • 拖动条最好用html5的range控件来实现
  • 脚本可以再完善

价值:

接手“前端和视觉之间的故事”这个团队内部的项目已经有一段时间了,一直没有找到一个让视觉同学了解前端可以做什么前端有什么新技术的好方法,一旦这个做不好,让视觉协助我们前端做一些渐进增强的东西就很困难。

无意中我看到了Chris Coyier的button maker,我想这是一个有效解决问题的好方法,视觉用我们提供的工具进行设计,设计完成后,前端的代码也完成了,太棒了。为此我在征得Chris Coyier的同意后,借鉴了他的代码,进行了如上提到的改进,给视觉看后,似乎很认可,希望能真的运用到设计中去。

用:visited窥探用户访问历史

起因

mozilla发布了firefox4 beta 1,更新了一些CSS属性,不知道你有没有注意到其中有一条关于:visited伪类的更新,为了保护用户的隐私,限制绝了绝大多数的css样式应用到:visited元素上,这其中就包含了一些我们常用于访问过链接的CSS属性,比如backgroundborder-colorfont。比如我的博客,对访问过的链接,就会用content属性生成一个勾,提示读者这个链接他已经访问过了。我尝试了几个常用的CSS属性,只有color依然有效。

既然color仍然有效,不知道mozilla这样做有什么意义?在这个越来越注重可用性的时代,不考虑:visited样式绝不是一个好的做法,说句题外话,各位设计师们应该在设计中考虑更多的css伪类样式,而不局限于:hover,按下一个按钮、表单获得焦点这些动作没有反馈是一件令人沮丧的事。言归正传,限制那么多的CSS属性运用于:visited将大大降低设计师的创作空间。访问后变色本身又是一个不受大多数人喜欢的标识方法,a,a:visited{color:XXX;}是绝大多数网站的做法。只能用两个字形容我的现在的心情:“我操”。

如何窥探

我没有去查这个隐私问题到底是怎么发生的,我的猜测就是用脚本去遍历链接,判断它的:visited样式有没有生效,生效了就说明访问过了。这里原理类似我的这篇文章:Browser Detector With CSS。真想问问mozilla为什么不从js下刀呢,为什么不去限制window.getComputedStyle(node,pseudo)[property]:visited的值?IE知道这事笑了,“哈哈,我不支持用js取伪类样式!”。基本代码如下,请忽视转换色值的函数:

[Demo]用firefox4看看是不是只有颜色生效了? [Demo]看看你访问过哪里?


a:visited{color:#f00;}

var Color = {
    "toHex" : function(rgb){
        var a = rgb.replace(/rgb\(|\)/g,"").split(","),
              hex = "";
        for (var i = 0;i < 3;i++){
            var b = parseInt(a[i]).toString(16);
            hex += (b.length === 1)?"0"+b:b;
        }
        return "#" + hex;
    },
    "toComplate" : function(hex){
        if (hex.length === 4){
            var hex = hex.toLowerCase(),
                  newHex = "";

            for (var i = 0;i < 3;i++){
                var a = hex.substring(i+1,i+2);
                newHex += a + a;
            }
            return "#" + newHex;
        }else{
            return hex;
        }
    }
};
function getColor(node,property,pseudo){
    pseudo = pseudo?pseudo:null;
    if(node.currentStyle){
        return Color.toComplate(node.currentStyle[property]);
    }
    if(window.getComputedStyle){
        return Color.toHex(window.getComputedStyle(node,pseudo)[property]);
    }
}
var links = document.getElementsByTagName("a");
for(var i = 0,j = links.length;i<j;i++){
    var color = getColor(links[i],"color","visited");
    if(color === "#ff0000"){
        alert("你访问过:"+links[i].firstChild.nodeValue);
    }
}

iframe自适应高度

同域、子页面高度不会动态增加

这种情况最简单,直接通过脚本获取字页面实际高度,修改iframe元素高度即可。但有二点必须注意:

  1. 如果页面内有绝对定位或者没有清浮动的元素,情况有些复杂,不同浏览器处理结果不同,甚至包括Webkit内核的浏览器,具体请看这个Demo。所以你要么进行浏览器检测,要么用Math.max计算一个最大值,要么你想别的方法。
  2. iframe所包含页面可能非常大,需要很长的加载时间,为此直接计算高度的时候,很可能页面还没下载完,高度计算就会有问题。所以最好在iframeonload事件中计算高度。这里还要注意的是,IE下必须使用微软事件模型obj.attachEvent来绑定onload事件。而别的浏览器直接obj.onload = function(){}也可以。

(function(){
    var frame = document.getElementById("frame_content_parent"),
        setIframeHeight = function(){
            var frameContent = frame.contentWindow.document,
                frameHeight = Math.max(frameContent.body.scrollHeight,frameContent.documentElement.scrollHeight);

            frame.height = frameHeight;
        };
    if(frame.addEventListener){
        frame.addEventListener("load",setIframeHeight,false);
    }else{
        frame.attachEvent("onload",setIframeHeight);
    }
})();

同域、子页面高度会动态增加

原理与第一种情况一样,多一个计时器,一直检测字页面高度,当子页面高度和iframe的高度不一致时,重新设置iframe的高度。这边也可以加一个try在js出错时,加一个足够的高度。


(function(){
    var _reSetIframe = function(){
        var frame = document.getElementById("frame_content_parent")
        try {
            var frameContent = frame.contentWindow.document,
                bodyHeight = Math.max(frameContent.body.scrollHeight,frameContent.documentElement.scrollHeight);
            if (bodyHeight != frame.height){
                frame.height = bodyHeight;
            }
        }
        catch(ex) {
            frame.height = 1800;
        }
    }
    if(frame.addEventListener){
        frame.addEventListener("load",function(){setInterval(_reSetIframe,200);},false);
    }else{
        frame.attachEvent("onload",function(){setInterval(_reSetIframe,200);});
    }
})();

同域、子页面高度会动态增加、脚本可能完全失效

第二个例子中,考虑到了脚本出错的情况,但是万一脚本根本不执行了呢,那iframe中的内容就会因为iframe的高度不够而显示不了。为此我们通常事先设置一个足够的高度,为了前端控制方便,我觉得写在CSS文件中比较合适,需要修改时只改CSS就行了。这里我设置了selector{ height:1800px; }。需要注意的是,写在样式表里的样式,不能直接用node.style[property]来取,对于微软模型,要用node.currentStyle[property](题外话:悲剧的IE模型不支持CSS伪类),对于W3C模型,要用window.getComputedStyle(node,null)[property]来取。我这里图方便直接用了YUI。

这里又有一个问题,设置iframe的高度大于其包含页面的高度时,各个浏览器的处理不一样。例如在Firefox下,必须计算body元素的高度,而html元素的高度等于iframe的高度,然而当恰巧这个页面又有绝对定位未清浮动元素时,又不能通过body元素来取,显然第一种方法缺点更小一些。具体请看这个Demo

从上面这个Demo可以看到,除IE浏览器外,别的浏览器计算出来的都是iframe的高度,即CSS里设置的#frame_content_parent{ height:1800px; }。而IE计算出来的是iframe所引用页面的实际高度。


#frame_content_parent{ height:1800px; }


(function(){
    var $ = YAHOO.util.Dom,
        frame = $.get("frame_content_parent");
    function reSetIframe(){
        var frameContent = frame.contentWindow.document,
            bodyHeight = Math.max(frameContent.documentElement.scrollHeight,frameContent.body.scrollHeight);
        if (bodyHeight != $.getStyle(frame, "height")){
            $.setStyle(frame, "height", bodyHeight + "px");
        }
    }
    if(frame){
        $.setStyle(frame,"height","auto");
        setInterval(reSetIframe,300);
    }
})();

跨域

这里提供一个Iframe代理的方法,简单地说一下原理。假设有3个页面,分别是主页面A.html,字页面B.html,代理页面C.html。其中A与B是跨域的,而A和C是同域的。它们的关系:A包含B,B包含C。很显然A和B,以及B和C,因为跨域不能相互通信,而A和C同域,可以相互通信。为此我们就想到让C页面告诉A页面,B页面到底有多少高。因为B和C也是跨域的不能相互通信,所以想在C页面中,直接window.parent.document.body.scrollHeight这样是行不通的,所以我们只能让B页面自己计算自身的高度,然后通过某种方法告诉C页面,再由C页面告诉A页面。这里的一个方法就是在B页面生成一个Iframe节点,然后设置它的src属性,在这个地址上附加一个参数,即B页面计算出来的高度,然后C页面就可以通过window.location获取这个地址栏中的地址,提取出高度值,通过window.top找到A页面,设置A页面的Iframe的高度。基本的原理就是这样,看代码吧:

DEMO


//B页面脚本
//任务:计算其实际高度,然后生成一个iframe节点,将高度作为代理页面C的地址的一部分赋值给Src属性
(function(){
    var agent_iframe = document.createElement("iframe"),
        b_height = Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);
    agent_iframe.src = "http://demo.zhouqicf.com/js/2010/iframe_height/agent_iframe_once.html#" + b_height;
    document.body.appendChild(agent_iframe);
    agent_iframe.style.display = "none";
})();


//C页面脚本
//任务:获取请求地址中的高度值,将其赋值给A页面的Iframe的高度
window.top.document.getElementById("frame_content_parent").height = parseInt(window.location.hash.substring(1),10);

跨域、字页面高度动态变化

这里结合了第2、第4两种方法,我的想法是在B页面通过一个计时器,不停计算B页面的高度,一但变化,马上修改iframe标签的src属性,而C页面也有计时器不断监听src的变化,改变Aiframe标签的高度。需要注意的是仅仅修改src属性后面的锚点值(如“#1234”),页面并不会刷新,不会重新请求,这也是在C页面增加计时器的原因。

DEMO


//B页面脚本
(function(){
    var getHeight = function(){
        return Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);
    };

    var preHeight = getHeight(),
        agent_iframe;

    var createIframe = function(height){
        agent_iframe = document.createElement("iframe");
        agent_iframe.style.height = "0";
        agent_iframe.style.width = "0";
        agent_iframe.style.border = "none";
        agent_iframe.src = "http://demo.zhouqicf.com/js/2010/iframe_height/agent_iframe.html#" + height;
        document.body.appendChild(agent_iframe);
    }

    createIframe(preHeight);

    var checkHeight = function(){
        var currentHeight = getHeight();
        if(currentHeight != preHeight){
            agent_iframe.src = "http://demo.zhouqicf.com/js/2010/iframe_height/agent_iframe.html#" + currentHeight;
            preHeight = currentHeight;
        }
        setTimeout(checkHeight,500);
    }

    setTimeout(checkHeight,500);
})();


//C页面脚本
(function(){
    var preHeight = parseInt(window.location.hash.substring(1),10),
        ifrmae = window.top.document.getElementById("frame_content_parent");

    ifrmae.height = preHeight;
    setInterval(function(){
        var newHeight = parseInt(window.location.hash.substring(1),10);
        if (newHeight !== preHeight){
            ifrmae.height = newHeight;
            preHeight = newHeight;
        }
    },500);
})();

这里还有另一种方案,就是让iframe每一次都重新请求,这样C页面就不需要计时器了,但是如果2次计算高度重复的话,就会导致src属性的值相同,这样浏览器就很可能不重新请求该页面了,那么C页面中的脚本也就不运行了。要修复这个问题很简单,只要在每次计算出来的src属性上增加一个随机数的参数就行了。比如http://demo.zhouqicf.com/js/2010/iframe_height/agent_iframe.html?temp=123123423712937#1563


//B页面关键脚本
agent_iframe.src = "http://demo.zhouqicf.com/js/2010/iframe_height/agent_iframe.html?a=" + Math.random() + "#" + currentHeight;


//C页面脚本
window.top.document.getElementById("frame_content_parent").height = parseInt(window.location.hash.substring(1),10);

No_Css3.png

按需加载

优雅降级已经越来越深入人心,比如一个圆角效果,我们可以让支持CSS3的浏览器用border-radius实现,而让不支持的用图片实现,但我们通常会把这些图片也合入Sprites图片里。今天在一个小项目的时候,我想为什么不用不把它做的更进一步,把这些用来代替CSS3的图片单独合并成一张图片呢?这样子,对于不支持CSS3的浏览器就下载这张图片,而高级浏览器就不用浪费性能来下载这些图片了。

一个在面包屑导航上,利用content生成”>”符号,代替背景的例子,这里是Demo,这里是主要代码:


/*
ie6,7 use image
other broswer use content
*/
.nav a {
 *background:url(../img/no_css3.png) no-repeat right -248px;
 *padding-right:10px;
  text-decoration:none;
}
.nav a:after {
  content:">";
  margin-left:5px;
  color:#ccc;
}

Browser Detector With CSS

CSS hacks已经非常成熟,通过它你不仅可以区分浏览器类型,甚至可以区分它们的版本号,而且你几乎听不到它出错的例子。但Javascript就没那么幸运了,navigator经常会欺骗你,对象检测很安全,但它要区分浏览器的版本号就没那么简单了。当然我Js很懒,也许有好的办法。

为此我就想为什么不用CSS来辅助JS进行浏览器检测呢?先看代码:


#selector{
   background-color:#000;
   background-color:#f00\0;  /*IE 8*/
   *background-color:#f60;  /*IE 6、7*/
   _background-color:#fff;  /*IE 6*/
}

var nodeBg = document.getElementById("selector").style.backgroundColor;
var nodeBg = $D.getStyle($("selector"),"background-color");
if (nodeBg === "#f00"){
    alert("IE8");
}else if(nodeBg === "#f60"){
    alert("IE7");
}else if(nodeBg === "#fff"){
    alert("IE6");
}else{
    alert("not IE");
}

原理就是通过js去判断浏览器解析到的CSS属性值,通过该值判断浏览器的版本。当然我这里只写了IE的几个版本的hack,你还可以通过一些别的hack或者私有属性来区分别的浏览器,不一一列举了。

当然,正如三七说的,这显然绕了一圈,Js直接就能判断;且不利于团队协作,万一某一天别人删了这句看似“没用”的CSS规则,你的脚本就傻逼了。只能算是一种小技巧吧。