此组件用到了Tether插件,Tether一般用于搞定两个元素的位置关系,如下代码:
new Tether({
// 此为主动附着元素
element: blurEle,
// 此为被依附元素
target: greenEle,
// 主动依附和被动依附都可以选择位置,两者通过x,y确定一个点
// 最后呈现出来就是两个点重合
// 主动依附
attachment: 'bottom center',
// 被动依附
targetAttachment: 'top center'
})
那么所呈现出来的就是蓝色元素的左下角与绿色元素的顶部中间重合
delay
即延迟弹出(收回)的情况下,且假设触发事件是click,一次点击后tooltip还没出来,立刻进行下一次点击,此时不是重置计数器,而是取消tooltip的弹出此组件的初始模板为
<div class="tooltip" role="tooltip">
<div class="tooltip-inner">
div>
div>
最简单的用法是在需要触发tooltip的元素标签上指定 title
属性,继而通过 $().tooltip()
初始化
tooltip
元素隐藏后会被从document中移除,而每次显现则再次插入到document中,在元素被插入document后,会触发 INSERTED
事件主要就是两个类的样式: tooltip
+ tooltip-inner
关于内层的 .tooltip-inner
.tooltip-inner {
// 指定了最大宽大,如果要在内部添加元素,则需要注意此限制
max-width: $tooltip-max-width;
... ...
}
脚本主要用于控制tooltip的隐藏与显现,用户可以通过在初始化函数中传入对象,改变元素触发tooltip的方式,可以是 click, hover, focusin
这三种触发方式中一种或是多种组合。
默认属性
// 下面是默认属性,用户可以选择性的覆盖这些属性
const Default = {
// 是否有动画
animation : true,
// tooltip模板,一般是将内容插入到 '.tooltip-inner' 中
template : ''
+ '',
// 触发tooltip的方式
trigger : 'hover focus',
// 可以是某个dom元素或是jquery对象或是一个函数(this指向依附的元素)
// 注意这里是 data-title 属性
title : '',
// 传入对象 {show: 500, hide: 300} 单位是毫秒 或是 一个整数
delay : 0,
// 弹出的tooltip是否允许被渲染为html节点(内容)
// 因为使用者可以为 data-title 属性赋值为节点或函数,若html===false,那么最终显示在
// template里的内容是 $(节点).text(),否则就是整个$(节点)
html : false,
// 事件代理,即$('#aaa').tooltip({selector:$('.bbb')}),要求b是a的子元素,最终触发tooltip
// 的就是 .bbb 元素。作用就与事件代理一致,可以动态的添加包含 bbb 类的元素,自带tooltip的效果
selector : false,
// tooltip显示的位置
placement : 'top',
// (属于tether插件)偏移量 tether 默认情况下将两个元素紧紧的贴合在一起,如果想要像 tooltip一样显示一个小三角就必须用到
offset : '0 0',
// (属于tether插件)未知
constraints : [],
// tooltip元素是凭空生成的,在隐藏的时候不存在在document中
// 显现此元素的时候将tooltip放在哪个父元素下面?默认是body
container : false
}
值得注意的是,如果使 html === true
,即想要在tooltip中添加html内容,可能需要手动修改 .tooltip
和 .tooltip-inner
类的样式。
代码梗概
class Tooltip {
constructor(element, config) {
// 是否激活
this._isEnabled = true
// dealy的settimeout的返回值,用于clear
this._timeout = 0
// 用于控制在tooltip正在进行打开动画的状态下,触发了_leave函数,虽然会因为_isTransitioning不会执行hide函数,但
// 会更改this._hoverState的值为HoverState.OUT
// 在tooltip真正打开后,如果此状态为HoverState.OUT,那么依旧会执行一下_leave函数
this._hoverState = ''
// 判断当前激活状态,是由甚么激活的
this._activeTrigger = {}
this._isTransitioning = false
this._tether = null
// protected
// 指向触发tooltip的元素
this.element = element
this.config = this._getConfig(config)
// 指向tooltip元素
this.tip = null
// 为触发tooltip的事件绑定回调函数
this._setListeners()
}
// public公共函数
// 让此组件可用或不可用
toggleEnabled() {}
// 隐藏或显现tooltip
toggle(event) {}
// 辅助函数
// 判断是否有内容放到tooltip中
isWithContent() {}
// 如果是第一次,那么取模板; 否则获取之前隐藏的tooltip元素
getTipElement() {}
// 将title内容或是 data-title 内容放到 .tooltip-inner 元素下
setContent() {}
// 获取tite属性或是data-title属性的值
getTitle() {}
// 为tooltip的触发钩子绑定触发事件
_setListeners() {}
// click事件调用此函数: _enter(null, context)
// hover, focusin事件调用: _enter(event)
_enter(event, context) {}
_leave(event, context) {}
// static
// 初始化入口
static _jQueryInterface(config) {
return this.each(function () {})
}
}
从整个组件的代码(部分未贴出)梗概来看,可以看出能触发tooltip的事件可以有三种: click, hover, focusin
;不同的事件调用不同的回调(只讨论让tooltip显现):
click
: toggle(event) ==> _enter(null, context) ==> show()hover or focusin
: _enter(event) ==> show()另一个地方是 _setListeners
函数,通过此函数可以发现selector
属性的作用
_setListeners() {
// 触发tooltip的方式,可以有多个 用空格隔开
const triggers = this.config.trigger.split(' ')
// 根据不同的触发方式注册不同的事件回调
triggers.forEach((trigger) => {
// click事件注册 toggle 函数
// jQuery.on函数,若委托元素this.config.selector为null,表示事件就绑定在$(this.element)上
if (trigger === 'click') {
$(this.element).on(..., (event) => this.toggle(event))
} else if (trigger !== Trigger.MANUAL) {
...
// hover 与 focusin 事件注册 _enter, _leave函数
$(this.element)
.on(..., (event) => this._enter(event)
)
.on(..., (event) => this._leave(event)
)
}
// 如果是放在Modal中的,那么Modal在hide后也会将这些tooltip关掉
$(this.element).closest('.modal').on(
'hide.bs.modal',
() => this.hide()
)
})
// 如果在初始化这个实例的时候发现其实触发tooltip的元素
// 应该是子元素 this.config.selector
if (this.config.selector) {
this.config = $.extend({}, this.config, {
trigger : 'manual',
selector : ''
})
} else {
// 正是这个元素会触发 tooltip,那么就会去处理他的title属性(不是data-title)
this._fixTitle()
}
}
selector
的属性可见是用于事件委托绑定 $(parent).on('click', ?, func)
的第二个参数的,也就明白了为什么 selector
必须是$(parent)元素的子元素了。这里也可以发现并没有为真正触发tooltip的元素创建实例,不过后面会由
这里仍需要讨论一下 _fixTitle
函数,此组件允许用户将 title
属性中的内容放到 tooltip 中。如果这个触发tooltip的元素有title属性,此函数清除此属性,且将此属性值移植到属性 data-original-title 上(因为title属性本身在鼠标hover的时候会有提示,这么做在保存title值的同时,也移除了这个负面效果)
CLICK 事件回调
既然 click
事件的回调覆盖了所有主要函数,那么这里就着重讨论click打开tooltip事件的回调过程
调用 toggle(event)
函数。
.show
类来选择触发 _enter 调用 _enter(event, context)
函数
hover或focusin
事件触发(click事件不会传event参数),为 hover trigger 或 focusin trigger 做标记。做另外一个标记HoverState
,标记此时需要将 tooltip打开;继而判断是否有 delay
属性值,有则调用setTimeout延迟打开 tooltip
调用 show()
函数
.tooltip-inner
元素下。细节回顾
获取 tooltip
的填充内容
title
属性获取。 文章开头介绍的 _fixTitle
属性已经对 title
属性的处理以及处理原因进行了介绍data-title
属性获取。 // 获取内容
getTitle() {
// 获取title, 没有的话获取 data-title
let title = this.element.getAttribute('data-original-title')
if (!title) {
title = typeof this.config.title === 'function' ?
this.config.title.call(this.element) :
this.config.title
}
return title
}
// 插入内容
setElementContent($element, content) {
const html = this.config.html
// 可以为 data-title 属性指定一个节点,用于插入template
if (typeof content === 'object' && (content.nodeType || content.jquery)) {
// content is a DOM node or a jQuery
// 如果不用担心XSS攻击,那么可以插入一个节点
if (html) {
if (!$(content).parent().is($element)) {
$element.empty().append(content)
}
} else {
// 担心的话还是不要允许直接添加HTML内容
$element.text($(content).text())
}
} else {
// 也可以在title属性上指定html内容(类似这种'')
$element[html ? 'html' : 'text'](content)
}
}
Array.from
将类数组转化为数组规则
// 语法: Array.from(arrayLike[, mapFn[, thisArg]])
Array.from({length:100}).map((item, index) => index)
// 或是 Array.from(obj, mapFn, thisArg) has the same result as Array.from(obj).map(mapFn, thisArg)
Array.from({length:100}, (item, index)=>index)
// 第三个参数 thisArg用于改变map函数中的this指向
Array.from([1, 2, 3], function(x){return x + this.a},{a:3})
// 上面可别用es6的箭头函数,箭头函数的this继承自上下文
jQuery选择器获取的多是Array-Like类型,可以通过 Array.from
将其转换为数组类型(即 Array.from($('xxx')) instanceof Array === true
),只是转换后不再是jQuery类型,而是DOM类型了
不过,jQuery自身就有这种函数,即 $('xxx').get()
,参数可以是一个数组下标($(this).get(0)与$(this)[0]
等价),可以不填写下标,作用就是转换为DOM数组
.disabled
的类,表明不可用,但依旧会触发事件,需要在函数中通过$(this).hasClass('disabled')
控制