zoukankan      html  css  js  c++  java
  • jQuery-ui源代码重点难点分析

    本文以jquery-ui sortable排序功能插件为例分析jquery-ui源代码重点难点。

    jquery-ui-sortable插件非常复杂深奥,本来是针对鼠标操作设计的,在手机运行无效,因为手机没有mouse事件,更没有drag事件。

    但增加一个jquery-ui-touch-punch.js文件之后在手机运行也有效,touch有效,原理在于sortable源代码不是针对鼠标drag and drop设计的,而是针对mouse事件中的位置数据设计的,底层有复杂的算法,touch事件数据也包含位置数据,因此sortable能兼容touch,很强大。

    sortable插件的调用方式是$().sortable(opts),那么sortable()函数就是入口初始化函数,我们从这个入口初始化函数源代码开始分析。

    $.widget.bridge = function( name, object ) {
      var fullName = object.prototype.widgetFullName || name;
      $.fn[ name ] = function( options ) { // 此即为sortable()函数代码

        this.each(function() { // this是$()元素集合,一般只有一个节点,$().each()就是循环一次,元素就是container元素。
          var instance = $.data( this, fullName ); // this是元素集合里面每一个元素对象,这是从元素对象取比如ui-sortable属性值是一个实例
          if ( instance ) {
            instance.option( options || {} );
            if ( instance._init ) {
              instance._init();
            }
          } else {
            $.data( this, fullName, new object( options, this ) ); // 如果没有就建一个实例保存到元素属性中

              constructor = $[ namespace ][ name ] = function( options, element ) { // object代码
                // allow instantiation without "new" keyword
                if ( !this._createWidget ) {
                  return new constructor( options, element );
                }

                // allow instantiation without initializing for simple inheritance
                // must use "new" keyword (the code above always passes args)
                if ( arguments.length ) {
                  this._createWidget( options, element );                  

                    $.Widget.prototype = {
                      _createWidget: function( options, element ) {

                        this._create(); // 调sortable插件的内置方法初始化sortable插件,由于加了一层封装,用apply方式调用

                           

                            var sortable = $.widget("ui.sortable", $.ui.mouse, { // sortable插件的定义
                              _create: function() {

                                this.refresh();//根据已经存在的网页中的列表创建一套items[]
                                this._mouseInit(); //调用mouse插件的通用初始化方法,这是juqery-ui-touch修改之后的init代码

                                  $.ui.mouse.prototype._mouseInit=function(){
                                    var _this=this; 

                                    _this.element.bind("touchstart",$.proxy(_this,"_touchStart"))

                                      .bind("touchmove",$.proxy(_this,"_touchMove"))

                                      .bind("touchend",$.proxy(_this,"_touchEnd"));
                                      oldMouseInit.call(_this);

                                  };

                              };                     

                          this._trigger( "create", null, this._getCreateEventData() );
                          this._init(); // 空函数
                        }

    sortable入口函数就是创建一个实例保存在container元素属性中,先调用通用插件widget代码创建实例,再调用sortable插件的初始化方法,sortable插件初始化方法
    创建items[]保存在sortable实例中,执行mouse初始化代码,如果执行了jquery-ui-touch-punch.js,mouse初始化代码被jquery-ui-touch修改了,就是绑定touch事件,touch事件会转换为mouse事件。

    因此执行$().sortable()之后,创建了sortable实例,进行了初始化,绑定了touch事件,当touch操作时,执行mouse事件绑定的handler实现拖拽功能。

    下面来看sortable插件的重点函数代码:

    var sortable = $.widget("ui.sortable", $.ui.mouse, { // sortable的内置方法都加了一层封装,用value.apply方式调用
      _mouseDrag: function(event) { //拖拽时会连续执行多次,拖拽时处理网页样式变化,把placeholder插入到目标元素之前,sortable不修改items[]原数据  

        //Compute the helpers position
        //当拖动一个元素时,其class增加helper成为helper,成为浮动元素,元素跟着拖动浮动,离开了列表,helper的意思就是预览让人能看见元素被拖动走了。

        //placeholder是新建一个元素代替它占据在列表中的位置,就是被拖动元素在列表中的替身,当拖动元素时,placeholder元素就插入相应的位置,这样就能预览插入的效果

        

        this.position = this._generatePosition(event); // 计算helper的位置,来自touch event x/y位置数据,拖动时位置不断变化,helper跟着拖动走
          _generatePosition: function(event) {
            event中有touch位置x/y,计算时涉及到父容器scroll,要把父容器scroll去掉简化处理,调插件传参scroll:false忽略scroll处理。
            return {
              top: (
                pageY - // The absolute mouse position
                this.offset.click.top - // Click offset (relative to the element)
                this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
                this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
                ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
              ),
              left: (
                pageX - // The absolute mouse position
                this.offset.click.left - // Click offset (relative to the element)
                this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
                this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
                ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
              )
            };
          }

        

        //Rearrange,产生placeholder元素插入到touch位置
        for (i = this.items.length - 1; i >= 0; i--) { //

          intersection = this._intersectsWithPointer(item); // intersect是touch与元素重叠的意思

              

            _intersectsWithPointer: function(item) {
              var isOverElementHeight = (this.options.axis === "x") || this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
                isOverElementWidth = (this.options.axis === "y") || this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),

                _isOverAxis: function( x, reference, size ) { // x-touch位置 reference-元素位置
                  return ( x >= reference ) && ( x < ( reference + size ) ); // 判断touch位置是否在元素中,在水平方向和垂直方向分别判断
                }

                isOverElement = isOverElementHeight && isOverElementWidth,
                  verticalDirection = this._getDragVerticalDirection(),
                  horizontalDirection = this._getDragHorizontalDirection();

                return this.floating ?
                  ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
                    : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );

         

          if (!intersection) {
            continue;
          }
          //如果item与touch重叠,要修改列表,把placeholder插入到item元素之前
          this._rearrange(event, item); 

              _rearrange: function(event, i, a, hardRefresh) {  // i是touch目标item元素            

                  i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));
                  //向上向左拖时direction=down,向下向右拖时direction=up,placeholder插入位置有可能是item之前,也有可能是item下一个元素之前

          this._trigger("change", event, this._uiHash());

        }

        

        //Call callbacks
        this._trigger("sort", event, this._uiHash()); //如果有绑定"sort"逻辑事件,则执行handler,实际上没有

      }

      

      _mouseStop: function(event, noPropagation) { // 单步看开始执行之前start/end元素已经交换了位置,是由drag过程处理的,stop只是善后处理
        $.ui.ddmanager.drop(this, event); // debug看没有处理内容
        that._clear(event);

          _clear: function(event, noPropagation) {

            this.placeholder.before(this.currentItem); // sortbale实例里面的placeholder对象的before方法

                before: function() {
                  return domManip( this, arguments, function( elem ) {
                      if ( this.parentNode ) {
                        this.parentNode.insertBefore( elem, this ); // elm是helper(drag)元素,this是placeholder,把拖动的元素插入到placeholder位置,这也是整个sortable操作的最终目的,helper元素已经在网页中,insert只是移动位置,插入之后还要修改css样式(修改class即可)。
                      }
                  } );
                }

                    

                    function domManip( collection, args, callback, ignored ) { // dommanip非常复杂
                      callback.call( collection[ i ], node, i );
                      return collection;
                    }

              

            this._trigger("update", event, this._uiHash());
            this._trigger("beforeStop", event, this._uiHash());
            this.placeholder[0].parentNode.removeChild(this.placeholder[0]); // 删除placeholder
            this._trigger("stop", event, this._uiHash());

    sortable处理网页在拖动时插入placeholder最后再把拖动的元素插入到placeholder位置。

    sortable是根据鼠标的位置数据进行计算判断目标位置元素,所以也适用于touch触发,因为touch事件数据中含位置数据,只要把touch事件转换成mouse事件即可,
    如果sortable是按drag target元素设计的,就没法适用于touch,因为touchend没有target元素,没法获取到target元素。

    sortable插件代码非常复杂,很难控制,本人参考它的处理思路和算法,自己写了一个简化的sortable插件来实现touch触发排序,测试效果没问题:

    因为用前端框架比如vue开发,touch拖动时无需处理拖拽效果(实际上鼠标操作时h5会自动产生拖拽效果),只需要在touchend时计算出target元素,有了start元素和end元素,只需要把原数组变换一下,框架会自动更新页面,无需写代码处理页面更新,而jquery-ui-sortable的功能就是在拖拽时更新页面效果,我不需要它这个功能,只需要参考它计算mouseup/touchend元素的算法,它的算法很复杂,简化一下测试结果也是可以的。

    _touchEnd(e){
      //console.log(e);
      e.preventDefault();
      e.stopPropagation();
      var _this = this;
      var intersection = false;
      $("#tf_box > li").each(function(k,v){
        //console.log(v.offsetLeft);
        intersection = _this._intersectsWithTouch(e,v);
        if (intersection) {
          //item与touchend位置重叠,则item即为touchend/drag目标元素,修改原数组交换start/end元素
          _this.endIndex = k;
          _this.updateData(); // 修改原数组,页面会自动更新显示拖拽交换后的效果
        }
      });
    },
    _intersectsWithTouch(event,item) { // 计算touchend元素位置
      var touchY = event.originalEvent.changedTouches[0].clientY; // 注意不要用screenX/Y,那是在屏幕中的位置,要用在浏览器窗口中的位置
      var touchX = event.originalEvent.changedTouches[0].clientX;
      //console.log(touchX+":"+item.offsetLeft);
      var isOverElementHeight = touchY >= item.offsetTop && ( touchY < ( item.offsetTop + item.offsetHeight )),
        isOverElementWidth = touchX >= item.offsetLeft && ( touchX < ( item.offsetLeft + item.offsetWidth )),
        isOverElement = isOverElementHeight && isOverElementWidth;
        //console.log(isOverElementHeight+":"+isOverElementWidth);

        if (!isOverElement) {
          return false;
        }

        return true;

    },

    本来无意研究jquery-ui源代码,因为发现有些sortble程序在手机运行无效果,有些在手机运行有效果,而jquery-ui-sortable在手机运行也是有效的,有点好奇,才研究了jquery-ui-sortable源代码,搞清楚了其中的是非,涉及到mouse/touch/drag事件原理和算法,还是非常复杂的,拖拽比较复杂,比拖拽更复杂的就是动画了。

    文中错误之处欢迎大家指正交流。

  • 相关阅读:
    LVS DR模式的简单应用
    Linux新加硬盘如何识别 分区 持续挂载
    13周作业
    12周作业
    2次月考
    10周作业
    9周作业
    8周作业
    7周作业
    6周作业
  • 原文地址:https://www.cnblogs.com/pzhu1/p/9212753.html
Copyright © 2011-2022 走看看