bootstrap之collapse

一、前言

这节介绍下collapse(手风琴)模块的源码实现。

二、源码

 /* ========================================================================
  * Bootstrap: collapse.js v3.3.7(折叠/手风琴)
  * http://getbootstrap.com/javascript/#collapse
  * ========================================================================
  * Copyright 2011-2016 Twitter, Inc.
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  * ======================================================================== */

 /* jshint latedef: false */

 +function ($) {
   'use strict';

   // COLLAPSE PUBLIC CLASS DEFINITION
   // ================================
   var Collapse = function (element, options) {
     this.$element      = $(element)
     this.options       = $.extend({}, Collapse.DEFAULTS, options)
     this.$trigger      = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
                            '[data-toggle="collapse"][data-target="#' + element.id + '"]')
     // 变量:是否正在过渡动画中
     this.transitioning = null

     if (this.options.parent) {
       this.$parent = this.getParent()
     } else {
       this.addAriaAndCollapsedClass(this.$element, this.$trigger)
     }

     if (this.options.toggle) this.toggle()
   }

   Collapse.VERSION  = '3.3.7'

   Collapse.TRANSITION_DURATION = 350

   Collapse.DEFAULTS = {
     toggle: true
   }

   // 高度还是宽度发生改变
   Collapse.prototype.dimension = function () {
     var hasWidth = this.$element.hasClass('width')
     return hasWidth ? 'width' : 'height'
   }

   // 显示
   Collapse.prototype.show = function () {
     // 如果正在动画过渡中,或者已经显示,那么直接返回不做任何处理
     if (this.transitioning || this.$element.hasClass('in')) return

     var activesData
     // 展开或者正在展开的手风琴控制面板
     var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')

     // 如果手风琴控制面板正在过渡中,则不作任何处理(这里只考虑actives.length为1的处理,即控制面板只有单个展开的情况)
     if (actives && actives.length) {
       activesData = actives.data('bs.collapse')
       if (activesData && activesData.transitioning) return
     }

     var startEvent = $.Event('show.bs.collapse')
     this.$element.trigger(startEvent)
     if (startEvent.isDefaultPrevented()) return

     if (actives && actives.length) {
       // 将之前拓展开的控制面板隐藏起来
       Plugin.call(actives, 'hide')
       // 存在一种情况:如果一开始有控制面板展开,这时候点击其他面板,展开的面板会被触发隐藏同时初始化bs.collapse对象('hide'参数)存储到
       // data中,下次点击最开始展开的面板时,这时候会从data中读取bs.collapse作为对象而不是重新初始化,然后触发toggle,但是因为并没有将
       // parent传到option中,所以后面点击的面板并不会收拢;这里做的处理是将所有已经展开的面板都隐藏,而且清除掉存在data的bs.collapse数
       // 据,下次点击重新初始化
       // 不过其实将对象存在data的bs.collapse属性中还是有必要的,如果多次点击同一面板触发器,可以避免多次初始化对象
       activesData || actives.data('bs.collapse', null)
     }

     var dimension = this.dimension()

     // 目标元素移除collapse类,添加collapsing,设置height为0
     this.$element
       .removeClass('collapse')
       .addClass('collapsing')[dimension](0)
       .attr('aria-expanded', true)

     // 触发元素移除collapsed类
     this.$trigger
       .removeClass('collapsed')
       .attr('aria-expanded', true)

     this.transitioning = 1

     var complete = function () {
       // 目标元素移除collapsing类,添加.collapse .in类,并重置height
       this.$element
         .removeClass('collapsing')
         .addClass('collapse in')[dimension]('')
       this.transitioning = 0
       this.$element
         .trigger('shown.bs.collapse')
     }

     // 如果设备不支持动画,那么直接执行完成函数
     if (!$.support.transition) return complete.call(this)

     var scrollSize = $.camelCase(['scroll', dimension].join('-'))

     // 设置函数动画结束监听函数,并设置height为目标元素的scrollHeight
     this.$element
       .one('bsTransitionEnd', $.proxy(complete, this))
       .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
   }

   // 隐藏
   Collapse.prototype.hide = function () {
     if (this.transitioning || !this.$element.hasClass('in')) return

     var startEvent = $.Event('hide.bs.collapse')
     this.$element.trigger(startEvent)
     if (startEvent.isDefaultPrevented()) return

     var dimension = this.dimension()

     // 强制回流,为页面重绘做准备
     this.$element[dimension](this.$element[dimension]())[0].offsetHeight

     this.$element
       .addClass('collapsing')
       .removeClass('collapse in')
       .attr('aria-expanded', false)

     this.$trigger
       .addClass('collapsed')
       .attr('aria-expanded', false)

     this.transitioning = 1

     var complete = function () {
       this.transitioning = 0
       this.$element
         .removeClass('collapsing')
         .addClass('collapse')
         .trigger('hidden.bs.collapse')
     }

     if (!$.support.transition) return complete.call(this)

     this.$element
       [dimension](0)
       .one('bsTransitionEnd', $.proxy(complete, this))
       .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
   }

   // 切换手风琴显示隐藏
   Collapse.prototype.toggle = function () {
     this[this.$element.hasClass('in') ? 'hide' : 'show']()
   }

   // 找到手风琴目标元素并为所有拓展面板执行addAriaAndCollapsedClass方法
   Collapse.prototype.getParent = function () {
     return $(this.options.parent)
       .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
       .each($.proxy(function (i, element) {
         var $element = $(element)
         this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
       }, this))
       .end()
   }

   // 添加无障碍阅读属性和collapsed类
   Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
     var isOpen = $element.hasClass('in')

     $element.attr('aria-expanded', isOpen)
     $trigger
       .toggleClass('collapsed', !isOpen)
       .attr('aria-expanded', isOpen)
   }

   // 获取目标元素
   function getTargetFromTrigger($trigger) {
     var href
     var target = $trigger.attr('data-target')
       || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7

     return $(target)
   }

   // COLLAPSE PLUGIN DEFINITION
   // ==========================
   function Plugin(option) {
     return this.each(function () {
       var $this   = $(this)
       var data    = $this.data('bs.collapse')
       var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)

       // 如果直接用JS触发手风琴,而且option为show/hide字符串,那么没有不调用toggle方法,而直接调用相应hide/show方法
       if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
       if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
       if (typeof option == 'string') data[option]()
     })
   }

   var old = $.fn.collapse

   $.fn.collapse             = Plugin
   $.fn.collapse.Constructor = Collapse


   // COLLAPSE NO CONFLICT
   // ====================
   $.fn.collapse.noConflict = function () {
     $.fn.collapse = old
     return this
   }


   // COLLAPSE DATA-API
   // =================
   $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
     var $this   = $(this)

     if (!$this.attr('data-target')) e.preventDefault()

     var $target = getTargetFromTrigger($this)
     // 如果已经初始化了bs.collapse属性对应的对象,那么直接触发toggle方法(即除了第一次点击初始化之外)
     var data    = $target.data('bs.collapse')
     var option  = data ? 'toggle' : $this.data()

     Plugin.call($target, option)
   })

 }(jQuery);

三、应用 & 源码分析

1、应用
 <div class="panel-group" id="accordion">
   <div class="panel panel-default">
     <div class="panel-heading">
       <h4 class="panel-title">
         <a data-toggle="collapse" data-parent="#accordion"
            href="#collapseOne">
           点击我进行展开,再次点击我进行折叠。第 1 部分
         </a>
       </h4>
     </div>
     <div id="collapseOne" class="panel-collapse collapse in">
       <div class="panel-body">
         Nihil anim keffiyeh helvetica, craft beer labore wes anderson
         cred nesciunt sapiente ea proident. Ad vegan excepteur butcher
         vice lomo.
       </div>
     </div>
   </div>
   <div class="panel panel-default">
     <div class="panel-heading">
       <h4 class="panel-title">
         <a data-toggle="collapse" data-parent="#accordion"
            href="#collapseTwo">
           点击我进行展开,再次点击我进行折叠。第 2 部分
         </a>
       </h4>
     </div>
     <div id="collapseTwo" class="panel-collapse collapse">
       <div class="panel-body">
         Nihil anim keffiyeh helvetica, craft beer labore wes anderson
         cred nesciunt sapiente ea proident. Ad vegan excepteur butcher
         vice lomo.
       </div>
     </div>
   </div>
   <div class="panel panel-default">
     <div class="panel-heading">
       <h4 class="panel-title">
         <a data-toggle="collapse" data-parent="#accordion"
            href="#collapseThree">
           点击我进行展开,再次点击我进行折叠。第 3 部分
         </a>
       </h4>
     </div>
     <div id="collapseThree" class="panel-collapse collapse">
       <div class="panel-body">
         Nihil anim keffiyeh helvetica, craft beer labore wes anderson
         cred nesciunt sapiente ea proident. Ad vegan excepteur butcher
         vice lomo.
       </div>
     </div>
   </div>
 </div>

 <script>
   $(function () { $('#collapseFour').collapse({
     toggle: false
   })});
   $(function () { $('#collapseTwo').collapse('show')});
   $(function () { $('#collapseThree').collapse('toggle')});
   $(function () { $('#collapseOne').collapse('hide')});
 </script>
2、源码分析

1、data-parent
如果元素具有相同的 data-parent 属性,那么可以具有点击一个折叠面板,其他所有展开的面板均折叠
2、show
开始时每个面板均添加 collapse 类(display:none),动画准备时移除 collapse 并添加 collapsing 类(height:0;overflow:hidden;),
动画执行时 collapsing 移除添加 .collapse.in(display: block;) 并清空行内 height 达到过渡打开面板的效果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值