Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Click-event Handling #38

Closed
DRippstein opened this issue Sep 12, 2012 · 4 comments
Closed

Better Click-event Handling #38

DRippstein opened this issue Sep 12, 2012 · 4 comments
Labels

Comments

@DRippstein
Copy link

There are some minor issues with signature-pad's click events, and what happens when the mouse goes outside the canvas.

  1. If you drag the mouse out of the canvas quickly, it sometimes won't draw the final stroke (from the starting-point to where the mouse leaves the canvas).
  2. If you drag the mouse out and then drag it back in, it will draw a straight line from the cursor's last exit point to the new entry point, adding a line we literally didn't draw.
  3. If you drag the mouse out and release the mouse button (still outside the canvas), moving the cursor over the canvas again will start drawing although the mouse is not being clicked.
  4. If you drag the mouse out of the canvas at all in Chrome, it starts highlighting text.

The first three scenarios only apply if you don't linger outside, because mouseLeaveTimeout ends any drawing when triggered.

A bit of extra coding solve both these problems. Namely:

A. Add an optional parameter (e) to stopDrawing that draws one last dot (at the event mouse cursor location) before stopping the draw.
B. mouseleave always calls stopDrawing(e) to send the last known mouse position
[These 2 changes solve problem 1]
C. If stopDrawing is passed an event from mouseleave, don't unbind the mousemove events like normal
D. mousemove detects if the drawing was stopped, and if so, begins a new line
[These 2 changes solve problem 2]
E. Use $(document) instead of canvas for all mouseup event bindings.
F. Create private boolean variable mouseButtonDown that is set to 'true' by mousedown events and 'false' by mouseup/mousedowntimeout events.
G. Mouseup events won't run code unless mouseButtonDown is true.
[These 3 changes solve problem 3]
H. Add a 'noselect' class to signaturepad.css that disables text selection.
I. Add/remove the 'noselect' class on body whereever mouseButtonDown is set to true/false.
[These 2 changes solve problem 4]

Now that you know the "why", here's the actual code...

function SignaturePad (selector, options) {
...
  /**
* Whether the mouse button is currently pressed down or not
*
* @private
*
* @type {Boolean}
*/
  , mouseButtonDown = false
...

Notice the added e variable in this block. e is only passed by mouseleave, so this can draw a point at the last known mouse location and leave bindings alone.

  function stopDrawing (e) {
    if (!!e) {
      drawLine(e, 1)
    } else {
      if (touchable) {
        canvas.each(function () {
          this.removeEventListener('touchmove', drawLine)
        })
      } else {
        canvas.unbind('mousemove.signaturepad')
      }
    }
  ...

I've added this above startDrawing though it doesn't really matter where.

  /**
* Callback registered to mouse/touch events of the canvas
* Draws a line at the mouse cursor location, starting a new line if necessary
*
* @private
*
* @param {Object} e The event object
* @param {Object} o The object context registered to the event; canvas
*/
  function onMouseMove(e, o) {
    if (previous.x == null) { drawLine(e, 1) } else { drawLine(e, o) }
  }

Changed callbacks from drawLine to onMouseMove.

  function startDrawing (e, o) {
    if (touchable) {
      canvas.each(function () {
        this.addEventListener('touchmove', onMouseMove, false)
      })
    } else {
      canvas.bind('mousemove.signaturepad', onMouseMove)
    }
  ...

Time to update more mouseup/down/leave bindings.

  function disableCanvas () {
    ...
    $(document).unbind('mouseup.signaturepad')
    ...

  function initDrawEvents (e) {
  ...
      $(document).bind('mouseup.signaturepad', function (e) {
        if (mouseButtonDown) {
          mouseButtonDown = false
          $('body').removeClass('noselect')
          stopDrawing()
        }
      })
      canvas.bind('mouseleave.signaturepad', function (e) {
        stopDrawing(e)
        if (!mouseLeaveTimeout) {
          mouseLeaveTimeout = setTimeout(function () {
            stopDrawing()
            clearTimeout(mouseLeaveTimeout)
            mouseLeaveTimeout = false
            mouseButtonDown = false
            $('body').removeClass('noselect')
          }, 500)
        }
      })
  ...

  function drawIt () {
  ...
    canvas.bind('mousedown.signaturepad', function (e) {
      mouseButtonDown = true
      $('body').addClass('noselect')
      initDrawEvents(e)
      startDrawing(e, this)
    })
  ...

But I only wrote this today and this really needs more (multi-browser) testing. Hope this helps!

@thomasjbradley
Copy link
Contributor

Do these changes still allow the user to accidentally exit the and come back in without cancelling their signature?

@DRippstein
Copy link
Author

It sure does. As long as it's within 0.5 seconds, the mouseLeaveTimeout limit.

It doesn't actually stop mousemove from working, it adds to it. Now it will 'stop' and 'start' a new line on mouseleave/mousemove, instead of always drawing connect-the-dots like in problem 2... even when off-canvas.

And I almost forgot, here's the added signaturePad.css lines...

.noselect {
    /* Prevent text selection */
    /* added from http://stackoverflow.com/a/5332860 */
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -o-user-select: none;
    user-select: none;
}

@DRippstein
Copy link
Author

You know what, I don't think I made the logic of those changes really clear. Let me try again.

signaturePad is a game of connect-the-dots.

  1. When the last dot is outside the canvas, nothing can be drawn (we don't want lines extending outside the canvas). So just as the mouse leaves the canvas, add a dot right at the exit point. Then signaturePad can draw the last line.
  2. When the mouse leaves and returns to the canvas, we expect signaturePad to 'draw offscreen' and resume on canvas like one continual line never ended. But signaturePad doesn't read minds, so it connects the last known dot before exiting with the new entry dot, adding a surprise line. 'Stopping' and 'starting' the line drawing when leaving/entering the canvas solves this.
  3. When the mouse button is released, the drawing stops. But if we're only tracking mouseUp on the canvas, we'll never know if they released the button off-canvas... which can result in the mouse drawing forever. Tracking mouseUp on the whole HTML doc (and adding an If to see whether we're currently drawing) solves this.
  4. I don't know what the W3C specs say, but Chrome starts selecting text when the mouse is dragged. Even if it originates inside a canvas object. Most browers check for that, but Chrome doesn't. So just tell Chrome (or any browser) to not highlight text when we're drawing, plain and simple. CSS does it nicely.

@thomasjbradley
Copy link
Contributor

Ah, thank you! That explanation was very clear.
All of your improvements are fantastic, thanks for contributing.
I didn't even know about the user-select property. Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants