jQueryUI 可排序和拖拽的速度问题

7
我有一个使用jQueryUI sortable的列表。 我需要根据插件的“change”事件来跟踪每个元素被拖动时的每个步骤。
但是,只有在缓慢到正常拖动时才能可靠地工作。 在这些速度下,完全没有错误。 但是,如果用户相对较快地拖动元素(列表越大,它就越容易发生,因为鼠标有更多空间可以加速),则“change”事件会丢失一些步骤,从而丢失一些有用信息,而不会在控制台中抛出任何错误。 展示问题:

https://jsfiddle.net/yhp3m6L8/2/

在这个jsFiddle中,你可以拖动一个元素并查看其索引更改作为一个示例。在列表下方,在拖动时,您将有一个控制台仿真来跟踪黑色的索引。
如果拖动得足够快,您会看到其中一些索引变成红色。这就是问题所在。这意味着在排序脚本的更改事件期间它们的先前位置被忽略了,原因我不明白。连续性被打破,而这种连续性对我的需求至关重要。 速度是唯一打破连续性的因素。但是,一个无法跟上相当快的鼠标拖动的“change”事件似乎相当奇怪。

enter image description here

脚本:

其中一半是为了跟踪索引,因为Sortable有一种奇特的引用索引的方式,这取决于方向(向上/向下),但也取决于元素相对于其初始位置(之前/之后)的当前位置。因此,需要最少量的代码来直观地理解这些索引。使用该脚本,元素会获得一个直观的视觉索引,按顺序排列。

然而,这个脚本可能只是问题的展示(请参见下面的附注)。

脚本的另一半只是为了模拟一种调试目的的控制台。

我的猜测:

我可能错了,但最终我认为这是“change”事件的跟踪问题或者被拖动的元素跟不上鼠标光标(感觉在相对较高的速度下它并不总是在光标下面)。除非有Sortable选项可供使用,我不知道...

我认为这是其中一种情况,因为无论我尝试在“change”事件中使用什么代码,我总是遇到这个间隙问题。

HTML:

<html>
    <body>
        <div class="panel">
            <ul class="panel_list">
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="ticked"></li>
                <li class="unticked"></li>
                <li class="unticked"></li>
                <li class="unticked"></li>
            </ul>
        </div>
        <div class='console'></div>
    </body>
</html>

CSS:

.console{
    width:100%;
}

.panel ul{
    list-style-type:none;
    width:30%;
}

.panel li{
    height:29px;
    border-bottom:1px solid #454d5a;
    text-align: left;
    vertical-align: middle;
    line-height:29px;
    padding-left:8px;
    background: #666;
}

.panel_highlight{
    height:29px;
    background:#1c2128 !important;
}

.unticked{
    color:#7c7a7d;  
    background:#222 !important;
}

jQuery:

// jQuery: 3.3.1

// jQueryUI: 1.11.4 (downgraded from 1.12.1, because of documented 
// performance issues but...with no effect)

 

var origValue;
var oldInfo;
var c=0;
var x=0;

// LIST RELATED

//Just to have a visual on the indexes in the list. 
$('li').each(function(){
    $(this).data('idx',c++);
    $(this).text(c);
})
c=0;

$( ".panel_list" ).sortable({
    placeholder: 'panel_highlight',
    items      : '>li.ticked',
    cancel     : '>li.unticked',
    tolerance  : 'pointer',
    axis       : 'y',
    opacity    :  0.9,
    start: function(event, ui){

    // LIST RELATED

        origValue = ui.item.data('idx');

    // CONSOLE RELATED (initialise variables)

        $('.console').html('');oldInfo='';
    },
    change: function(event, ui){

    // LIST RELATED

        var idx = ui.placeholder.prevAll().filter(':not(.ui-sortable-helper)').length;
        var a=ui.placeholder.index();
        var b=a+1;

        //detect the direction of the dragging
        if(idx - $(this).data('idx')==1)
        {//downward dragging

            //if the element is below its initial position (or x=1)
            if((a<=origValue) || (x==1)){ui.item.text(b);x=0;}
            //if the element is still above its initial position
            else                        {ui.item.text(a);};
        }
        else
        {//upward dragging

            //if the element is still below its initial position
            if(a<=origValue)            {ui.item.text(b);x=1;}
            //if the element is above its initial position
            else                        {ui.item.text(a);};
        };
        $(this).data('idx', idx);

       // Update the visual on the indexes in the list. 
        $('li').each(function(){
            if(ui.item.index() !=$(this).index()){           
                $(this).data('idx',c++);
                $(this).text(c);
            }
        })
        c=0;

    // CONSOLE RELATED (show indexes progression and gaps)

        var info=$('.console').html();
        if(oldInfo !=''){
            var valAbs= Math.abs( parseInt(ui.item.text()) - parseInt(oldInfo));
            if(valAbs==1){info=info+' + '+ui.item.text();}
            else{info=info+' + <span style="color:red">'+ui.item.text()+'</span>';};
        }
        else{info=ui.item.text();};
        $('.console').html(info);
        oldInfo = ui.item.text();
    }
});

补充说明:

“change”事件中的所有代码都是为了让您看到问题,而不是问题本身。我让您自己判断,但公平地提醒一下。

我的实际脚本在“change”部分是不同的。它会触发一些表格列的重新排序,这就是我发现问题的方式,因为在高速情况下表格重新排序会出现故障。因此,这里的虚拟脚本只是我将其缩小到最小限度,同时以视觉方式向您展示问题。

重点是评估这是否是可以解决的性能问题,我应该添加一个Sortable选项,可以修复拖动/光标延迟,还是有什么诀窍可以绕过这个跟踪问题。

我认为这是一个公平的警告,以防止您费力调试一个仅仅是展示用的虚拟脚本。但考虑到我只是一个新手,可能是错误的,请按照您的意愿进行操作。

在所有情况下,非常感谢您的帮助或意见。

编辑:这是我真正的(缩小范围后的)脚本,它会触发“dataTable.colReorder.move”事件。它更为复杂,因为它需要知道着陆索引(a/b),同时也需要知道被拖动元素的当前索引(a/b/c/d)。而sortable有自己情境化的方式来索引这些内容。

$( ".panel_list" ).sortable({
    placeholder: 'panel_highlight',
    items      : '>li.ticked',
    cancel     : '>li.unticked',
    start: function(event, ui){
        lastValue='';
        origValue=ui.item.index();
        $(this).data('idx', ui.item.index());
    },
    change: function(event, ui){
        var x;
        var idx = ui.placeholder.prevAll().filter(':not(.ui-sortable-helper)').length;
        var a= ui.placeholder.index();
        var b=a+1;var c=a+2;var d=a-1;

        if(idx - $(this).data('idx')==1)
        {//downward
            if((origValue>=a) || (x==1)){dataTable.colReorder.move(a,b);lastValue=b;x=0}
            else                        {dataTable.colReorder.move(d,a);lastValue=a;}
        }
        else
        {//upward
            if(origValue>=a)            {dataTable.colReorder.move(c,b);lastValue=b;x=1}
            else                        {dataTable.colReorder.move(b,a);lastValue=a;}
        }   

        $(this).data('idx', idx);       
    }
});

这个快速变化是由人工输入还是通过读取API结果完成的?这看起来很像足球联赛结果表。 - Vickel
好老的人工拖拽.. - Bachir Messaouri
为什么有人想要如此快速地拖动?您真的需要记录所有上下移动,直到到达“停车”吗?还是单向路径(向上或向下)就足够了? - Vickel
实际上它并不快。它是相对快速的。 试着用四个项目列表,你不可能那么快。 试着用一个30项的列表,从一端到另一端,然后你就有了。 两个坐标之间的距离越大,你就会越快。这很正常。 当像任何人一样使用系统时,每四次中就有一次我会出现问题。所以,这根本不是一个假设性的问题。而且那种速度真的不超出范围。在问题中已经说过,“列表越大,这种情况发生的频率就越高,因为鼠标有更多的空间来获得速度”。 - Bachir Messaouri
我昨天已经看过这个了,但像Louys一样,我也找不到任何解决方案。因此,在之前的评论中问了我的问题:你真的需要记录所有的上下移动吗?因为如果不是,我们可以计算出所有的移动,直到达到“停车”=你停下来并放下的地方。 - Vickel
我不记录任何东西。该列表允许实时拖动与列表元素相关的表格列。因此,如果我想知道移动了什么以及移动方向,需要实时了解发生了什么。就像我下面所说的那样,如果跳过了某个位置,除非等待下一个拖动位置的值,否则不可能知道它是向下还是向上跳过。但是,您已经失去同步。这足以使脚本在过程中拖动错误的表格列并使表格混乱。如果我记录下这些值,那将很容易。但是这是实时的。 - Bachir Messaouri
2个回答

4

不幸的是...没有解决这个问题的方法。

我最初尝试使用sort event callback,但在非常快速的拖动时仍然会出现“跳过”的问题。

我想知道为什么要花费几分钟时间... 然后我思考了一下 .sortable() 可能依赖于什么... 我得出了唯一可能的“浏览器”事件:mousemove

虽然 mousemove 很频繁地触发... 就像一把“机枪”... 但它不够快以满足您的期望。

为了验证这一点,我只需在未更改的 Fiddle 的末尾添加此 mousemove 处理程序:

$( ".panel_list" ).on("mousemove",function(e){
  console.log($(e.target).index());
});

它会记录与您的 div.console 中完全相同的数字。

enter image description here

所以这个问题肯定不是由于 .sortable() 引起的。


编辑
我上面说的都还是真的... 但既然你已经有了“跳过检测”,为什么不用它来填补空缺呢?

您有oldInfo,它是此新change之前的最后一个.index()
而你有新的 .index()...

根据移动是向上还是向下,您需要一个或另一个for循环来填补编号中的间隙。 对于数字,这也是触发点...

;)

以下是更改后的部分:

// CONSOLE RELATED

info=$('.console').html();
if(oldInfo !=''){

  var valReal = parseInt(ui.item.text()) - parseInt(oldInfo);
  var valOld = parseInt(oldInfo);

  var valAbs= Math.abs( parseInt(ui.item.text()) - parseInt(oldInfo));
  if(valAbs==1){info=info+' + '+ui.item.text();}
  else{

    // ADDING THE SKIPPED ONES!
    // Upward (in blue)
    if(valReal>0){
      for(i=valOld+1;i<valOld+valReal;i++){
        info=info+' + <span style="color:blue">'+( i )+'</span>';
      }

    // Downward (in green)
    }else{
      for(i=valOld-1;i>valOld+valReal;i--){
        info=info+' + <span style="color:green">'+( i )+'</span>';
      }
    }

    // The one caugth after a gap (in red)
    info=info+' + <span style="color:red">'+ui.item.text()+'</span>';
  };
}


这值得一提...为了后人留存。(以全屏模式运行。)

var origValue;
var oldInfo;
var info;
var c=0;
var x=0;

// LIST RELATED

//With or without this, the problem will occur.
$('li').each(function(){
  $(this).data('idx',c++);
  $(this).text(c);
})
c=0;

$( ".panel_list" ).sortable({
  placeholder: 'panel_highlight',
  tolerance  : 'pointer',
  axis       : 'y',
  opacity    : 0.9,
  items      : '>li.ticked',
  cancel     : '>li.unticked',
  start: function(event, ui){

    // LIST RELATED

    origValue = ui.item.data('idx');

    // CONSOLE RELATED

    $('.console').html(''); oldInfo=''; info='';
  },
  change: function(event, ui){

    // LIST RELATED

    var idx = ui.placeholder.prevAll().filter(':not(.ui-sortable-helper)').length;
    var a=ui.placeholder.index();
    var b=a+1;
    if(idx - $(this).data('idx')==1)
    {//downward dragging
      if((a<=origValue) || (x==1)){ui.item.text(b);x=0;}
      else            {ui.item.text(a);};
    }
    else
    {//upward dragging
      if(a<=origValue)      {ui.item.text(b);x=1;}
      else             {ui.item.text(a);};
    };
    $(this).data('idx', idx);

    //With or without this, the problem will occur.
    $('li').each(function(){
      if(ui.item.index() !=$(this).index()){
        $(this).data('idx',c++);
        $(this).text(c);
      }
    })
    c=0;

    // CONSOLE RELATED

    info=$('.console').html();
    if(oldInfo !=''){

      var valReal = parseInt(ui.item.text()) - parseInt(oldInfo);
      var valOld = parseInt(oldInfo);

      var valAbs= Math.abs( parseInt(ui.item.text()) - parseInt(oldInfo));
      if(valAbs==1){info=info+' + '+ui.item.text();}
      else{

    // ADDING THE SKIPPED ONES!
        if(valReal>0){
          for(i=valOld+1;i<valOld+valReal;i++){
            info=info+' + <span style="color:blue">'+( i )+'</span>';
          }
        }else{
          for(i=valOld-1;i>valOld+valReal;i--){
            info=info+' + <span style="color:green">'+( i )+'</span>';
          }
        }
        
        // The one caugth after a gap
        info=info+' + <span style="color:red">'+ui.item.text()+'</span>';
      };
    }
    else{info=ui.item.text();};
    $('.console').html(info);
    oldInfo = ui.item.text();
  }
});
.console{
  width:100%;
}

.panel ul{
 list-style-type:none;
  width:30%;
}

.panel li{
 height:29px;
 border-bottom:1px solid #454d5a;
  text-align: left;
  vertical-align: middle;
 line-height:29px;
 padding-left:8px;
 background: #666;
}


.panel_highlight{
    height:29px;
    background:#1c2128 !important;
}

.unticked{
 color:#7c7a7d; 
 background:#222 !important;
}


.ui-sortable-helper{
  background-color:red !important;
}
<link href="https://www.lexgotham.com/test5/styles/reset.css" rel="stylesheet"/>
<link href="https://www.lexgotham.com/test5/scripts/jQuery/jquery-ui-1.12.1/jquery-ui.css" rel="stylesheet"/>
<script src="https://www.lexgotham.com/test5/scripts/jQuery/jquery.3.3.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script>
<html>
<body>
  <div class="panel">
    <ul class="panel_list">
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="ticked"></li>
      <li class="unticked"></li>
      <li class="unticked"></li>
      <li class="unticked"></li>
    </ul>
  </div>
  <div class='console'></div>
</body>
</html>

示例代码


我制作了一个演示,其中数据表格与可排序列表相结合。我遇到了很多错误!但是我已经让它工作了这里。你欠我几瓶啤酒!;) - Louys Patrice Bessette

4

前面的回答中有一些观点是正确的,但这是我的看法。顺便说一句,我相信这是“可修复的”。

在我看来,有一个非常简单的解决方案。每次出现“跳过”时,可排序列表将不会告诉您所有的中间步骤,那么,为什么不自己构建它们呢?

var origValue;
var oldInfo;
var info;
var c=0;
var x=0;

// LIST RELATED

//With or without this, the problem will occur.
$('li').each(function(){
    $(this).data('idx',c++);
    $(this).text(c);
})
c=0;

$( ".panel_list" ).sortable({
    placeholder: 'panel_highlight',
    tolerance  : 'pointer',
    axis       : 'y',
    opacity    : 0.9,
    items      : '>li.ticked',
    cancel     : '>li.unticked',
    start: function(event, ui){

    // LIST RELATED

                origValue = ui.item.data('idx');

        // CONSOLE RELATED

            $('.console').html(''); oldInfo=''; info='';
    },
    change: function(event, ui){

    // LIST RELATED

            var idx = ui.placeholder.prevAll().filter(':not(.ui-sortable-helper)').length;
            var a=ui.placeholder.index();
            var b=a+1;
            if(idx - $(this).data('idx')==1)
            {//downward dragging
            if((a<=origValue) || (x==1)){ui.item.text(b);x=0;}
                    else                                     {ui.item.text(a);};
            }
            else
            {//upward dragging
            if(a<=origValue)             {ui.item.text(b);x=1;}
            else                                     {ui.item.text(a);};
            };
            $(this).data('idx', idx);

            //With or without this, the problem will occur.
            $('li').each(function(){
          if(ui.item.index() !=$(this).index()){
               $(this).data('idx',c++);
               $(this).text(c);
              }
        })
            c=0;

        // CONSOLE RELATED

            info=$('.console').html();
            if(oldInfo !=''){
          oIv = parseInt(oldInfo);
          uiV = parseInt(ui.item.text());
          stepIn = (uiV > oIv ? 1 : -1);
          switch (stepIn) {
          case 1:
          for (i=oIv+1;i<uiV;i++) {info=info+' + <span style="color:green">'+i+'</span>';}
          break;
          case -1:
          for (i=uiV+1;i<=oIv;i++) {info=info+' + <span style="color:red">'+i+'</span>';}
          break;
          }
            }
            else{info=ui.item.text();};
            $('.console').html(info);
            oldInfo = ui.item.text();
    }
});

如您所见,如果跳过发生了,我将只是将所有被change事件跳过的步骤添加到info变量中... 然而,遗憾的是,这仍然不起作用... 原因很简单... change事件的重新进入。

现在,我没有时间编写一个可行的解决方案,但这是我的想法。 每当change事件结束时,您必须使用缺失的中间值重建列表。 这样,您就可以拥有连续的序列。 因此,从您的原始代码开始,只需向stop事件添加一个方法,该方法基于change创建的序列填充中间值,voila,您就有了您的序列。

请让我知道这是否满足您的要求。


感谢您的输入。如果我错了,请纠正我,但我认为这样做没有帮助。请记住,我需要实时更改。因此,在跳过元素时,系统如何知道它是向下还是向后跳过的?它必须知道列表的下一个元素才能评估。如果是这样,那么不再是实时的了,因为您需要等待下一个更改事件才能知道如何处理上一个事件。如果我的评论没有回答您的建议,请详细说明(并原谅我)。 - Bachir Messaouri
1
没什么好道歉的。解决方案很简单...你可以将其放在更改事件中以保持实时性...基本上,当你进行交互时,你会有一个值数组,带有或不带有“跳过”...现在...根据该值,你总是可以生成一个没有跳过的值列表...例如:假设你有1、2、3、7、3、4、5、6、2...,所以基本上你可以遍历数组并根据差异填充间隙,并将更正后的值存储在新数组中...因此,当你到达第一个3时,你可以看到下一个值是7,因此你可以在结果中添加缺失的值4、5、6,--> - Merak Marey
我会考虑一下并看看我能想出什么。如果我有想法,我会告诉你的。 - Bachir Messaouri
1
然后,接下来的7..你会找到另外一个3,所以现在缺失的值将是6、5、4..明白了吗?...填补完这些空缺之后,只需显示已更正的数组即可完成。同样,您可以在更改事件中执行此操作,以实现“实时”效果。 - Merak Marey
不确定我能否完成这个任务,但我会根据您的输入尽力而为。非常感谢您的关心。您的答案是最接近解决方案的,而LP Bessette的答案则对问题最有启发性。我希望在您的帮助下能够想出一些东西。如果成功了,我会再联系您的。 - Bachir Messaouri
当然,如果你有一个JSFiddle链接,我非常支持它;-) - Bachir Messaouri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接