JavaScript与jQuery:单击和双击同一元素,有不同效果,一个禁用另一个。

44
我有一个有趣的情况 - 我有一行表格,目前当我点击“展开”按钮时,它会显示它隐藏的对应项。原始(未隐藏)的行包含展开按钮的同时也在某个单元格中包含一些内容,当单击该单元格时,该内容变为可编辑状态。我想摆脱展开按钮,并通过双击行本身(包括点击该单元格后变成可编辑状态的字段)来扩展行。你已经可以闻到麻烦了。

当我双击行时,首先会触发两个单击事件,然后才是双击事件。这意味着如果我双击该字段,它将变成可编辑状态,而行也将展开。我想防止这种情况发生。我希望双击可以防止单击事件的触发,而单击则像往常一样执行。

使用event.stopPropagation()显然行不通,因为它们是两个不同的事件。

有什么想法吗?

编辑(一些半伪代码):

原始版本:

<table>
    <tbody>
        <tr>
            <td><a href="javascript:$('#row_to_expand').toggle();" title="Expand the hidden row">Expand Row</a></td>
            <td>Some kind of random data</td>
            <td><?= $editable_cell_which_turns_into_an_input_field_on_single_click[0]->value("First editable value") ?></td>
            <td><?= $editable_cell_which_turns_into_an_input_field_on_single_click[1]->value("Second editable value") ?></td>
            <td><?= $editable_cell_which_turns_into_an_input_field_on_single_click[2]->value("Third editable value") ?></td>
            <!-- ... -->
            <td><?= $editable_cell_which_turns_into_an_input_field_on_single_click[n]->value("Nth editable value") ?></td>
        </tr>
        <tr style="display: none" id="row_to_expand">
            <td colspan="n">Some hidden data</td>
        </tr>
    </tbody>
</table>

所期望的版本:

<table>
    <tbody>
        <tr ondblclick="$('#row_to_expand').toggle()">
            <td>Some kind of random data</td>
            <td><?= $editable_cell_which_turns_into_an_input_field_on_single_click[0]->value("First editable value") ?></td>
            <td><?= $editable_cell_which_turns_into_an_input_field_on_single_click[1]->value("Second editable value") ?></td>
            <td><?= $editable_cell_which_turns_into_an_input_field_on_single_click[2]->value("Third editable value") ?></td>
            <!-- ... -->
            <td><?= $editable_cell_which_turns_into_an_input_field_on_single_click[n]->value("Nth editable value") ?></td>
        </tr>
        <tr style="display: none" id="row_to_expand">
            <td colspan="n">Some hidden data</td>
        </tr>
    </tbody>
</table>

干杯


3
你可以尝试使用setTimeout延迟click-handler,然后在双击时提高“doubleclick-has-happened”标志,并在单击处理程序中检查该标志。 - Thor Jacobsen
@diEcho 完成了!@freaktm 谢谢,我考虑过这个方案,但似乎不可行和不专业,尤其是因为我需要在整个网站上修改可编辑单元格助手的点击处理程序。我可能会将其作为最后一种选择使用,但仍然希望知道是否有更好的解决方案。 - Swader
如果你到那里了,我已经为你做了一个例子:http://jsfiddle.net/nslr/2TvgE/ 但是,确实不太好看 :) - Thor Jacobsen
嗯,尝试了一下,似乎没有按计划工作。当我双击时,我得到了双击事件,而当我单击时,我得到了单击事件。但是当我在双击后再次双击时,我同时得到了单击和双击事件 :) - Swader
可能是Jquery分别绑定双击和单击的重复问题。 - Popnoodles
10个回答

24

一般的想法:

  1. 在第一次单击时,不要调用相关的函数(比如single_click_function())。相反,在一定时间段内设定一个计时器(比如x毫秒)。如果在这段时间内没有再次单击,就执行single_click_function()。如果有第二次单击,则调用double_click_function()

  2. 计时器会在接收到第二次单击时被清除。当经过x毫秒后,计时器也会被清除。

另外,可以查看Paolo的回复:Need to cancel click/mouseup events when double-click event detected,以及整个帖子! :-)

更好的答案:https://dev59.com/i2w15IYBdhLWcg3w4_7k#7845282


12
@Swader,你能分享一下你的解决方案吗?因为我也不想使用settimeout(...)方法。 - zaree
5
@Swader正在 Pull 一个 Fermat。 - Dan
2
我猜这个人从来没有发布更好的解决方案,是吗? - Brian Stinar
1
我想@Swader决定采用一种UX设计,不需要在同一元素上绑定单击和双击。当您希望在桌面和移动设备上使用时,这会变得更加复杂,将轻触-触摸-按住映射到单击-dblclick-context。我知道这可能不明智,但我只是很心动。 - prototype
1
@user645715 正确,我没有考虑到这一点。对于我们来说,移动设备并不是很重要,因为当时还处于桌面电脑时代,而且这是一个内部员工系统,但最终我选择了一个设计更好的界面。 - Swader
显示剩余2条评论

13

演示

$('#alreadyclicked').val('no click');
$('td.dblclickable').on('click',function(){
    var $button=$(this);
    if ($button.data('alreadyclicked')){
        $button.data('alreadyclicked', false); // reset
        $('#alreadyclicked').val('no click');

        if ($button.data('alreadyclickedTimeout')){
            clearTimeout($button.data('alreadyclickedTimeout')); // prevent this from happening
        }
        // do what needs to happen on double click. 
        $('#action').val('Was double clicked');
    }else{
        $button.data('alreadyclicked', true);
        $('#alreadyclicked').val('clicked');
        var alreadyclickedTimeout=setTimeout(function(){
            $button.data('alreadyclicked', false); // reset when it happens
            $('#alreadyclicked').val('no click');
            $('#action').val('Was single clicked');
            // do what needs to happen on single click. 
            // use $button instead of $(this) because $(this) is 
            // no longer the element
        },300); // <-- dblclick tolerance here
        $button.data('alreadyclickedTimeout', alreadyclickedTimeout); // store this id to clear if necessary
    }
    return false;
});

显然使用 <td class="dblclickable">


我将其调整到1300,无论我点击多快,它都会简单地延迟我的警报显示“单个”的时间,再延迟一秒钟。 - BReal14
实际上,如果我慢慢点击,我可以使dbl在1300窗口内触发。点击1 = 0,点击2 = 500毫秒后(-ish)时间框架。 2次缓慢而故意的点击。是JS文件执行所需的时间吗?(加载时包含在function(){}包装器中的一堆东西导致它等待执行?) - BReal14
不,我的逻辑是正确的 http://jsfiddle.net/V8twU/ 你把双击事件的程序放在正确的位置了吗? - Popnoodles
你在IE8中试过了吗?即使使用这个fiddle,它似乎对我来说也没有正确触发。 - BReal14
嘿,我很乐意。不过,这就是这里的“环境”,而且它仍然是唯一使用的浏览器。 :( - BReal14
非常好用!我将双击速度设置为250毫秒,接近熟悉的速度。 - Jonathan

4
我写了一个简单的jQuery插件,可以使用自定义的“singleclick”事件来区分单击和双击。这使您可以构建一个界面,在其中单击使输入变为可编辑,而双击则扩展它。就像Windows/OSX文件系统重命名/打开UI一样。 https://github.com/omriyariv/jquery-singleclick
$('#someInput').on('singleclick', function(e) {
    // The event will be fired with a small delay but will not fire upon a double-click.
    makeEditable(e.target);
}

$('#container').on('dblclick', function(e) {
    // Expand the row...
}

3

重写user822711的答案以便重复使用:

  $.singleDoubleClick = function(singleClk, doubleClk) {
    return (function() {
      var alreadyclicked = false;
      var alreadyclickedTimeout;

      return function(e) {
        if (alreadyclicked) {
          // double
          alreadyclicked = false;
          alreadyclickedTimeout && clearTimeout(alreadyclickedTimeout);
          doubleClk && doubleClk(e);
        } else {
          // single
          alreadyclicked = true;
          alreadyclickedTimeout = setTimeout(function() {
            alreadyclicked = false;
            singleClk && singleClk(e);
          }, 300);
        }
      }
    })();
  }

调用该函数。

$('td.dblclickable').bind('click', $.singleDoubleClick(function(e){
  //single click.
}, function(e){
  //double click.
}));

我似乎无法让它工作...在函数调用中不断出现缺少)的错误。 - BReal14

2
这个纯JavaScript的示例应该可以运行:
var timer = 0
var delay = 200
var prevent = false

node.onclick = (evnt) => {
    timer = setTimeout(() => {
         if(!prevent){
             //Your code
         }
         prevent = false
    }, delay)
}
node.ondblclick = (evnt) => {
    clearTimeout(timer)
    prevent=true
    //Your code
}

2
只有当可编辑字段被点击时才会出现此问题,因此请将常规的 click 处理程序附加到该元素,该处理程序将取消事件的传播(参见 stopPropagation),并设置一个超时时间(setTimeout(...)),例如 600ms(默认两次单击之间被视为双击的时间为 500ms [src])。如果在那个时间内没有发生 dblclick(您可以在两个事件处理程序中都可以访问的变量作为标志来检测此操作),则可以假定用户想要展开行并继续执行该操作...
我认为您应该重新考虑这个问题。不幸的是,单个单击处理程序无法知道用户是否即将双击。 我建议将两个操作都改为单击,并且在单击可编辑字段时不要向上传播。

1
window.UI = {
  MIN_LAPS_FOR_DBL_CLICK: 200, // msecs

  evt_down:null,
  timer_down:null,
  mousedown:function(evt){
    if( this.evt_down && evt.timeStamp < this.evt_down.timeStamp + this.MIN_LAPS_FOR_DBL_CLICK ){
      clearTimeout(this.timer_down);
      this.double_click(evt); // => double click
    } else {
      this.evt_down   = evt;
      this.timer_down = setTimeout("UI.do_on_mousedown()", this.MIN_LAPS_FOR_DBL_CLICK);
    }
  },
  evt_up:null,
  timer_up:null,
  mouseup:function(evt){
    if( this.evt_up && evt.timeStamp < this.evt_up.timeStamp + this.MIN_LAPS_FOR_DBL_CLICK ){
      clearTimeout(this.timer_up);
    } else {
      this.evt_up   = evt;
      this.timer_up = setTimeout("UI.do_on_mouseup()", this.MIN_LAPS_FOR_DBL_CLICK);
    }
  },
  do_on_mousedown:function(){
    var evt = this.evt_down;
    // Treatment MOUSE-DOWN here
  },
  do_on_mouseup:function(){
    var evt = this.evt_up;
    // Treatment MOUSE-UP here
  },
  double_click:function(evt){
    // Treatment DBL-CLICK here
  }

}

// Then the observers

$('div#myfoo').bind('mousedown',  $.proxy(UI.mousedown, UI));
$('div#myfoo').bind('mouseup',    $.proxy(UI.mouseup, UI));

0

补充一下@puttyshell的答案,这段代码有一个visualSingleClick函数的空间。这个函数在setTimeout之前运行,旨在为超时之前的操作显示一些视觉动作,这样我就可以使用更长的超时时间。我正在使用它在Google Drive浏览界面上向用户展示他点击的文件或文件夹已被选中。Google Drive本身也做了类似的事情,但没有看到它的代码。

/* plugin to enable single and double click on same object */
$.singleDoubleClick = function(visualSingleClk, singleClk, doubleClk) {
  return (function() {
    var alreadyclicked = false;
    var alreadyclickedTimeout;

    return function(e) {
      if (alreadyclicked) {
        // double
        alreadyclicked = false;
        alreadyclickedTimeout && clearTimeout(alreadyclickedTimeout);
        doubleClk && doubleClk(e);
      } else {
        // single
        alreadyclicked = true;
        visualSingleClk && visualSingleClk(e);
        alreadyclickedTimeout = setTimeout(function() {
          alreadyclicked = false;
          singleClk && singleClk(e);
        }, 500);
      }
    }
  })();
}

0

这是我在jQuery中处理它的方式:

async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
let clickCount = 0;
$(`#btnDebug`).on(`click`, async function() {
    clickCount++;
    if (clickCount > 1) {
        // handle our double-click event
        console.log(`got double click`);
        /*
        ...do stuff
        */
        clickCount = -1;
        return;
    }
    await sleep(300);
    if (clickCount === -1) {
        // we handled a double-click event so set everything back and exit
        clickCount = 0;
        return;
    }
    // handle our single-click event. We made it past our 300ms sleep and we didn't get another click so we're assuming a single-click.
    clickCount = 0;
    console.log(`got single click`);
        /*
        ...do stuff
        */
});

我意识到可以使用setTimeout而不是sleep,但我经常使用sleep函数,所以我在这里仍然使用了它。


-2

如果你在使用backbonejs,处理这个问题有一种更加优雅的方式:

    initialize: function(){
        this.addListener_openWidget();
    }
addListener_openWidget: function(){ this.listenToOnce( this, 'openWidgetView', function(e){this.openWidget(e)} ); },
events: { 'click #openWidgetLink' : function(e){this.trigger('openWidgetView',e)}, 'click #closeWidgetLink' : 'closeWidget' },
openWidget: function( e ){ //做你需要的事情 },
closeWidget: function(){ this.addListener_openWidget(); }

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