javascript模拟select,jselect的方法实现_javascript技巧

由于主流浏览器对select元素渲染不同,所以在每种浏览器下显示也不一样,最主要的是默认情况下UI太粗糙,即使通过css加以美化也不能达到很美观的效果。这对于我们这些专注于UX的前端开发人员是无法容忍的。于是在项目不太忙的时候,就计划写一个模拟的select控件出来。接下来就把实现的细节、遇到的问题以及如何使用和大家分享一下。
1. 实现细节
init: function(context) {
//获取指定上下文所有select元素
var elems = squid.getElementsByTagName('select', context)
this.globalEvent()
this.initView(elems)
}
在一个用户注册的应用场景,有多个select元素。模拟的select控件(以下简称jselect)初始化方法会获取页面上所有select元素,然后绑定全局事件globalEvent,初始化页面显示initView。globalEvent方法如下:

复制代码 代码如下:

globalEvent: function() {
//document 添加click事件,用户处理每个jselect元素展开关闭
var target,
className,
elem,
wrapper,
status,
that = this;

squid.on(document, 'click', function(event) {
target = event.target,
className = target.className;

switch(className) {
case 'select-icon':
case 'select-default unselectable':
elem = target.tagName.toLowerCase() === 'div' ? target : target.previousSibling
wrapper = elem.nextSibling.nextSibling

//firefox 鼠标右键会触发click事件
//鼠标左键点击执行
if(event.button === 0) {
//初始化选中元素
that.initSelected(elem)
if(squid.isHidden(wrapper)) {
status = 'block'
//关闭所有展开jselect
that.closeSelect()
}else{
status = 'none'
}
wrapper.style.display = status
elem.focus()
}else if(event.button === 2){
wrapper.style.display = 'none'
}
that.zIndex(wrapper)
break
case 'select-option':
case 'select-option selected':
if(event.button === 0) {
that.fireSelected(target, target.parentNode.parentNode.previousSibling.previousSibling)
wrapper.style.display = 'none'
}
break
default:
while(target && target.nodeType !== 9) {
if(target.nodeType === 1) {
if(target.className === 'select-wrapper') {
return
}
}
target = target.parentNode
}
that.closeSelect()
break
}
})
}

globalEvent实现了在document绑定click事件,然后在页面上触发点击事件的时候通过事件代理来判断当前点击元素是否是需要进行处理的目标元素,判断条件是通过元素的class,代码中语句的分支分别是:展开当前点击的jselect元素下拉、选中点击列表项、判断是否需要关闭jselect。

initView方法如下:

复制代码 代码如下:

initView: function(elems) {
var i = 0,
elem,
length = elems.length,
enabled;

for(; i < length; i++) {
elem = elems[i]
enabled = elem.getAttribute('data-enabled')
//使用系统select
if(!enabled || enabled === 'true')
continue
if(squid.isVisible(elem))
elem.style.display = 'none'

this.create(elem)
}
}

initView实现了将需要使用jselect替换的select元素先隐藏然后调用create方法,生成单个jselect的整体结构并插入到页面并替代默认select位置。

create方法如下:

复制代码 代码如下:

create: function(elem) {
var data = [],
i = 0,
length,
option,
options,
value,
text,
obj,
lis,
ul,
_default,
icon,
selectedText,
selectedValue,
div,
wrapper,
position,
left,
top,
cssText;

options = elem.getElementsByTagName('option')
length = options.length
for(; i < length; i++) {
option = options[i]
value = option.value
text = option.innerText || option.textContent

obj = {
value: value,
text: text
}
if(option.selected) {
selectedValue = value
selectedText = text
obj['selected'] = true
}
data.push(obj)
}

lis = this.render(this.tmpl, data)
ul = '<ul class="select-item">' + lis + '</ul>'
//
div = document.createElement('div')
div.style.display = 'none'
div.className = 'select-wrapper'
//已选元素
_default = document.createElement('div')
_default.className = 'select-default unselectable'
_default.unselectable = 'on'
//让div元素能够获取焦点
_default.setAttribute('tabindex', '1')
_default.setAttribute('data-value', selectedValue)
_default.setAttribute('hidefocus', true)
_default.innerHTML = selectedText
div.appendChild(_default)
//选择icon
icon = document.createElement('span')
icon.className = 'select-icon'
div.appendChild(icon)
//下拉列表
wrapper = document.createElement('div')
wrapper.className = 'select-list hide'
wrapper.innerHTML = ul
//生成新的元素
div.appendChild(wrapper)
//插入到select元素后面
elem.parentNode.insertBefore(div, null)
//获取select元素left top值
//先设置select显示,取完left, top值后重新隐藏
elem.style.display = 'block'
//事件绑定
this.sysEvent(div)
position = squid.position(elem)
elem.style.display = 'none'
left = position.left
top = position.top
cssText = 'left: ' + left + 'px; top: ' + top + 'px; display: block;'
div.style.cssText = cssText
}

create方法实现了将系统select数据拷贝到jselect下拉列表,jselect的层级关系是最外层有一个class为select-wrapper的元素包裹,里面有class为select-default的元素用于存放已选的元素,class为select-icon的元素用户告诉用户这是一个下拉列表,class为select-list的div元素里面包含了一个ul元素里面是从系统select拷贝的option的文本和值分别存放在li元素的文本和data-value属性。sysEvent方法是为jselect添加点击展开关闭下拉列表事件以及键盘上下选择下拉元素回车选中下拉元素事件。squid.position方法用于获取系统select元素相对于其offsetParent的位置,这里与获取系统select元素的offset是有区别。其实就是获取自己的offset得到top,left值然后分别减去offsetParent获取的offset的top,left值。最后是把jselect插入到系统select元素后面,显示到页面。

jselect创建的基本流程就是上面描述的这样,剩下就是细节地方的实现,比如说:点击展开下拉显示上次已选择的元素,具体实现该功能的是initSelected方法如下

复制代码 代码如下:

initSelected: function(elem) {
var curText = elem.innerText || elem.textContent,
curValue = elem.getAttribute('data-value'),
wrapper = elem.nextSibling.nextSibling,
n = wrapper.firstChild.firstChild,
text,
value,
dir,
min = 0,
max,
hidden = false;

for(; n; n = n.nextSibling) {
text = n.innerText || n.textContent
value = n.getAttribute('data-value')
if(curText === text && curValue === value) {
//显示已选中元素
if(squid.isHidden(wrapper)) {
wrapper.style.display = 'block'
hidden = true
}
max = wrapper.scrollHeight
if(n.offsetTop > (max / 2)) {
if(wrapper.clientHeight + wrapper.scrollTop === max)
dir = 'up'
else
dir = 'down'
}else{
if(wrapper.scrollTop === min)
dir = 'down'
else
dir = 'up'
}
this.inView(n, wrapper, dir)
if(hidden)
wrapper.style.display = 'none'
this.activate(n)
break
}
}
}

该方法接收class为select-default的div元素即用于存放用户已选择内容的元素,具体实现方式是先遍历所有选项获取class有selected的li元素,通过activate方法标示为当前已选中的元素。这里有一个需要计算的地方,就是每次展开下拉列表都要将已选中的元素滚动到页面可视区。因为有可能下来列表内容很多,但是下拉列表的外层select-list会有一个最大的高度,超过最大高度会出现滚动条,默认不做计算的话有可能已选中的元素会在滚动条下面或者是滚动条上面,所以需要通过计算来重置容器滚动条的位置。具体是已选中内容显示到滚动条的上面还是下面需要根据已选中元素的offsetTop值是否大于外层容器select-list的实际高度一半,把已选中元素显示到可视区的方式是inView方法。inView方法如下

复制代码 代码如下:

inView: function(elem, wrapper, dir) {
var scrollTop = wrapper.scrollTop,
//已选中元素offsetTop
offsetTop = elem.offsetTop,
top;

if(dir === 'up') {
if(offsetTop === 0) {
//滚动条置顶
wrapper.scrollTop = offsetTop;
}else if(offsetTop < scrollTop) {
top = offsetTop - scrollTop
//滚动条滚动到top值
this.scrollInView(wrapper, top)
}
}else{
var clientHeight = wrapper.clientHeight;

if(offsetTop + elem.offsetHeight === wrapper.scrollHeight) {
wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight
}else if(offsetTop + elem.offsetHeight > clientHeight + scrollTop) {
top = (offsetTop + elem.offsetHeight) - (scrollTop + clientHeight)
this.scrollInView(wrapper, top)
}
}
}

inView方法需要判断是向上滚动还是向下滚动,scrollInView方法代码很简单就是把下拉列表外层容器的scrollTop设置为指定的值。方法实现如下

复制代码 代码如下:

scrollInView: function(elem, top) {
setTimeout(function() {
elem.scrollTop += top
}, 10)
}

这个方法实现放到了setTimeout里面做了一个延迟添加到javascript执行队列里面,主要解决的是IE8下展开下拉列表滚动条会最终滚动到顶部,忽略代码设置的scrollTop(从表现上来看好像对scrollTop的设置也能生效,但是最后会重置滚动条到顶部,不知道IE8为什么会有这个问题。),不能把已选中的元素显示到可视区范围,其他浏览器下不会有这个问题。
整个的实现细节大致就这么多,键盘上下键回车键,关闭下拉列表逻辑都很简单。
遇到的问题
如何让div获取焦点来响应键盘keydown, keyup, keypress事件,到谷歌(非常时期谷歌都不好用了,没办法谁让这是咱的特色呢)查找一些资料最后发现需要为div元素设置tabindex属性,这样就可以让div元素获取焦点,来响应用户的操作。因为浏览器在默认情况下双击或者是点击太频繁的话会选中当前区域,为了取消这个默认操作给用户一个好的体验需要为div元素添加一个属性unselectable,不过这个属性只能适用于IE浏览器,其他浏览器下可以通过添加一个class名字是unselectable来避免这个问题。其他的问题都是逻辑上的控制,还有一些位置的计算了,这里就不再说了。
使用方法
首先是在页面模板把希望通过jselect替换的元素隐藏或者不做任何处理,默认情况下jselect会获取页面所有select依次替换,如果不希望jselect替换的select元素
需要添加自定义属性data-enabled="true"。当然添加data-enabled="false"和没有这个自定义属性一样都会被jselect替换。在使用的过程中可能对于布局结构比较复杂的页面还会有其他的问题,因为我测试的页面结构很简单,所以可能没有测试出来。
使用jselect需要先引入squid.js,然后引入jselect-1.0.js, jselect-1.0.css文件,在需要调用jselect的地方通过如下的调用方式来初始化jselect:squid.swing.jselect();
注:jselect源码以及demo可以通过这里下载。

时间: 2024-11-03 18:21:50

javascript模拟select,jselect的方法实现_javascript技巧的相关文章

javascript获取select值的方法分析_javascript技巧

本文实例讲述了javascript获取select值的方法.分享给大家供大家参考.具体分析如下: 1. 获取显示的汉字 复制代码 代码如下: document.getElementById("bigclass").options[window.document.getElementById("bigclass").selectedIndex].text 2. 获取数据库中的id 复制代码 代码如下: window.document.getElementById(&q

JavaScript中setter和getter方法介绍_javascript技巧

javascript中的setter.getter是平时接触比较少的方法,其本身也并不是标准方法,只在非ie浏览器里支持(ie内核也许有其他方法可以做到呢?暂时不知其解),但是加以利用可以做许多事情,比如: 1.对数据的访问限制: a.value是对value变量的getter方法调用,如果在getter方法实现中抛出异常,可以阻止对value变量的访问 2.对dom变量进行监听: window.name是一个跨域非常好用的dom属性(大名鼎鼎,详见百度),如果覆盖window.name的set

关于JavaScript中事件绑定的方法总结_javascript技巧

最近收集了一些关于JavaScript绑定事件的方法,汇总了一下,不全面,但是,希望便于以后自己查看. JavaScript中绑定事件的方法主要有三种: 1 在DOM元素中直接绑定 2 JavaScript代码中直接绑定 3 绑定事件监听函数 一.在DOM元素中直接绑定 也就是直接在html标签中通过 onXXX="" 来绑定.举个例子: <input type="button" value="点我呦" onclick="aler

把json格式的字符串转换成javascript对象或数组的方法总结_javascript技巧

第一种 JSON.parse(jsonString) 第二种 eval("("+jsonString+")") 第三种 var obj=(function ToJSON(o){ if(typeof(o)=="string")try{return new Function("return "+o)();}catch(e){return null;} })("[{name:'zhangsan',age:'24'},{na

JavaScript用select实现日期控件_javascript技巧

代码很简单,这里就不多废话了,直接给大家源码吧 <!doctype html> <html> <head> <title>年月日</title> </head> <body onLoad="init()"> <select id="year" onChange="swap_day()"></select>年 <select id=&q

javascript多物体运动实现方法分析_javascript技巧

本文实例讲述了javascript多物体运动实现方法.分享给大家供大家参考,具体如下: 这里需要注意:每个运动物体的定时器作为物体的属性独立出来互不影响,属性与运动对象绑定,不能公用. 运行效果截图如下: 例子: <!doctype html> <html> <head> <meta charset="utf-8"> <title>多物体运动</title> <style> div{ width:100

JavaScript中textRange对象使用方法小结_javascript技巧

TextRange对象是动态HTML(DHTML)的高级特性,使用它可以实现很多和文本有关的任务,例如搜索和选择文本.文本范围让您可以选择性的将字符.单词和句子从文档中挑选出来.TextRange对象是在HTML文档将要显示的文本流上建立开始和结束位置的抽象对象. 下面是TextRange的常用属性与方法: 属性 boundingHeight 获取绑定TextRange对象的矩形的高度 boundingLeft 获取绑定TextRange 对象的矩形左边缘和包含TextRange对象的左侧之间的

javascript实现跨域的方法汇总_javascript技巧

由于同源策略的限制,XMLHttpRequest只允许请求当前源(包含域名.协议.端口)的资源. json与jsonp的区别:     JSON是一种数据交换格式,而JSONP是一种依靠开发人员创造出的一种非官方跨域数据交互协议. script标签经常被用来加载不同域下的资源,可以绕过同源策略.(有src属性的都可以获取异域文件). 如果请求的这个远程数据本身就是一段可执行的js,那么这些js会被执行(相当于eval). 方法一: 利用script标签请求(<script src="htt

javascript数组去重的六种方法汇总_javascript技巧

面试前端必须准备的一个问题:怎样去掉Javascript的Array的重复项.据我所知,百度.腾讯.盛大等都在面试里出过这个题目. 这个问题看起来简单,但是其实暗藏杀机. 考的不仅仅是实现这个功能,更能看出你对计算机程序执行的深入理解.     我总共想出了三种算法来实现这个目的: Array.prototype.unique1 = function() { var n = []; //一个新的临时数组 for(var i = 0; i < this.length; i++) //遍历当前数组