javascript類比select,jselect的方法實現

來源:互聯網
上載者:User

由於主流瀏覽器對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事件,到Google(非常時期Google都不好用了,沒辦法誰讓這是咱的特色呢)尋找一些資料最後發現需要為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可以通過這裡下載。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.