在讲解开始之前,你可能希望先查看:demo download
当用户使用邮箱登录你网站时,你可能希望免去用户一些输入,以及防止用户写错邮箱地址,类似于新浪微博登录时的提示:
首先需要定义它的html结构,这决定了它的显示方式及运行原理,我使用了一个无序列表“UL”:
1 2 3 4 5 6 | <ul id="emailpop" class="autopop"> <li class="notpop">请选择邮箱类型</li> <li class="pop">yu123</li> <li>yu123@gmail.com</li> <li>yu123@sina.com</li> </ul> |
基本结构固定好以后,由于选择表现与逻辑分离,需要给它定义样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | .autopop{/*承载数据的容器,即UL*/ position: absolute;/*需要它显示在文本框的正下方,采用绝对定位*/ background: #fff; border: 1px #ddd solid; box-shadow: 0px 1px 4px #ccc inset; border-radius: 3px;color: #999; z-index: 19;/*层级设定高一些,避免被不必要的元素遮盖*/ display: none;/*一开始是不显示的*/ } .autopop li{/*邮箱列表的基本状态*/ font-size:12px; height: 24px; line-height: 24px; padding: 0 6px; cursor: pointer;/*需要模拟可以点击的鼠标手型样式*/ overflow: hidden; } .autopop li.notpop{/*默认提示语句一行是无法选取的*/ cursor: default; } .autopop li.pop{/*选中状态*/ background: #dee; color:#333; } |
需要说明的是,我只定义了基本样式,假如你拿去使用,可能样式与图片显示有差异,视你的reset样式及文档声明类型有关,请自行修改所需要的视觉表现。如有问题,概不负责。
结构和样式定义完了,提示也有了个基本的模样,接下来我们就需要在使用时把它插入到“body”里:
1 | $("body").append('<ul id="emailpop" class="autopop"></ul>'); |
然后给它绑定鼠标悬浮以及点击事件:
1 2 3 4 5 6 7 | $pop = $("#emailpop").on("mouseover", "li:not(:first)", function() {//鼠标滑过的时候会有高亮,也就是刚刚定义的“.pop”样式 $pop.find("li.pop").removeClass("pop"); $(this).addClass("pop"); }).on("mousedown", "li:not(:first)", function() {//点击以后提示隐藏,并把值赋给文本框 $pop.hide(); $bind.val($(this).text()); }) |
“$bind” 是当前正在输入的文本框,你可能要问:这样使用不会出现未定义吗?后面讲 。
元素创建了,事件绑定了,数据哪里来?考虑到邮箱后缀一般都比较稳定,没有哪个邮箱提供商会没事就改下后缀名的,我就定义死了。
1 2 | var list = ["gmail.com", "sina.com", "163.com", "qq.com", "126.com", "vip.sina.com", "sina.cn", "hotmail.com", "sohu.com", "yahoo.cn", "139.com", "wo.com.cn", "189.cn", "21cn.com"], $bind, delay, rsDelay, l//这几个参数后面有用 |
如果你想改成自定义或者根据参数来控制数据,请自行修改,很简单的啦,改改后缀就行了。
当用户输入邮箱的时候,假如他改变了浏览器大小,这个提示信息是不会自己跟着文本框跑的,为避免这种情况发生,我们要给浏览器窗口绑定“resize”事件:
1 2 3 4 5 6 7 8 9 10 11 | resize = function() { if (rsDelay) clearTimeout(rsDelay);//延迟方法执行,很多时候要用到这种延迟以降低浏览器负担 rsDelay = setTimeout(function() { var offset = $bind.offset();//每次浏览器改变大小时计算下正在输入文本框的位置,然后调整下赋值的位置 $pop.css({ left: offset.left, top: offset.top + $bind.outerHeight() + 2, width: $bind.outerWidth()//使得提示信息的宽度与文本框宽度一致,好看点 }); }, 99) }; |
完事具备,只欠东风,最后就只剩下如何根据用户输入来显示提示信息了,想一下用户要在文本框输入的时候触发的第一个事件是什么?“focus”,当然是先选中它啦:
1 2 3 4 5 | focus: function() { $bind = $t.trigger("keydown");//绑定$bind为当前正在输入的文本框,这样上面的$bind就不会在使用时出现未定义了,为什么要触发keydown事件? resize();//调整下提示信息的位置 $(window).on("resize", resize);//把resize事件绑定到浏览器窗口上 } |
接下来想一下用户在文本框输入的时候触发的第一个事件是什么?这跟上一个题目可不一样哦。“keydown”,键盘按下事件啊,你打字肯定是先down后up啊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | keydown: function() { var val = $t.val(); if ($.trim(val).length) { var i = 0, s = val.indexOf("@"),//获取到用户输入内容里“@”的位置 u = val, r = "", html = '<li class="notpop">请选择邮箱类型</li><li class="pop">' + val + '</li>';//第一条肯定是提示,第二条是用户自己的输入 if (s >= 0) { u = val.substr(0, s); r = val.substr(s + 1) }//把输入内容根据“@”分成两部分 l = list.length; for (; i < l; i++) {//循环下一开始定义的邮箱数组 if (r.length > 0) {//如果用户输入了邮箱后缀一部分 if (list[i].indexOf(r) > -1 && r != list[i])//判断数组内是否有用户输入的邮箱后缀且不是全等于,思考下为什么不是全等于? html += "<li>" + u + "@" + list[i] + "</li>"; } else html += "<li>" + u + "@" + list[i] + "</li>" } l = $pop.html(html).show().find("li").length;//把数据填充进去显示出来并获取到数据长度 } else $pop.hide();//比如说我把文本框的内容全删除了,提示当然就应该隐藏起来 } |
用户填完了,焦点离开输入框,这时候就应该把提示隐藏起来:
1 2 3 4 | blur: function() { $pop.hide();//隐藏 $(window).off("resize", resize);//移除浏览器窗口绑定的resize事件,养成好习惯,用不到的事件及时移除,减少浏览器负担 } |
大功告成,快试试效果如何,等等,好像少了点什么?假如我想在输入框里用上下来选择邮箱,用回车来补全怎么办?判断用户按的哪个键来执行不同的方法就行啦:
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 | keydown: function(e) { switch (e.which) { case 9://TAB键,隐藏 $pop.hide(); break; case 32://空格键,没反应,放心,不会有邮箱名有空格的 return false; break; case 13://回车,补全邮箱后缀 $t.val($pop.hide().find(".pop").text()); break; case 38://向上键 var $p = $pop.find(".pop").removeClass("pop");//先找到当前选择的一列去掉高亮 if ($p.index() > 1)//判断它的位置,记住有一行是提示信息是无法选择的 $p.prev().addClass("pop");//给它前一项添加高亮 else//如果是第二列即用户输入那列 $pop.find("li:last").addClass("pop");//就给最后一项添加高亮 return false; case 40://向下键,跟向上是反着的 var $p = $pop.find(".pop").removeClass("pop"); if ($p.index() < l - 1) $p.next().addClass("pop"); else $pop.find("li:eq(1)").addClass("pop"); return false; default: //这里是刚才写的方法 } } |
在这一步你当然也可以禁止用户输入非法字符,只是多添加几个case而已,不过谁知道谁的非法字符都有哪些呢?想加自己加吧。
可能要问一开始定义的“delay”是干嘛用的?当用户按住一个键不放的时候,提示也会根据输入内容自动改变,为了减少浏览器消耗,所以要使用延迟把执行推后,那为什么不直接把事件定义在keyup上?当回车键keyup的时候,表单就已经开始提交了,这时候你再修改文本框的内容,表单是无法获取的它已经提交了还获取个毛啊,所以就只好绑定在keydown事件上了,不过你也可以把修改内容事件绑定在keyup,当keydown发生时,判断是否回车键,不过这样就需要多绑定一个事件,能省点事就省点嘛,不过假如编写搜索自动补全插件的话就需要用上了,这个回头再说吧。
最后的最后,把代码整合起来。如果你已经根据上面的内容自己写了一个插件出来,下面就可看可不看了。
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 84 85 86 87 | (function() { var $bind, $pop, delay, rsDelay, l, list = ["gmail.com", "sina.com", "163.com", "qq.com", "126.com", "vip.sina.com", "sina.cn", "hotmail.com", "sohu.com", "yahoo.cn", "139.com", "wo.com.cn", "189.cn", "21cn.com"], resize = function() { if (rsDelay) clearTimeout(rsDelay); rsDelay = setTimeout(function() { var offset = $bind.offset(); $pop.css({ left: offset.left, top: offset.top + $bind.outerHeight() + 2, width: $bind.outerWidth() }); }, 99) }; $(function() { $("body").append('<ul id="emailpop" class="autopop"></ul>'); $pop = $("#emailpop").on("mouseover", "li:not(:first)", function() { $pop.find("li.pop").removeClass("pop"); $(this).addClass("pop"); }).on("mousedown", "li:not(:first)", function() { $pop.hide(); $bind.val($(this).text()); }), }); $.fn.emailpop = function() { return $(this).attr("autocomplete", "off").each(function() { var $t = $(this).on({ focus: function() { $bind = $t.trigger("keydown"); console.log(1) resize(); $(window).on("resize", resize); }, keydown: function(e) { switch (e.which) { case 9: $pop.hide(); break; case 32: return false; break; case 13: $t.val($pop.hide().find(".pop").text()); break; case 38: var $p = $pop.find(".pop").removeClass("pop"); if ($p.index() > 1) $p.prev().addClass("pop"); else $pop.find("li").last().addClass("pop"); return false; case 40: var $p = $pop.find(".pop").removeClass("pop"); if ($p.index() < l - 1) $p.next().addClass("pop"); else $pop.find("li").eq(1).addClass("pop"); return false; default: if (delay) clearTimeout(delay); delay = setTimeout(function() { var val = $t.val(); if ($.trim(val).length) { var i = 0, s = val.indexOf("@"), u = val, r = "", html = '<li class="notpop">请选择邮箱类型</li><li class="pop">' + val + '</li>'; if (s >= 0) { u = val.substr(0, s); r = val.substr(s + 1) } l = list.length; for (; i < l; i++) { if (r.length > 0) { if (list[i].indexOf(r) > -1 && r != list[i]) html += "<li>" + u + "@" + list[i] + "</li>"; } else html += "<li>" + u + "@" + list[i] + "</li>" } l = $pop.html(html).show().find("li").length; } else $pop.hide(); }, 99) } }, blur: function() { $pop.hide(); $(window).off("resize", resize); } }) }) } })() |
使用的话:
1 2 | $("#email").emailpop();//单个文本框使用 $("#email input").emailpop();//多个 |
我尝试着不太讲语法,只讲下我编写插件时的思路,因为我觉得,语法说重要也不重要,毕竟你测试的时候语法错误肯定报错,但是思路错误或者逻辑错误,有时候即使你运行正常还是会出很多BUG,或者效率不达标。我把自己的代码分享出来,在这个过程里,本身就是对我自己编程的一个回顾,即使刚才,我又发现了两处错误(不能算错误,只是改一下效率会好一些,代码能更简单一些),希望你看完后能给你一点启发,或者发现我的错误,如有问题,请留言不保证一定回复。
题外话:近期一直在使用seajs,感觉很不错,延时,按需加载,模块化编程,修改方便,文件也很小,不错不错。我在GitHub上分享了工作中编写的一些插件,不过都是基于seajs的,我会慢慢把他们剥离出来一一讲解,敬请期待我很懒所以别期待太多。
Pingback引用通告: 搜索辅助输入插件 AutoCS - 吁... |禹向辉的长吁短叹
谢谢··········已经测试成功,^_^狠喜欢不错不错。
为什么用鼠标点击选中不了后缀,非要用键盘才能选中后缀
我测试鼠标点击可行啊