Things to try with a mouse:
- Drag outside the diagram, and come back {should still be dragging}
- Drag outside the diagram, and let go of the mouse button, then come back into the diagram {should stop dragging}
- Drag outside the window, then let go of the mouse button {should stop dragging}
- Right click on the circle {should not drag, but bring up the context menu}
- Right button drag the circle {should not drag, but bring up the context menu on Mac or Linux, or do nothing on Windows}
- Drag with the middle button {should not drag, but enable autoscroll}
- Drag with the left button, then hold down right button, continue drag, then release left button, then move mouse, then release right button {should stop dragging when releasing left button; I want a different behavior from this test[1]}
- Chords¹: drag with the left button, then hold down Ctrl, then release the left button {Mac/Firefox behaves differently, giving
pointerdown.left
and thenpointerup.right
whereas other browsers givepointerdown.left
andpointerup.left
} - While dragging, Alt+Tab (Cmd+Tab) to switch windows, then let go of the mouse button {should stop dragging}
- While dragging, use keyboard to scroll {should still be dragging² ⁵}
- While dragging, use scrollwheel to scroll {should still be dragging²}
- While dragging, use middle button to scroll {should still be dragging²}
- While dragging, resize the window {should still be dragging²}
- Select all text on page, then drag the circle {should drag the circle, not the text}
- Plug in two mice, start dragging on one, click on the other {want this to continue dragging, but it won’t³}
- On Mac, use an iPad as a second screen, drag with stylus on a mac browser {should drag}
- While dragging, put the computer to sleep and then wake it up and continue the drag {??}
- While dragging, unplug the mouse and plug it back in {??}
Things to try with touch:
- Drag the circle up or down {should not scroll the page}
- Drag the diagram up or down {should scroll the page}
- Long press on the circle {should not select the text or bring up a menu}
- Use two separate fingers to drag two separate objects {should both drag independently}
- Use two separate fingers to drag one object {should only drag with the first finger⁴}
- Drag with one finger then try to pinch zoom with a second finger {should not zoom}
- Drag with one finger then try to pinch zoom with two other fingers {does not zoom, not sure what it should do}
- Drag with one finger then rotate the phone {will rotate and continue dragging on iOS; will not rotate on Android}
- On iPad, use Mac “continuity” to drag with the mouse on an ipad browser {should drag}
Notes:
- ¹solution based on pointermove getting called for button changes[2]
- ²it won’t update until you nudge the mouse pointer
- ³this is a limitation of the OS level mouse handling
- ⁴I have a solution but rarely use it
- ⁵Firefox will fire
mouseover
,mouseenter
whereas Chrome will firepointerover
,pointerenter
,mouseover
,mouseenter
. There are a lot of differences in over/enter events but I haven’t investigated them yet.
The Webplatform Tests folk have run a lot more tests across browsers[3], like what happens if you remove an element in the middle of a pointer event.
1 HTML div with a link#
- The dragging code intercepts the left button so you can’t click on the link in Chrome, but you can click in Firefox or Safari.
- You can still middle click or right click on the link.
I tweaked the demo so you can click the link in Chrome:
This was a little tricky and I don’t know if it handles all cases but:
- on
pointerdown
, don’t capture; instead, set a flag moved=false - on
pointermove
, ifmoved
is stillfalse
, capture - on
pointermove
, set moved=true - on
click
, ifmoved
istrue
,event.preventDefault()
In general though I don’t try to do this type of thing. I don’t put links inside my draggable objects.
2 Click events#
This stackoverflow question[4] made me wonder what happens when we have both click and drag event handlers on the same object. The idea here is to listen to both types of events. If we detect a pointermove
, then don’t process the click
. Maybe it needs to have some threshold of how many pixels we moved before we suppress the click. I also wrote up a full demo on the stackoverflow page.
3 Coordinate transform on parent#
The draggable element might be inside another element that has a transform
applied to it.
The problem is that the eventToSvgCoordinates
function I use calculates coordinates relative to the <svg>
element. This is fine if your draggable object is positioned relative to the <svg>
’s coordinate system, but in this test there’s <svg>
then <g>
with a rotate()
on it. The pointer coordinates are in screen coordinates, and we need to unrotate them if we want the draggable to be placed within the rotated <g>
.
To handle this, extend the eventToSvgCoordinates
function to take an element the coordinate should be relative to, instead of assuming the coordinates should be in the <svg>
’s coordinate space.
/** Convert from event coordinate space (on the page) to SVG coordinate * space (within the svg, honoring responsive resizing, width/height, * and viewBox) */ function eventToSvgCoordinates(event, relativeTo=event.currentTarget.ownerSVGElement) { // if relativeTo is the <svg> then its ownerSVGElement is null, so // we want to point back to the <svg> but otherwise we assume it's // a child of <svg> and we want to find the <svg> let p = (relativeTo.ownerSVGElement ?? relativeTo).createSVGPoint(); p.x = event.clientX; p.y = event.clientY; return p.matrixTransform(relativeTo.getScreenCTM().inverse()); }
However, this function only handles transforms on the SVG elements. For transforms on non-SVG elements, I don’t know what to do. This is an unsolved issue, not only for me, but also other libraries like d3.js. See d3-selection issue #67[5] and firefox bug #1610093[6] . There’s probably something we can do using window.getComputedStyle(element)
but I haven’t tried.