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

preventDefault on mousedown and touchstart by default? #9

Closed
mbostock opened this issue May 8, 2016 · 6 comments
Closed

preventDefault on mousedown and touchstart by default? #9

mbostock opened this issue May 8, 2016 · 6 comments
Assignees

Comments

@mbostock
Copy link
Member

mbostock commented May 8, 2016

There’s a long history here (d3/d3#1322, particularly @jfirebaugh’s comment) but I think it may be best to mousedown.preventDefault and touchstart.preventDefault by default, rather than allow the defaults and then subsequently try to prevent them selectively later.

Here’s my rationale:

  • It’s a Chrome bug that calling mousemove.preventDefault prevents text selection, drag-and-drop, and scrolling (see issue 485892). The mousemove event is defined as having no default action. Thus, the only standards-compliant way of preventing these default behaviors is to do it on mousedown.

    Yes, this prevents capturing mouse events outside an iframe in Chrome due to another bug (issue 269917), but when Pointer Events are implemented (issue 471824) we’ll have element.setPointerCapture as the standard way of capturing events without needing the window hack for mousemove and mouseup.

  • If multiple touches start on the same element, you must call touchstart.preventDefault to disable pinch-and-zoom. We’ve ignored this in the past because the old drag behavior assumed the touches were always on different elements (e.g., distinct SVG circles). The new d3-drag is agnostic about the DOM structure, and it should be usable even if the circles are rendered with Canvas. It would be brittle and inconsistent to allow the default behavior on touchstart if there’s only one touch (on a given element) and not allow it if there are multiple touches.

    Calling touchstart.preventDefault prevents a subsequent click. That’s somewhat unfortunate because this is not true of mousedown.preventDefault, and in the future, pointerdown.preventDefault:

    In user agents that support firing click and/or contextmenu, calling preventDefault during a pointer event typically does not have an effect on whether click and/or contextmenu are fired or not.

    However, you could still implement clickable and draggable things by calling element.click on drag end (if there’s no movement), and this is probably more obvious than checking click.defaultPrevented. I’ll have to test whether it’s possible to call element.click to explicitly click during a mouse-based drag, while still suppressing the default click…

  • The selectstart event is a working draft specification and not yet available in Firefox (Standard). Yes, Firefox supports -moz-user-select, but that’s vendor-prefixed (and buggy; see issue 648624). Given the inconsistency of default behaviors across browsers and input modes, trying to prevent them selectively has been an ongoing headache.

  • Whatever we choose as the default behavior of d3-drag (no pun intended!), that behavior is just the default and can be overridden or disabled. So if we decide that d3-drag should mousedown.preventDefault and touchstart.preventDefault by default, users can disable this behavior by calling drag.on and removing the .nodefault listener, and then do whatever they want. In contrast to D3 3.x, it is not necessary to fork the implementation.

The Chrome bug is unfortunate. But it feels like we should assume that this will eventually be fixed when Pointer Events are implemented, and that the most future-proof way to implement d3-drag will be to mousedown.preventDefault and touchstart.preventDefault (and eventually, pointerdown.preventDefault). Changing d3-drag’s default behavior in regards to preventing browser default behaviors in the future should probably be considered a breaking change, so it’d be great to commit to doing it now as part of the D3 4.0 major release.

/cc @jfirebaugh @jasondavies

@mbostock mbostock self-assigned this May 8, 2016
@mbostock
Copy link
Member Author

mbostock commented May 9, 2016

There’s a partial workaround for the inability to capture events with mousedown.preventDefault: listen to top rather than window. But it only works if the top frame has the same domain as the frame, which makes the workaround much less useful.

@mbostock
Copy link
Member Author

mbostock commented May 9, 2016

If we mousedown.preventDefault:

  • We may want to call element.focus on an ancestor. However, that appears to only work on HTML elements with a tabIndex attribute or SVG a elements. Alternatively we could call element.blur on the document.activeElement.
  • We may want to listen to top rather than window. However, this would require catching the security error and a fallback to window, so dragging will still break if the mouse leaves the iframe and the top window is not on the same domain.
  • We may or may not want to suppress all clicks, or just clicks on moved drags.

If we don’t mousedown.preventDefault:

  • We need to preventDefault on dragstart and selectstart and set-moz-user-select: none.
  • We may or may not want to suppress all clicks, or just clicks on moved drags.

If we touchstart.preventDefault:

  • We may want to generate a synthetic click, or just on a moved drag. This is a bit tricky. We can’t use element.click because it’s unsupported on SVG elements, and we probably want the click event to have the correct coordinates.

If we don’t touchstart.preventDefault:

  • Some drag operations may trigger a pinch-zoom. It’s unclear when or why, as in most cases touchmove.preventDefault seems to be sufficient to prevent it.

@mbostock mbostock closed this as completed May 9, 2016
@jfirebaugh
Copy link

👍

In contrast to D3 3.x, it is not necessary to fork the implementation.

Given this, I agree that it's best to have the most standards compliant and forward looking behavior be the default for D3 4.0.

@mbostock
Copy link
Member Author

mbostock commented May 9, 2016

Thanks for replying. I think I’m leaning towards keeping things mostly the same with D3 3.x (although D3 4.0 will be more easily customizable, should you want different behavior) for now.

When the Chrome bug is fixed, I might switch to mousedown.preventDefault, but then I’d need to figure out how to focus.

When Pointer Events are supported, we could use those instead of touch events, and then we should be able to pointerdown.preventDefault to prevent pinch-and-zoom without suppressing click.

But it’s all a big if, since the nature of the preventDefault API is that it doesn’t let you explicitly specify what you intend to prevent.

@Herst
Copy link
Contributor

Herst commented May 24, 2019

Pointer events have landed in Chrome in the meantime: https://caniuse.com/#feat=pointer

@Herst
Copy link
Contributor

Herst commented Jul 31, 2019

The selectstart event landed in Firefox: https://caniuse.com/#feat=selection-api

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

No branches or pull requests

3 participants