nipplejs: Joystick gets stuck

I’m using Nipple.js on two separate transparent elements, overlaid on top of an HTML canvas - a ‘left’ and ‘right’ element. This allows me to use two joysticks for a ‘twin stick shooter’ style game.

I’m having a lot of trouble determining when this actually happens, but it is happening quite frequently: the right joystick “locks up”, frozen at the max extent of its bounds, and cannot be released without a page refresh. The left joystick never seems to lock up (it is defined first in my code).

Part of the difficulty in debugging this is that it only happens when using a phone to visit the website, and as such my debugging tools (namely access to the browser console) are practically non-existent. In the browser, I cannot reproduce the bug.

It also seems to happen only when there are 2 joysticks active at once. If only 1 joystick is used (despite both having been instantiated), the problem doesn’t seem to surface.

Any ideas? I’m digging through your code to see if I can spot where this bug might come from, but figured I would bring it up in case you are able to spot the issue more easily.

Might not be relevant, but here’s my usage:

function initializeMovementJoystick(){
    movementJoystick = NippleJS.create({zone: leftDiv});
    var currentOctant = null;
    var octants = new Array(8);
    octants[0] = function(state){InputHandler.setInputState("strafeRight", state);};
    octants[1] = function(state){InputHandler.setInputState("strafeRight", state);InputHandler.setInputState("forward", state);};
    octants[2] = function(state){InputHandler.setInputState("forward", state);};
    octants[3] = function(state){InputHandler.setInputState("strafeLeft", state);InputHandler.setInputState("forward", state);};
    octants[4] = function(state){InputHandler.setInputState("strafeLeft", state);};
    octants[5] = function(state){InputHandler.setInputState("strafeLeft", state);InputHandler.setInputState("backward", state);};
    octants[6] = function(state){InputHandler.setInputState("backward", state);};
    octants[7] = function(state){InputHandler.setInputState("strafeRight", state);InputHandler.setInputState("backward", state);};

    movementJoystick.on('end move', function(event, data){
        if (event.type === 'move'){
            if(data.distance < 15){
                if (currentOctant !== null){
                    //un-fire current
                    octants[currentOctant](false);
                    currentOctant = null;
                }
            } else {
                let angle = data.angle.degree;
                let octant =  Math.floor(((angle + 22.5) % 360) / 45.0);

                if(octant !== currentOctant){
                    if(currentOctant !== null){
                        //changed from one to another, un-fire last
                        octants[currentOctant](false);
                    }
                    //fire current
                    octants[octant](true);
                    currentOctant = octant;
                }
            }
        } else if (event.type === 'end'){
            if (currentOctant !== null){
                octants[currentOctant](false);
                currentOctant = null;
            }
        }
        //console.log(event);
        //console.log(data);
    });
}

function initializeRotationJoystick(){
    rotationJoystick = NippleJS.create({zone: rightDiv});
    var currentOctant = null;
    var octants = new Array(8);
    octants[0] = function(state){InputHandler.setInputState("turnRight", state);};
    octants[1] = function(state){InputHandler.setInputState("turnRight", state);InputHandler.setInputState("boost", state);};
    octants[2] = function(state){InputHandler.setInputState("boost", state);};
    octants[3] = function(state){InputHandler.setInputState("turnLeft", state);InputHandler.setInputState("boost", state);};
    octants[4] = function(state){InputHandler.setInputState("turnLeft", state);};
    octants[5] = function(state){InputHandler.setInputState("turnLeft", state);InputHandler.setInputState("shoot", state);};
    octants[6] = function(state){InputHandler.setInputState("shoot", state);};
    octants[7] = function(state){InputHandler.setInputState("turnRight", state);InputHandler.setInputState("shoot", state);};

    rotationJoystick.on('end move', function(event, data){
        if (event.type === 'move'){
            if (useRelativeOrientation){
                InputHandler.setInputState("targetAngle", data.angle.degree);
            } else if(data.distance < 15){
                if (currentOctant !== null){
                    //un-fire current
                    octants[currentOctant](false);
                    currentOctant = null;
                }
            } else {
                let angle = data.angle.degree;
                let octant =  Math.floor(((angle + 22.5) % 360) / 45.0);

                if(octant !== currentOctant){
                    if(currentOctant !== null){
                        //changed from one to another, un-fire last
                        octants[currentOctant](false);
                    }
                    //fire current
                    octants[octant](true);
                    currentOctant = octant;
                }
            }
        } else if (event.type === 'end'){
            if (currentOctant !== null){
                octants[currentOctant](false);
                currentOctant = null;
            }
        }
        //console.log(event);
        //console.log(data);

    });
}

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 15 (7 by maintainers)

Commits related to this issue

Most upvoted comments

I ended up getting stuck on this. It probably has something to do with event.preventDefault() or returning false from an event handler, but I couldn’t figure out what handler was causing the issue. Most of the occurrences of this issue can be solved by setting fadeTime: 0 so I’m going to stick with that for now.

I’ve done some more research today. It looks like the second touchend is not fired on mobile Safari or Mobile Firefox on Android when you do a slow double tap. This causes the second nipple element to get stuck.

I’m seeing this as well and can replicate only on iOS devices, Android seems to work fine.

I’ve added a console log to Super.trigger and I’m seeing the following output (you’re going to want to paste this into an editor to make any sense of it 😅):

[Log] added 0:added – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] added 0:added – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] start – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] start 0:start – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] rested – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] rested 0:rested – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] rested 0:rested – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] move – {identifier: 0, position: {x: 268, y: 525}, force: 0, …} (nipplejs.js, line 251)
{identifier: 0, position: {x: 268, y: 525}, force: 0, pressure: 0, distance: 0, …}Object
[Log] move 0:move – {identifier: 0, position: {x: 268, y: 525}, force: 0, …} (nipplejs.js, line 251)
{identifier: 0, position: {x: 268, y: 525}, force: 0, pressure: 0, distance: 0, …}Object
[Log] move – {identifier: 0, position: {x: 269, y: 523}, force: 0.044721359549995794, …} (nipplejs.js, line 251)
{identifier: 0, position: {x: 269, y: 523}, force: 0.044721359549995794, pressure: 0, distance: 2.23606797749979, …}Object
[Log] move 0:move – {identifier: 0, position: {x: 269, y: 523}, force: 0.044721359549995794, …} (nipplejs.js, line 251)
{identifier: 0, position: {x: 269, y: 523}, force: 0.044721359549995794, pressure: 0, distance: 2.23606797749979, …}Object
[Log] move – {identifier: 0, position: {x: 270, y: 523}, force: 0.0565685424949238, …} (nipplejs.js, line 251)
{identifier: 0, position: {x: 270, y: 523}, force: 0.0565685424949238, pressure: 0, distance: 2.8284271247461903, …}Object
[Log] move 0:move – {identifier: 0, position: {x: 270, y: 523}, force: 0.0565685424949238, …} (nipplejs.js, line 251)
{identifier: 0, position: {x: 270, y: 523}, force: 0.0565685424949238, pressure: 0, distance: 2.8284271247461903, …}Object
[Log] move – {identifier: 0, position: {x: 269, y: 524}, force: 0.0282842712474619, …} (nipplejs.js, line 251)
{identifier: 0, position: {x: 269, y: 524}, force: 0.0282842712474619, pressure: 0, distance: 1.4142135623730951, …}Object
[Log] move 0:move – {identifier: 0, position: {x: 269, y: 524}, force: 0.0282842712474619, …} (nipplejs.js, line 251)
{identifier: 0, position: {x: 269, y: 524}, force: 0.0282842712474619, pressure: 0, distance: 1.4142135623730951, …}Object
[Log] end – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] end 0:end – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] added 1:added – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object
[Log] added 1:added – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object
[Log] start – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object
[Log] start 1:start – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object
[Log] rested – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object
[Log] rested 1:rested – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object
[Log] rested 1:rested – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object
[Log] move – {identifier: 1, position: {x: 275, y: 524}, force: 0, …} (nipplejs.js, line 251)
{identifier: 1, position: {x: 275, y: 524}, force: 0, pressure: 0, distance: 0, …}Object
[Log] move 1:move – {identifier: 1, position: {x: 275, y: 524}, force: 0, …} (nipplejs.js, line 251)
{identifier: 1, position: {x: 275, y: 524}, force: 0, pressure: 0, distance: 0, …}Object
[Log] removed – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] removed 0:removed – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] removed 0:removed – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] destroyed – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] hidden – {el: <div id="nipple_0_0">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_0">, on: function, off: function, show: function, hide: function, …}Object
[Log] shown – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object
[Log] shown 1:shown – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object
[Log] shown 1:shown – {el: <div id="nipple_0_1">, on: function, off: function, …} (nipplejs.js, line 251)
{el: <div id="nipple_0_1">, on: function, off: function, show: function, hide: function, …}Object

It looks as though the nipple_0_0 element is added, end is called, nipple_0_1 is added, nipple_0_0 is removed, and then nipple_0_1 has it’s shown event fired and is stuck.

@yoannmoinet couldn’t reproduce bug with dataOnly param set to true. Could it be dom manipulation related issue?