1 .dropdown-menu { 2 position: absolute; 3 top: 100%; 4 left: 0; 5 z-index: 1000; 6 display: none; 7 float: left; 8 min-width: 160px; 9 padding: 5px 0; 10 margin: 2px 0 0; 11 font-size: 14px; 12 text-align: left; 13 list-style: none; 14 background-color: #fff; 15 -webkit-background-clip: padding-box; 16 background-clip: padding-box; 17 border: 1px solid #ccc; 18 border: 1px solid rgba(0, 0, 0, .15); 19 border-radius: 4px; 20 -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); 21 box-shadow: 0 6px 12px rgba(0, 0, 0, .175); 22 }
對於一個下拉菜單來講,最重要的就是下拉區域的定位問題,bootstrap經過top:100%這個屬性設置,輕鬆地完成下拉菜單的定位問題,不須要任何的js計算操做。 細心的話就會發現,只要控制下拉元素的display就能夠簡單地實現下拉的效果,不過bs爲了兼容更多的場景,加入動畫的效果,js的處理也並不簡單。下面分析理解dropdown.js的源碼。jquery
1 $(document) 2 .on('click.bs.dropdown.data-api', clearMenus)//目的是爲了在展開下拉菜單後,再次點擊頁面任何區域都能隱藏以前已經展開的菜單,考慮的是一個頁面中存在多個下拉菜單的狀況,可是也有一個問題,就是點擊展開的下拉菜單裏面的菜單項,一樣會隱藏菜單,這在不須要隱藏菜單的場景中將會是一個問題 3 .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })//假以下拉菜單裏有表單元素時,經過冒泡阻止菜單的隱藏,就是阻止第一行代碼裏的監聽器響應點擊事件 4 .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)//自動註冊dropdown組件 5 .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)//在toggle元素獲取焦點後,容許按向下箭頭展開菜單 6 .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)//容許在展開菜單後,能夠經過向上向下箭頭,切換菜單項 7 .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)//容許在展開菜單後,能夠經過向上向下箭頭,切換菜單項
關於對click.bs.drop.data-api的理解web
①首先,Jquery的APIbootstrap
1 events,[selector],[data],fnV1.7 2 3 events:一個或多個用空格分隔的事件類型和可選的命名空間,如"click"或"keydown.myPlugin"
關於參數[selector]
,你能夠簡單地理解爲:若是該參數等於null
或被省略,則爲當前匹配元素綁定事件;不然就是爲當前匹配元素的後代元素中符合selector
選擇器的元素綁定事件。api
②命名空間的理解dom
1 var backdrop = '.dropdown-backdrop' 2 var toggle = '[data-toggle="dropdown"]' 3 var Dropdown = function (element) { 4 $(element).on('click.bs.dropdown', this.toggle) 5 } 6 7 Dropdown.VERSION = '3.3.4' 8 9 function Plugin(option) { 10 return this.each(function () { 11 var $this = $(this) 12 var data = $this.data('bs.dropdown') 13 14 if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) 15 if (typeof option == 'string') data[option].call($this) 16 }) 17 } 18 19 var old = $.fn.dropdown 20 21 $.fn.dropdown = Plugin 22 $.fn.dropdown.Constructor = Dropdown 23 24 25 // DROPDOWN NO CONFLICT 26 // ==================== 27 28 $.fn.dropdown.noConflict = function () { 29 $.fn.dropdown = old 30 return this 31 }
1 Dropdown.prototype.toggle = function (e) { 2 var $this = $(this) 3 4 if ($this.is('.disabled, :disabled')) return 5 6 var $parent = getParent($this)//getParent方法用來獲取父元素,不過這個父元素頗有可能不是dom結構上的父節點,而是經過data-target或者href指定的某個dom元素,因此纔有專門的一個方法來寫 7 var isActive = $parent.hasClass('open')//父元素有open類,則說明菜單當前是已經展開的 8 9 clearMenus()//先清空已經展開的全部菜單 10 11 //只有在菜單未展開的時候才進行如下邏輯處理 12 if (!isActive) { 13 if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 14 // if mobile we use a backdrop because click events don't delegate 15 $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus) 16 } 17 //以上代碼的目的是爲了在移動端裏展開菜單後,點擊頁面任何區域都能隱藏菜單,由於bs的事件都是經過代理註冊的監聽器,而click事件在移動端裏若是是經過代理註冊的,不會執行相應的監聽器,因此bs才用了backdrop這樣的一個元素,替代實現隱藏菜單的功能。 18 19 var relatedTarget = { relatedTarget: this } 20 $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) 21 22 if (e.isDefaultPrevented()) return 23 24 $this 25 .trigger('focus') 26 .attr('aria-expanded', 'true') 27 28 $parent 29 .toggleClass('open') 30 .trigger('shown.bs.dropdown', relatedTarget) 31 } 32 33 return false 34 } 35 36 Dropdown.prototype.keydown = function (e) { 37 if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return 38 //若是按鍵不是向上向下箭頭,空格和ESC鍵,或者按鍵是爲了在文本控件裏輸入字符,就不作如下處理 39 //注意按下回車鍵,會觸發click事件!!! 40 41 var $this = $(this) 42 43 e.preventDefault() 44 e.stopPropagation() 45 46 if ($this.is('.disabled, :disabled')) return 47 48 var $parent = getParent($this) 49 var isActive = $parent.hasClass('open') 50 51 if ((!isActive && e.which != 27) || (isActive && e.which == 27)) { 52 //實現的就是按向上向下和空格鍵展開菜單,再按esc鍵隱藏菜單。。。 53 if (e.which == 27) $parent.find(toggle).trigger('focus')//這裏須要用find(toggle)的緣由是由於,$this有多是[role=menu]的dom元素而不是data-toggle元素 54 return $this.trigger('click') 55 } 56 57 var desc = ' li:not(.disabled):visible a' 58 var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc) 59 60 if (!$items.length) return 61 62 var index = $items.index(e.target) 63 64 if (e.which == 38 && index > 0) index-- // 按向上鍵,index-- 65 if (e.which == 40 && index < $items.length - 1) index++ // 按向下鍵,index++ 66 if (!~index) index = 0 67 //~index的做用:~3 = -4,~4=-5,~5=-6,~0=-1,~-1=0,~-2=1,~-3=2,其實不必搞這種寫法,尼瑪。。。 68 69 $items.eq(index).trigger('focus') 70 } 71 72 function clearMenus(e) { 73 if (e && e.which === 3) return 74 $(backdrop).remove() 75 $(toggle).each(function () { 76 var $this = $(this) 77 var $parent = getParent($this) 78 var relatedTarget = { relatedTarget: this } 79 80 if (!$parent.hasClass('open')) return 81 82 $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) 83 84 if (e.isDefaultPrevented()) return 85 86 $this.attr('aria-expanded', 'false') 87 $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) 88 }) 89 } 90 91 function getParent($this) { 92 var selector = $this.attr('data-target') 93 94 if (!selector) { 95 selector = $this.attr('href') 96 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 97 } 98 99 var $parent = selector && $(selector) 100 101 return $parent && $parent.length ? $parent : $this.parent() 102 }