Bootstrap简单认识之Tooltips组件

Tooltips(提示工具)组件

一、简介

  1. 此组件用到了Tether插件,Tether一般用于搞定两个元素的位置关系,如下代码:

    new Tether({
      // 此为主动附着元素
      element: blurEle,
      // 此为被依附元素
      target: greenEle,
      // 主动依附和被动依附都可以选择位置,两者通过x,y确定一个点
      // 最后呈现出来就是两个点重合
      // 主动依附
      attachment: 'bottom center',
      // 被动依附
      targetAttachment: 'top center'
    })

    那么所呈现出来的就是蓝色元素的左下角与绿色元素的顶部中间重合

  2. 在指定 delay 即延迟弹出(收回)的情况下,且假设触发事件是click,一次点击后tooltip还没出来,立刻进行下一次点击,此时不是重置计数器,而是取消tooltip的弹出
  3. 此组件的初始模板为

    <div class="tooltip" role="tooltip">
     <div class="tooltip-inner">
     div>
    div>

    最简单的用法是在需要触发tooltip的元素标签上指定 title 属性,继而通过 $().tooltip() 初始化

  4. 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事件的回调过程

  1. 调用 toggle(event) 函数。

    • 如果是由click事件触发(click事件会传event参数),标记此时click使得tooltip弹出(如果同时支持hover触发,如果此时有click trigger的标记,hover out了也不会移除tooltip),只要有 click trgger 或是 hover trigger 或是 focusin trigger 的任何一个标记,则触发_enter函数
    • 如果是用户js手动触发,根据tooltip元素是否有 .show 类来选择触发 _enter
  2. 调用 _enter(event, context) 函数

    • 如果是由hover或focusin事件触发(click事件不会传event参数),为 hover trigger 或 focusin trigger 做标记。

    做另外一个标记HoverState,标记此时需要将 tooltip打开;继而判断是否有 delay 属性值,有则调用setTimeout延迟打开 tooltip

  3. 调用 show() 函数

    1. 触发 SHOW 钩子
    2. 获取 tooltip 元素,更改其 id 值为一个随机值(每次弹出都会变),将 title 或是 data-title 属性的内容插入 tooltip 元素中的 .tooltip-inner 元素下。
    3. 为tooltip元素也绑定Tooltip实例(与触发元素共享)
    4. 插入tooltip元素到document中,触发INSERTED钩子
    5. 使用Tether将tooltip放到指定的位置
    6. 为tooltip添加show类,(如果有动画)在动画结束后触发SHOWN钩子
    7. 假如在动画期间,鼠标离开了此元素,执行了_leave函数,此时的_hoverState就成了 out,虽然这次因为_isTransitioning的存在不会执行hide函数。动画结束后执行的回调中,会判断_hoverStat状态,若是out,则执行_leave函数

细节回顾

  1. 获取 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) } }

补充知识

  1. Array.from 将类数组转化为数组规则

    • array-like objects (objects with a length property and indexed elements)
    • iterable objects (objects where you can get its elements, such as Map and Set).
    • 下面生成一个从0到100的数组:
      
      // 语法: 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数组

  2. bootstrap提供了class为.disabled的类,表明不可用,但依旧会触发事件,需要在函数中通过$(this).hasClass('disabled')控制

你可能感兴趣的:(前端)