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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
jQuery UI标准后台页面演示系统说明 系统的设计目的 我做B/S软件开发,基本上还是采用原型法的--嗯,好吧,可能原型法还是往自己脸上贴金了。俗一点说吧,就是和客户定一个尽可能详细的功能描述文档,然后按文档写完程序,再回交给客户演示,客户观看演示后会提出很多意见,再按这些意见修改,然后再演示,如此循环往复,直至交稿。与最初的演示稿相比,最后的完成稿出入很大,大量的工作,其实都是浪费的。我就想,能不能以后先做一个快速生成的演示系统,用户确认后再真正制作程序呢?于是就有了这个演示系统的产生。 作为系统的第一个版本,这个系统功能比较简单。我之前的设想,是用zend framework框架设计一个接近CMS功能的后台,统一用户管理、文章发布等常用功能,但这么一来,系统的适用范围就小了。所以,先做一套极简易的小架构,即使没有学过PHP的.net/java程序员,应该也能很容易看懂并上手使用,甚至把它改成其它语言的版本。 目前的这个版本,最值得推荐的功能在于,jQuery代码是一次载入的,使用这套系统进行开发,程序员可以完全不懂jQuery,而仍能享用jQuery的种种便利。 0.1版系统特点介绍 • 集成了jQuery UI的风琴盒、页标签特效、ajaxForm表单递交和validate表单验证功能,无需程序员学习这些功能,按常规方式编程即可自动调用。 • 保持了与jqueryui的完全兼容,可以自行前往www.jqueryui.com网站下载更多酷炫界面风格,甚至自定义风格 • 涉及的PHP代码极少且足够建议,非PHP程序员也能使用 • 对系统要求非常低,只要普通PHP程序能够运行即可 系统实际使用步骤 1. 复制(解压)文件夹到网站测试文档根目录(apache系统下一般是htdocs),即可直接访问 2. 修改config.php文件中的数组,即可快速生成界面结构 3. config.php中的$tabs数组就是页面标签内容,其中的url可自行指定或新建文件,文件格式可以参照admin.php、helper.php等文件 4. 如果$tab中url指向的文件中需要包含表单,按常规写法即可,无需特殊代码即可自动以ajax方式递交 5. 如果表单需要ajax方式验证,可以给相应的表单项添加class,可参见systemsetting.php和address.php文件,也可自行网上搜索和阅读jquery.validate.js的帮助文档 6. 演示中的项目,只有核心系统下的后台首页和系统设置有效,其它都只是点缀
jquery-ui-timepicker-addon.css是基于jQuery UI的日期时间选择器插件datetimepicker的样式表文件,用于定义和控制datetimepicker组件的外观和样式。 datetimepicker插件是一个基于jQuery UI的日期时间选择器插件,它可以在输入框中集成日期和时间选择器,具有用户友好的界面和强大的可定制性。和其他jQuery UI组件一样,datetimepicker插件也需要相应的CSS文件来定义和控制其外观和样式。 jquery-ui-timepicker-addon.css文件是datetimepicker插件的样式表文件,它包含了datetimepicker的默认样式和主题,也可以用于自定义修改样式表文件以达到特定的视觉效果。 使用jquery-ui-timepicker-addon.css文件的方法和使用jquery-ui.css文件的方法类似,具体使用方法如下: 1. 下载datetimepicker插件和jQuery UI框架,从官网(https://jqueryui.com/)或其他来源下载datetimepicker插件和jQuery UI的压缩文件。 2. 引入jquery-ui-timepicker-addon.css文件,将jquery-ui-timepicker-addon.css文件复制到项目中,并在HTML页面中使用<link>标签引入该文件。 ``` <link rel="stylesheet" href="path/to/jquery-ui-timepicker-addon.css"> ``` 3. 引入jQueryjQuery UI的JS文件,使用<script>标签引入jQueryjQuery UI的JS文件。 ``` <script src="path/to/jquery.js"></script> <script src="path/to/jquery-ui.js"></script> ``` 4. 引入datetimepicker插件的JS文件,使用<script>标签引入datetimepicker插件的JS文件。 ``` <script src="path/to/jquery-ui-timepicker-addon.js"></script> ``` 引入这些文件后,就可以在输入框中集成日期时间选择器,并使用jquery-ui-timepicker-addon.css文件来控制其外观和样式了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值