此文在21日有修改。
话说某个晚上,在我发完如何重绘checkbox
之后秋水菊苣问我如何重画一个select
元素,我当时还觉得大概会是个很简单的事情,就接了这个科研任务……可是真正上手写的时候才发现(;´ ༎ຶ Д ༎ຶ)σ 太他娘的坑了(;´ ༎ຶ Д ༎ຶ)σ 妈妈我再也不造轮子了(;´ ༎ຶ Д ༎ຶ)σ 造轮大法一点也不好(;´ ༎ຶ Д ༎ຶ)σ 我的信仰崩溃了。
言归正传,select
元素其实一直都挺恶心的,最近接商单的时候也是因为select
不好看煞风景,客户让我解决一下。我刚开始用了一个消除掉mouse-event
的span
给盖上了,不过IE9下根本不兼容ヽ( ° ▽°)ノ,于是我们就只能开始自己徒手造了。
由于这次的代码特别长,所以先来看这个轨道图:
HTML这部分非常非常简单,扔个select进去:
1 2 3 4 5 6 7 8 9
| <select> <option value="a">AAA</option> <option value="b">CCC</option> <option value="c">AAA</option> <option value="d">CCC</option> <option value="e">AAA</option> <option value="f">BBB</option> <option value="g">AAA</option> </select>
|
一般情况下是先HTML再CSS最后JS这样的书写步骤,不过今天的比较特殊,让我们先来写JS,因为CSS需要根据JS来调整。
下面的内容对应轨道图的图1和图2:
看着轨道图,先给select上个套($(document).ready
略):
1
| $('select').wrap('<span class="s_select"></span>');
|
然后在容器里面追加内容:
1 2 3 4 5 6 7 8 9 10 11 12 13
| $('.s_select').append('<button class="s_choosen"></button><ul class="s_select_body"></ul>'); $('select').each(function() { var options = [], values = [], x; $(this).children('option').each(function() { options.push($(this).html()); values.push($(this).attr('value')); }); $(this).next('.s_choosen').html(options[0]); var selectBody = $(this).nextAll('.s_select_body'); for (x in options) { selectBody.append('<li val="' + values[x] + '">' + options[x] + '</li>'); } });
|
没啥复杂的。
不过有一个地方需要强调,也是我最近领悟出来的东西:无论啥情况都不要让JS去自动初始化变量,推荐全手动初始化,不然会闹很多作用域的毛病。
var
定义出来的变量是仅限当前作用域不对父类产生任何干涉的,具体有没有干涉到自己去看看netbeans的代码高亮就好了( • ω•́ )。
下面的内容对应轨道图的图3:
要触发一系列selection
操作都需要按.s_choosen
,所以$('.s_choosen').click(function(){…});
下面所有的代码都包裹在这里面。
1 2 3
| var that = this; var selectBody = $(this).next('.s_select_body'); var selectList = selectBody.children('li');
|
用that
变量存储这个button,用selectList
存储遍历出的li
的结果。这里有一点需要强调,如果一个遍历结果需要被多次复用,那么一定要将这个遍历结果存储到一个单独的对象中,再进行进一步操作,否则将造成不必要的资源损耗。
1
| if (!selectBody.hasClass("selected")) {
|
当selectBody这个选择器有selected这个属性的时候(这个属性表示option容器已经展开,后面会提到。)
1
| selectBody.addClass("selected");
|
这就是刚刚说的给option容器添加标识,我在这里打了个标记,一会在CSS中找这个标记,他俩是对应的哦(๑•̀ㅂ•́)و✧。
1 2 3 4 5 6 7 8
| setTimeout(function() { $("body").one("click.s_select_body", function() { $('.s_select_body').each(function() { $(this).removeClass('selected'); $(document).off('.s_select_keydown'); }); }); }, 1);
|
当在文档任何一处点击时,让所有的option
列表都缩回去。这里用了一个timeout
延迟出现,因为你去点击.s_choosen
时也算在文档上点击了一下,不设置延迟的话这个事件会被一并触发,所以我设定了当.s_choosen
被点击后一百毫秒才监听文档点击事件。
jQuery的one
方法是只绑定一次,触发完毕后自动自杀。
下面定义键盘事件要用到的变量:
1 2 3 4 5
| var _S_ = { select: { itemId: -1, totalNum: selectList.length - 1 }};
|
这段代码是拿来存储键盘事件的指针的,新建了一个对象,对象里存储的是当前鼠标指向的option
序号和总共有多少个option
。selectList.length
返回的是selectList
这个选择器里有多少个元素,这里是对jQuery选择器的一个活用。我之前的文章有讲过jQuery选择器和JS原生选择器互换的用法,在这里就不说了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| $(document).on('keydown.s_select', function(event) { selectList.each(function() { $(this).addClass('keyboard'); });
$(selectList).one('mousemove.s_select_key_mouse', function() { _S_.select.itemId = -1; selectList.each(function() { $(this).removeClass('selected keyboard'); }); });
function changeItem() { if (_S_.select.itemId < 0) _S_.select.itemId = _S_.select.totalNum; if (_S_.select.itemId > _S_.select.totalNum) _S_.select.itemId = 0;
selectList.each(function() { $(this).removeClass("selected"); });
$(selectList[_S_.select.itemId]).addClass("selected"); }
if (event.which === 38) { _S_.select.itemId -= 1; changeItem(); } if (event.which === 40) { _S_.select.itemId += 1; changeItem(); }
if (event.which === 13) { $(that).blur(); $(selectList[_S_.select.itemId]).click(); }
});
|
解释下为啥在listener
内定义function
,其实这很奇葩,但是有的时候选择器异常繁杂,在listener
里定义方法就能规避很多问题,比如很方便的使用this
之类的。
然后说一下这行:$(document).off(‘.s_select_key_mouse’);
我在设置监听的时候为这个监听单独命了一个名:$(document).on(‘keydown.s_select’, function(event) {…});
这样做是为了避免在解绑监听的时候对其他监听造成干扰,最小影响和独立命名空间的原则需要时刻注意。
然后,mousemove
那个Listener
必须使用one
或者去用off
自杀,这不只是代码规范的问题,引一段W3C的话:
注意:用户把鼠标移动一个像素,就会发生一次 mousemove 事件。处理所有 mousemove 事件会耗费系统资源。请谨慎使用该事件。
你的页面会卡飞。
1 2 3 4 5 6 7 8
| $('.s_select_body>li').click(function() { var selectObject = $(this).parents('.s_select').children('select'); selectObject.val($(this).attr('val')); selectObject.next('.s_choosen').html($(this).html()); $(document).off(".s_select");
selectObject.nextAll('.s_select_body').removeClass('selected'); });
|
当option
被按下的时候开始给select
进行赋值操作,并改变button
的文字,改成什么呢~改成被选中的option
呗(≖ᴗ≖๑)
最后一行是移除所有键盘选中的标识,没啥说的。
1 2 3 4 5 6 7 8 9
| setTimeout(function() { $("body").on("click.s_select_body", function() { $('.s_select_body').each(function() { $(this).removeClass('selected'); });
$("body").off(".s_select_body"); }); }, 100);
|
这样当你点击了option
或者是文档其他位置都能把ul
缩回去,点击option
的时候有单独的事件监听给select
对象赋值,这边再隐藏掉option
容器,这样就完成了一次操作。
为了防止你有啥奇葩JS或者奇葩代码,最后我们加上一个保险的东西:
禁掉默认行为。
最后:
1 2 3
| } else { $("body").click(); }
|
容器已经显示出来的话就点一下body把所有容器都归位(此处对应轨道图的“是”这一部分)。
JS部分就完成啦。
接下来写CSS(实际上我在写这个东西的时候也是差不多按照这么个顺序写的。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| .s_select{ position:relative; }
.s_select select{ display:none; }
.s_select .s_choosen{ width:126px; height:26px; padding:0 26px 0 0; background:white; border:1px solid #CCC; border-radius:13px; outline:none; position:relative; }
.s_select .s_choosen::after{ content:'>'; top:-1px; right:-1px; width:26px; height:26px; color:white; background:#00a2ff; border-radius:50%; line-height:26px; text-align:center; display:block; position:absolute; -webkit-transform: rotate(90deg); }
.s_select .s_choosen:hover{ border:1px solid #DDD; }
.s_select .s_choosen:hover::after{ background:#0096ff; }
.s_select .s_choosen:active{ border:1px solid #BBB; }
.s_select .s_choosen:active::after{ -webkit-transform: rotate(-90deg); }
.s_select .s_select_body{ top:19px; left:15px; height:0; width:90px; border:1px solid transparent; box-sizing:border-box; overflow:hidden; position:absolute; }
.s_select .s_select_body.selected{ //【1】 border:1px solid #ddd; height:auto; }
.s_select .s_select_body li{ padding:5px 10px; }
.s_select .s_select_body li:hover{ background:#deedf1; }
.s_select .s_select_body li.keyboard:hover{ //【2】 background:transparent; }
.s_select .s_select_body li.selected{ //【3】 background:#deedf1 !important; }
|
说说打标注的地方,【2】是当触发键盘事件时,鼠标指向的option不高亮,否则页面中会出现两个高亮,很奇葩。
如果把3写在2前面还不加important,keyboard:hover
不高亮了你正常键盘操作的时候鼠标指向的那个option
会一直不高亮,所以这里这么个顺序写它。
最最后,经验:在调EventListener
的事件时如果出现了莫名其妙的bug优先考虑有没有冲突事件被触发了。
之后,没了(∫・ω・)∫
很简单吧(∫・ω・)∫
最后,DEMO在这里,原谅我桀骜不羁的文件名ヾ(:3ノシヾ)ノシ
Comments
No comments here,
Why not write something?