ericdrowell / KineticJS

KineticJS is an HTML5 Canvas JavaScript framework that extends the 2d context by enabling canvas interactivity for desktop and mobile applications.
http://www.kineticjs.com
3.98k stars 753 forks source link

distinguish between tap/dbltap on mobiles #1057

Closed deostroll closed 9 years ago

deostroll commented 9 years ago

Facing an issue in a phonegap/cordova app.

I have written code for a Layer object to handle both tap and double-tap events. The issue is on double-tap - 2 tap events are triggered. This seems to be a common issue on mobile phones I guess.

I need a kinetic safe work around for this?

Here is code which allowed me to replicate and detect the issue:

    function _console(){
      var args = arguments;
      var dt = Date.now();
      var s = [];
      var dtFormat = function(dt){
        var h = dt.getHours(),
          m = dt.getMinutes(),
          s = dt.getSeconds();

        var pad = function(n) { return n < 10? '0' + n : n};
        return [h,m,s].map(pad).join(':');
      }
      if(!_console.calledOnce){
        _console.calledOnce = true;
        _console.tick = dtFormat(new Date());
        setInterval(function(){
          _console.tick = dtFormat(new Date());
        }, 500);
      }

      console.log('@: ' + _console.tick);
      if(args.length > 1){
        for(var i = 0, j = args.length; i < j; i++){
          s.push(JSON.stringify(args[i], null, 4));
        }
        //console.log("tick start: " + dt);
        s.forEach(function(str){
          console.log(str);
        });
        //console.log("tick end: " + dt);
      }
      else {
        //console.log("tick start: " + dt);
        console.log(JSON.stringify(args[0], null, 4));
        //console.log("tick end: " + dt);
      }

    }

    var stage = new Kinetic.Stage({
        height:300,
        width:300,
        container:'deviceready'
    });

    var layer = new Kinetic.Layer();

    var rect = new Kinetic.Rect({
        height:300,
        width: 300,
        stroke:'black'
    });

    layer.add(rect);

    layer.on('tap', function onLayerTapFn(){
        _console('onLayerTapFn');
    });

    layer.on('dbltap', function onLayerDblTap(){
        _console('onLayerDblTap');
    });

    stage.add(layer);

I realize you have to modify kinetic's event handling mechanism somewhere to this effect. But where to start hacking...

confile commented 9 years ago

+1

lavrton commented 9 years ago

What an issue? Second tap event is not expected?

deostroll commented 9 years ago

Yes the 2nd tap event is causing problems

lavrton commented 9 years ago

Usual DOM works is same way: http://jsbin.com/tejohe/1/edit?html,js,output. Second tap is still tap event, so event should be fired.

You can implement simple workaround:

var wasDblClick = false;
rect.on('click tap', function() {
        setTimeout(function() {
         if (wasDblClick) {
             wasDblClick = false;
             return;
         }
          console.log('click');
}, 10);
});

rect.on('dblclick dbltap', function() {
  wasDblClick = true;
  console.log('dblclick');
});
deostroll commented 9 years ago

Hi,

I've tried your suggestion, but I am getting incorrect behaviour. I am sharing code + log:

var wireKineticEventSafe = function (obj, evtname, callback) {            
  var dbltap = false;
  if(evtname && typeof(evtname) === 'string'
    && callback && typeof(callback) === 'function'
      && typeof(obj) === 'object' && obj.on) {
    switch(evtname) {
      case 'tap':
        obj.on('tap', function(evt) {
          _console('tap event fired');
          setTimeout(function(){
            _console('tap timeout fired');
            if (dbltap) {
              dbltap = false;
              return;
            }
            callback.call(obj, evt);
          }, 10);

        });
        break;
      case 'dbltap':
        obj.on('dbltap', function(evt){
          _console('dbltap event fired');
          dbltap = true;
          callback.call(obj, evt);
        });
        break;
      default:
        //wire others as usual
        obj.on(evtname, callback);
        break;
    }//end switch
  }//end if
};

function _console(){
  var args = arguments;
  var dt = Date.now();
  var s = [];

  var dtFormat = function(dt){
    var h = dt.getHours(),
      m = dt.getMinutes(),
      s = dt.getSeconds();

    var pad = function(n) { return n < 10? '0' + n : n};
    return [h,m,s].map(pad).join(':');
  }
  if(!_console.calledOnce){
    _console.calledOnce = true;
    _console.tick = dtFormat(new Date());
    setInterval(function(){
      _console.tick = dtFormat(new Date());
    }, 500);    
  }

  var show = function(msg){
    //console.log('show: ' + typeof _console.show);
    if(typeof _console.show === 'undefined') {
      _console.show = _console.tick;
      console.log(msg);
      return;
    }

    if(_console.show !== _console.tick) {
      _console.show = _console.tick;
      console.log(msg);
    }
  };

  show('@: ' + _console.tick);

  if(args.length > 1){
    for(var i = 0, j = args.length; i < j; i++){
      s.push(JSON.stringify(args[i], null, 4));
    }
    //console.log("tick start: " + dt);
    s.forEach(function(str){
      console.log(str);
    });
    //console.log("tick end: " + dt);
  }
  else {
    //console.log("tick start: " + dt);
    console.log(JSON.stringify(args[0], null, 4));
    //console.log("tick end: " + dt);
  }

}
app.initialize();

var stage = new Kinetic.Stage({
    height:300,
    width:300,
    container:'deviceready'
});

var layer = new Kinetic.Layer();

var rect = new Kinetic.Rect({
    height:300,
    width: 300,
    stroke:'black'
});

layer.add(rect);

// layer.on('tap', function onLayerTapFn(){
//     _console('onLayerTapFn');
// });

// layer.on('dbltap', function onLayerDblTap(){
//     _console('onLayerDblTap');
// });

wireKineticEventSafe(layer, 'tap', function(){
  _console('tap');
});

wireKineticEventSafe(layer, 'dbltap', function(){
  _console("dbltap");
});

stage.add(layer);

LogFile:

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 111 : @: 05:56:09

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "tap event fired"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "tap timeout fired"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "tap"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 111 : @: 05:56:21

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "tap event fired"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "tap timeout fired"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "tap"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "tap event fired"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "dbltap event fired"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "dbltap"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "tap timeout fired"

D/CordovaLog( 2786): file:///android_asset/www/js/index.js: Line 129 : "tap"

Perhaps I am doing something wrong but the sequence of events fired is incorrect. Any ideas on correcting this?

lavrton commented 9 years ago

var dbltap = false; should be outside of wireKineticEventSafe function.

deostroll commented 9 years ago

Thanks for making me realize the bug in the above post. Had to fix it by using memoization.