使用方法
有含有"dropdown"类的容器里面包含下拉触发器(按钮,或链接等),和下拉框的内容
- 下拉触发器必须包含"data-toggle=dropdown"的属性。可以用data-target或href指定包含下拉框的容器,如果没有,则从其父元素内找
- 下拉框内容必须有"dropdown-menu"的类
<div class="dropdown">
<button data-toggle="dropdown">点击</button>
<ul class="dropdown-menu">
<li>item1</li>
<li>item1</li>
<li>item3</li>
</ul>
</div>
核心思想
- 页面完成载入后,为documen和data-toggle="dropdown"绑定点击处理程序
- 点击document,则清除页面内所有下拉框的父元素的"open"类,从而隐藏下拉框
- 点击触发下拉框的元素,为其父元素增加"open"类,同时return false阻止冒泡,防止冒泡到document上调用隐藏下拉框的处理函数
初始化
在页面加载完就绑定document即对应下拉框触发器的点击事件
// 事件触发顺序是,如果指定事件目标,则先触发目标上的绑定事件处理程序,然后再冒泡到document
$(document)
.on('click.bs.dropdown.data-api', clearMenus) // 绑定document点击时,调用clearMenus事件处理程序
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) // 事件代理,在dropdown form上点击会阻止冒泡
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) // 在含有data-toggle="dropdown"的元素除点击,会调用toggle方法。这里会先触发
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
结构分析
// 绑定触发元素的点击事件
var Dropdown = function (element) {};
// 先调用clearMenu隐藏所有下拉框,在展示当前的下拉框
Dropdown.prototype.toggle = function (e) {};
// 清除全部容器的open类
function clearMenus(e) {};
// 获得触发元素指定的容器或其父元素
function getParent($this) {};
具体分析
/**
* 只是对传进来的元素绑定点击事件
*/
var Dropdown = function (element) {
$(element).on('click.bs.dropdown', this.toggle)
}
/** @type {String} 用于匹配backdrop类的字符串 */
var backdrop = '.dropdown-backdrop'
/** @type {String} 用于匹配含有data-toggle触发器的元素的字符串 */
var toggle = '[data-toggle="dropdown"]'
/**
* 先需找在触发器通过data-target或href指定的容器,若没有则默认是其父容器
* @param {jquery对象} $this 有data-toggle的触发器
* @return {jquery对象} 包含触发器的容器
*/
function getParent($this) {
var selector = $this.attr('data-target')
if (!selector) {
selector = $this.attr('href')
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
var $parent = selector && $(selector)
// 可以在触发器上用data-target或href来自定义父容器
// 如果没有自定义容器,则默认是触发器的父元素
return $parent && $parent.length ? $parent : $this.parent()
}
/**
* each遍历,把所有容器的open类去除,以此保证只能有一个出现
* @param {e} e 事件对象
* @return {undefined} 无返回
*/
function clearMenus(e) {
if (e && e.which === 3) return
$(backdrop).remove()
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this)
var relatedTarget = { relatedTarget: this }
// 如果容器没有open,就不继续
if (!$parent.hasClass('open')) return
// 例外条件
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
// 不明白干什么的
$this.attr('aria-expanded', 'false')
// 隐藏下拉框
$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
})
}
/**
* 下拉框的toggle,并返回false阻止冒泡到document上
* @param {object} e 事件对象
* @return {boolean} false
*/
Dropdown.prototype.toggle = function (e) {
var $this = $(this)
if ($this.is('.disabled, :disabled')) return
// 包含下拉框的容器
var $parent = getParent($this)
// 通过容器切换open类,来控制下拉框的显示与隐藏
var isActive = $parent.hasClass('open')
// 先隐藏页面的所有下拉框
clearMenus()
// 如果点击元素的容器没有.open(即下拉框隐藏)
if (!isActive) {
// 用ontouchstart来判断是否移动端
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// if mobile we use a backdrop because click events don't delegate
// 移动端的click事件不会冒泡到document?实验证明,没有这一段,也能实现点击下拉框外关闭的效果
// 移动端插入一个dropdown-backdrop元素(全屏但z-index比下拉框小),做点击下拉框外时隐藏的效果
$(document.createElement('div'))
.addClass('dropdown-backdrop')
.insertAfter($(this))
.on('click', clearMenus)
}
var relatedTarget = { relatedTarget: this }
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return
$this
.trigger('focus')
.attr('aria-expanded', 'true')
$parent
.toggleClass('open')
.trigger('shown.bs.dropdown', relatedTarget)
}
// 在这里return false阻止冒泡到document上,导致下拉框隐藏
return false
}
- keydown方法太烂了,多数情况下都没有用,暂不分析
总结:
- 事件处理函数的调用顺序:先调用目标元素上的,再调用冒泡到的
- 可以使用'ontouchstart' in document.documentElement来判断是否移动端
- $(document).on(click, clearMenu)实现点击下拉框外隐藏下拉框(其实$el.cloest()也可以实现)
- 使用toggle类比增加样式更优雅
- CSS
- 使用一个比下拉框的z-index低的dropdown-backdrop也能实现点击下拉框外隐藏下拉框
- 设置position: absolute;top: 100%实现在父元素的下方出现下拉框
- postion定位后的top|bottom|left|right单位为百分比时,是相对于其父元素的宽度和高度
- margin的top|bottom等单位为百分比时,也是相对于其父元素的宽度和高度
- transform里的translate:(50%, 50%)是相对于本身的宽度和高度