本文以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事件原理和算法,还是非常复杂的,拖拽比较复杂,比拖拽更复杂的就是动画了。

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

转载于:https://www.cnblogs.com/pzhu1/p/9212753.html

jQuery-ui源代码重点难点分析相关推荐

  1. react源代码重点难点分析

    网上已经有不少react源码分析文档,但都是分析主流程和主要功能函数,没有一个是从reactDOM.render()入口开始分析源码把流程逻辑走通尤其是把重点难点走通直到把组件template编译插入 ...

  2. vue 1.0源代码重点难点分析

    本文分析vue1.0源代码从入口开始到编译输出到网页显示生效的整个过程,并重点分析一些vue源代码设计原理. vue初始化根组件的入口代码: 对于没有路由的单页应用来说,入口就是new Vue(opt ...

  3. 地下管线探测重点与难点分析

    1 不同管线探测 (1)非金属管线(砼.UPVC 等).管线探测过程中,往往会遇到非金属(UPVC.砼等)管线及相邻较近且走向一致的地下管线埋设方式,由于目前的地下管线探测仪是利用金属管线对电磁波产生 ...

  4. 计算机应用教学对象分析,编辑演示文稿计算机应用基础教材分析学情分析教学目标重点难点 .ppt...

    编辑演示文稿计算机应用基础教材分析学情分析教学目标重点难点 魅力余姚期待您的光临 --编辑演示文稿 计算机应用基础 教材分析 学情分析 教学目标 重点难点 教学方法 教学设计 教学反思 教学准备 本节 ...

  5. Drupal第三方库jQuery UI起死回生,多个漏洞影响网站、企业产品等

     聚焦源代码安全,网罗国内外最新资讯! 编译:代码卫士 专栏·供应链安全 数字化时代,软件无处不在.软件如同社会中的"虚拟人",已经成为支撑社会正常运转的最基本元素之一,软件的安全 ...

  6. css拖拽调整高度,两种为wangEditor添加拖拽调整高度的方式:CSS3和jQuery UI

    wangEditor是一款优秀的Web富文本编辑器,但如果能像KindEditor那样支持拖拽调整高度就更好了.有两种方式可以为wangEditor添加这一功能,这里使用的wangEditor版本为2 ...

  7. jQuery UI在Server 2008 IE8下DatePicker问题修复

    这真是个WTF的问题,类似参见Stack Overflow 这个DatePicker问题只在Server 2008的IE8下出现.至于为什么win7的IE8支持,Server2008的IE8不支持,就 ...

  8. 解决Select2控件不能在jQuery UI Dialog中不能搜索的bug

    本文使用博客园Markdown编辑器进行编辑 1.问题呈现 项目中使用了jQuery UI的Dialog控件,一般用来处理需要提示用户输入或操作的简单页面.逻辑是修改一个广告的图片和标题. 效果截图如 ...

  9. Wijmo 更优美的jQuery UI部件集:复合图表(CompositeChart)

    Wijmo的CompositeChart控件允许您使用一个Chart来分析和展现复杂的数据.相同的数据可以使用不同的可视化效果,不同的图表类型展现在一个图表内,使得用户可以从不同的角度,了解分析这组数 ...

最新文章

  1. 富友电子商务系统的四大优势助网商轻松赚钱
  2. CSS公共清除浏览器默认样式
  3. JS日期函数getMonth()的值域是0--11
  4. 第六届省赛(软件类)真题----Java大学A组答案及解析
  5. jQuery-动画与特效
  6. linux如何运行sh监控文件夹,如何使用Shell进行文件监控?
  7. 收购YY直播,百度重返高位的关键布局
  8. 单元格赋值与联动 例:C1值赋予D1 ,并将D1的值传给图表元素联动
  9. 【雷达通信】基于matlab GUI雷达定位【含Matlab源码 302期】
  10. 欧姆龙plc多轴伺服控制程序fb
  11. 求各位大神帮忙看一下我用51做的万年历程序有没有问题
  12. css border实现渐变
  13. python中matplotlib的plot函数
  14. 技术决胜年----谈谈我2018年的新观念新思想
  15. 太阳直射点纬度计算公式_高中地理——每日讲1题(太阳直射点、太阳高度角、太阳视运动)...
  16. Jupyter notebook中自定义支持天软TSl语言的魔术命令
  17. BCDEDIT - 启动配置数据存储编辑器
  18. 思科认证入门级课程介绍(二)
  19. 爱剪辑为啥显示服务器繁忙,爱剪辑蓝屏怎么办?爱剪辑蓝屏的六大原因及解决方法...
  20. 天猫店群比淘宝店群好做吗?同是无货源差距为何那么大,个人分享

热门文章

  1. 在sqlserver中写脚本用到的关键字理解
  2. 使用jQuery获取表格内容、:nth-child() 选择器用法
  3. 灰度实战(四):Apollo配置中心(4)
  4. 工业互联网推动制造业高质量发展研讨会在京召开
  5. 李飞飞新动向:创建斯坦福“以人为本AI研究院”,担任共同院长
  6. 这位勇士,你别去读博了:搞机器学习要PhD何用?
  7. CNN手把手维修攻略:你的网络不好好训练,需要全面体检
  8. 阿里AI实验室负责人浅雪:从不淘宝购物的马云是天猫精灵用户
  9. 实习小白::(转) cocos2d-x使用cocosStudio编辑的动画文件
  10. erlang的简单模拟半包的产生