简单美化 select 标签

简单美化 select 标签

网页中最麻烦就是修改表单中的元素,其中 select 是最烦的了,直到 CSS3 也无法修改其样式,因为 select 的样式是系统控制而不是浏览器控制的,所以浏览器不能很好的对其渲染。我也一直很抗拒使用 select,因为不能修改样式的话还不如不用,硬着要「美化」就只能使用 JavaScript 模拟一个,再把原来的 select 隐藏。

最近我觉得是要正视 select,要不然将来非要用到时候就真的不知如何是好。

用 jQuery 「美化」一下,需要做的事情太多了!这次直接做一个仿 Mac OS X 的样式。
搜先是分析一个简单的 select 标签。就是这样,我们需要使用其他标签模拟一个样式。

<select name="name1">
    <option value="1">tab1</option>
    <option value="2" selected>tab2</option>
    <option value="3">tab3</option>
</select>

其实这类需要和文章结合的标签只有 display: inline; 或者 display: inline-block; 的元素最合适,所以选择用 span,然后就是里面的结构了,其实就像制作一个二级菜单一样,所以里面的选项就是用 ul>li 或者 ol>li 模拟 option,两者其实都差不多,但是因为 option 的特性,所以最好选择 ol>li,这样就有了下面的结构。其中 data-selectname="name1" 是用来记录当前模拟的 select 是那个,为什么选择 name?因为 select 可以没有 ID,但是不能没有 name

<span id="select-name1" class="select" data-selectname="name1" >
    <span>tabg</span>
    <ol>
        <li class="select-cat" data-value="1">tab1</li>
        <li data-value="2">tab2</li>
        <li data-value="3">tab3</li>
    </ol>
</span>


有了这样的结构就好了,但是注意的是里面 tabg 是一个文本,修改起来会有问题,所以应该用一个标签套着,方便后面选中修改。
这样就可以开始写样式了。
第一时间就是写第一层 span 样式

.select, .select ul, .select li, .select ol{
    /*首先清除 ul、ol、li 的默认样式*/
    list-style: none;   
    list-style-type: none;
    margin: 0px;
    padding: 0px;
}
.select {
    padding: 0; /*内部不要留白*/
    margin: 2px;    /*外部留白 2px 是 select 默认样式*/
    line-height: 1.7em; /*行距可以自己设置,当然 1.7em 是最舒服的*/
    cursor: pointer;    /*设置鼠标指针样式为点按时候的指针*/
    position: relative; /*定位方式使用 relative 是为了设置后面 ol 的定位*/
    display: inline-block;  /*设置成 inline-block 才可以让内部元素撑开容器*/
    background: #fff;   /*先直接设置一个背景色只是以防万一下面的渐变色浏览器不支持*/
    background-image: -webkit-linear-gradient(#fff,#f5f5f5 49%,#eaeaea 50%, #f2f2f2);   /*使用 webkit 似有属性设置一次是因为 iOS 6 不支持常规渐变色写法*/
    background-image: linear-gradient(#fff,#f5f5f5 49%,#eaeaea 50%, #f2f2f2);   /*成规渐变色写法,主流浏览器都已经支持了*/
    border-width: 0;    /*确保没有边框*/
    border-radius: 4px; /*容器四角都设置圆角*/
    text-shadow: 0 1px 1px rgba(255,255,255,0.5);   /*文字阴影设置不设置无所谓*/
    box-shadow: 0 0 1px rgba(0,0,0,0.7), 0 1px 3px rgba(0,0,0,.15), inset 0 1px 2px rgba(255,255,255,0.5);  /*容器投影,第一个是模拟边框,第二个投影是仿真效果,第三个是白色内阴影增加质感,其实怎么写无所谓,你喜欢*/
    text-align: left;
}

然后注意的是,不要使用最外层的 span 内部留白!因为后面触发 .click()是使用 .select>span,所以如果最外层 span 有内留白的话,呢部分就不能有效点击了。

.select>span {
    padding: 2px 20px 2px .6em; /*右边留白 20px 是给箭头留的空位*/
    margin: 0;
    white-space: nowrap;    /*强制单行显示!*/
    position: relative; /*这里设置是为了让 ::after ::before 定位,其实不写就会继承上一层的,写多一次保险而已 */
}

下面是设置右边的箭头样式,使用 ::after ::before 就不用另外添加元素了!主要是使用边线制作三角形。

.select>span::after, .select>span::before {
    content: "";    /*一定要设置 content 才能显示这两个伪元素的!*/
    position: absolute; /*使用绝对定位*/
    width: 0;
    height: 0;
    border: 4px solid #000;
    border-width: 4px 3px;
    right: 6px;
    top: 50%;
}
.select>span::after{
    /*transparent 是透明*/
    border-color: transparent transparent #444 transparent;
    margin-top: -10px;
}
.select>;span::before {
    border-color: #444 transparent transparent transparent;
    margin-top: 2px;
}

再有就是 ol li,当鼠标经过的时候是一种渐变色,从 #609cfe 渐变到 #5183d4

.select>ol {
    display: none;  /*隐藏这个元素,后面使用 jQuery 可以显示出来的!*/
    position: absolute;
    background: #fff;
    padding: 5px 0; /*这里上下的留白最好就是和圆角的半径一致或者比圆角半径大*/
    border-width: 0;
    border-radius: 4px;
    left: 50%;
    /*
    *   因为设置了定位中 ol 左边相对 .select 的边线 50%
    *   下面使用 CSS3 中的偏移值纠正位置
    *   Left 的偏移百分比是计算父元素
    *   translate 的百分比是计算当前元素的 X Y 轴的数据
    *   所以这里 X 轴 -50% 是当前元素往左偏移自身长度一半的距离
    */
    -webkit-transform: translate(-50%, 0);
    -moz-transform: translate(-50%, 0);
    -o-transform: translate(-50%, 0);
    transform: translate(-50%, 0);
    top: -2px;
    z-index: 2; /*这里是为了不让其他元素挡住 OL*/
    /*投影是必要的!造成一种「弹出」的层次感!*/
    box-shadow: 0 7px 10px rgba(0,0,0,.15), 0 0 1px rgba(0,0,0,0.3);
    overflow: auto;
}
.select>ol>li {
    border-width: 0;    /*同样确保没有边框*/
    padding: 0 1.5em;   /*两边内部留白是我的样式设置*/
    position: relative;
    white-space: nowrap;    /*强制单行显示*/
}
.select>ol>li:hover {
    /*
    *   这里 :hover 设置的是鼠标经过 li 时候的样式
    *   我设置是一种蓝色的渐变色。
    */
    background-image: linear-gradient(#609cfe, #5183d4);
    color: #fff;
    box-shadow: inset 0 0 1px rgba(0,0,0,0.3);
    text-shadow: 0 1px 1px rgba(0,0,0,0.2);
}
.select>ol>li.select-cat::before {
    /*
    *设置选中状态
    */
    position: absolute;
    content: "✓";
    width: 1em;
    height: 18px;
    line-height: 18px;
    top: 50%;
    margin-top: -10px;
    left: 5px;
}

好了,所有 CSS 样式写完,当然,样式怎样写随便,知道了 HTML 的结构,随便写。

下面就到了最烦人的 jQuery 部分,这部分我的写法想到烦,自己回头在看都觉得头晕,这是不知道自己写什么。
首先确保所有代码都放到 .ready() 中!这样页面框架载入完才会执行代码,就不会出问题了。

jQuery(document).ready(function($) { 
    //代码放置的位置
});

然后就先写一个复制 select 的函数然后执行生成。

function selectCopy () {
    $('select').each(function () {
        //实用 each function
        var thisName = $(this).attr('name');    //获取当前 select 的 name
        var thisVALUE = $(this).val();  //获取 当前 select 的 val

        //获取默认选中的 option 的文字
        var thisValText = $('select[name="'+thisName+'"]>option[value="'+thisVALUE+'"]').text();

        //计算 select 中有多少个 option
        var optionLength = $('select[name="'+thisName+'"]>option').length;

        //生成 span 中 di、data 和默认选择项的文字的代码
        var addHtml = '<span id="select-'+thisName+'" class="select" data-selectname="'+thisName+'"><span>'+thisValText+'</span>';
        var addHtmlEnd = '</span>';

        //先设置开头 ol 标签
        var addOpt = '<ol>';
        //根据上面计算到的 option 个数循环生成 li
        for (i = 1; i < optionLength+1; i++) {
            //获取当前的 option 的 value 值
            var thisOptVal = $('select[name="'+thisName+'"]>option:nth-child('+i+')').val();

            //获取当前 option 的文字描述
            var thisOptText = $('select[name="'+thisName+'"]>option:nth-child('+i+')').text();

            //判断当前的 value 是不是选中的那个,是的话就加上 .select-cat
            if (thisOptVal != thisVALUE) {
                addOpt+='<li data-value="'+thisOptVal+'">'+thisOptText+'</li>';
            } else {
                addOpt+='<li data-value="'+thisOptVal+'" class="select-cat">'+thisOptText+'</li>';
            };
        };
        //最后加上结尾的 </ol>
        addOpt += '</ol>';

        //直接在当前的 select 后面插入这个生成的模拟 select
        $(this).after(addHtml+addOpt+addHtmlEnd);
        $(this).hide(); //隐藏真正的 select
    });
};

/*
*   下面要判断是不是 iOS,因为 iOS 的 select 很特别,所以最好不要替换。
*   或者你可以写一个 css 就够了,不需要模拟。
*/
if((!navigator.userAgent.match(/iPhone/i)) || (!navigator.userAgent.match(/iPod/i))){ 
    selectCopy();
} else {
    $('select').addClass('select'); //如果是 iOS 就直接添加 .select
};

复制 select 好了,你会看到最后的效果了。但是点击什么效果都没有,那么下面我们就写点击的事件。

/*
*   使用 .on() 是确保你的「复制品」可以正常工作。
*   直接使用 .click() 的话是无法工作的,因为你的「复制品」
*   在页面载入时候是不存在的,所以 .click() 是不能操作的。
*/
$(document).on('click','.select>span',function(event) {
    //当 .select>span 点击时候
    var parentID = $(this).parent();    //这是选中第二层 span 的父元素
    var thisID = parentID.attr('id');   //然后选中他的 ID
    $('#'+thisID+'>ol').fadeIn('fast'); //接着就把你点击的这个「复制品」的选项弹出
});

$(document).on('click','.select>ol>li',function(event) {
    var parentUL = $(this).parent();
    var parentID = $(parentUL).parent();
    var thisID = parentID.attr('id');   //获取「复制品」的 ID
    var thisTEXT = $(this).text();  //获取所点击的li的文字
    var thisVALUE = $(this).data('value');  //获取所点击的 li 中的 value
    var thisselectname = $('#'+thisID).data('selectname');  //获取「真身」的 name

    //清除 li 中所有 cat
    $('#'+thisID+'>ol>li').removeClass('select-cat');
    //给当前点击的 li 加上 cat
    $(this).addClass('select-cat');

    //修改「真身」的 value
    $('select[name="'+thisselectname+'"]').val(thisVALUE);

    //修改「复制品」中 span 的显示文字
    $('#'+thisID+'>span').text(thisTEXT);
});

//当页面任何地方点击都会隐藏「复制品」中的 ol!
$('html').click(function(event) {
    $('.select>ol').fadeOut("fast");
});

OK,写完。如果想修改样式,jQuery 部分完全不用动,只要修改 CSS 就 OK 了。