Click and Drag demo, using mouse events. + Drag a physicist's name into one of the green slots to rearrange
+-
+
- + +
- + +
- + +
- + +
- + +
- +
diff --git a/mouseevents/README.md b/mouseevents/README.md new file mode 100644 index 0000000..ff1cb26 --- /dev/null +++ b/mouseevents/README.md @@ -0,0 +1,5 @@ +Examples: + +* [ClickNDrag program](https://mdn.github.com/dom-examples/mouseevents/Click_N_Drag.html). This illustrates clicking and dragging of objects on a web page, without the Drag & Drop API. + + diff --git a/mouseevents/click-and-drag/Click_N_Drag.css b/mouseevents/click-and-drag/Click_N_Drag.css new file mode 100644 index 0000000..6d1f004 --- /dev/null +++ b/mouseevents/click-and-drag/Click_N_Drag.css @@ -0,0 +1,57 @@ +/* without these, the page ends after the content. + I want to drag all over the page. */ +html, body, main.arena { + width: 100%; + height: 100%; +} + +html { + overflow: hidden; + background: white; +} + +/* wrapper for either a physicist or a slot */ +li { + list-style-type: none; + width: 400px; + padding: 2px; + margin: 13px; + border-radius: 5px; +} + +/* a target for mousedown */ +.physicist { + cursor: grab; // tells user they can grab it + line-height: 20px; + background-color: #488; + border: 4px outset #aca; + color: #fff; + text-align: center; +} + +/* the physicist that the user is dragging around, after user has clicked to drag */ +.physicist.ghost { + position: absolute; + background-color: #266; + opacity: 0.7; /* want to be able to see hilited slot underneath */ + pointer-events: none; /* so pointer events go thru to slot */ +} + +/* physicist, hidden after it's been picked up and only the ghost is visible */ +.physicist.original { + visibility: hidden; +} + +/* where to drag a physicist */ +.slot { + height: 15px; + background-color: #dfd; + border: 4px inset #aca; + /*color: black;*/ +} + +.slot.targeted { + /* When user moves ghost over a slot. A BIG change in appearance; + this only happens for a fraction of a second and is otherwise hard to see */ + background-color: #f80; +} diff --git a/mouseevents/click-and-drag/Click_N_Drag.html b/mouseevents/click-and-drag/Click_N_Drag.html new file mode 100644 index 0000000..2065dfc --- /dev/null +++ b/mouseevents/click-and-drag/Click_N_Drag.html @@ -0,0 +1,36 @@ + + +
+ + + + + + + ++So, normally, the user can move the mouse around without clicking and no physicist names are disturbed. +The user expects to click down on a physicist, drag it to some kind of destination, and let go. +The user might also want to cancel the operation after clicking down, by dragging away, +so most of the surface should NOT be a place that's an active slot.
+ ++First, decide upon what elements do what. +You'll have one or more objects that the user can drag around (physicist names in this example) +and one or more slot targets that the user can drag them into.
+ ++Your objects need a mousedown handler, cuz the user will be clicking down on them. +
+ physicistElements.forEach(physicist => { + physicist.addEventListener('mousedown', mouseDownHandler); + }); ++ +The mousedown handler sets everything up for dragging. +The original clickdown physicist is kept around, +but we make a clone of it for the ghost - the image of what the user thinks they're dragging. + The arena is the whole area the user can drag around in. +
+let arenaElement; +let arenaUl; // the
So all the mouse events have a few things in common: +they all contain the coordinates of the mouse, +and you have to move the ghost to match. +(And, at the initial mousedown, the ghost has to line up perfectly with the +original so it looks like the original just turned into the ghost.) +So setGhostLocation() does stuff that all the mouse handlers need to do.
++let ghostElement; +let ghostOffsetX; +let ghostOffsetY; + +function setGhostLocation(ev) { + ghostElement.style.left = `${ev.pageX - ghostOffsetX}px`; + ghostElement.style.top = `${ev.pageY - ghostOffsetY}px`; + + // no text selections! we need this for all the events + ev.stopPropagation(); + ev.preventDefault(); +} ++ +
+The browser will send mouse move events, always, as long as the handler's set up and all. +Even if the mouse isn't down. Even if the browser window isn't in front. +Mouse move events might come in a hundred per second, or even faster. +But, it's highly variable. +Conditions might include, how much work your handler does, +or how much overhead you have elsewhere on your page or your computer. +Or, what model of computer you have, as machines get faster over the years. +Never measure anything by the number of mouse move events, or their frequency -- +only by the linear distance as given by the x and y coordinates. +Or, possibly, by timestamps from the events.
++function mouseMoveHandler(ev) { + if (ghostElement && (ev.buttons & 1)) { + setGhostLocation(ev); + } +} ++ +
+Eventually, the drag must come to an end. +Typically, the user just lets go on the mouse. +But, if the user drags it out of the arena, you won't get the mouseup event, +the new mouse location will get the mouseup. +(And it probably will get ignored.) +So this is also the mouseleave handler. +You have to make sure that drag mode ends before the mouse slips away, +and all the things that were set in the mouseDown handler get closed or reset. +Also, get rid of the ghost element to indicate you're not in the middle of a drag.
++function mouseUpHandler(ev) { + // ev.buttons has already been set to zero + if (ghostElement) { + setGhostLocation(ev); + + // do we do it? Only if they've been dragging over a slot + if (targetSlot) { + moveAPhysicist(originalElement.dataset.physicist, targetSlot.dataset.slot) + + // Now's when you might want to trigger off some animation, + // depicting objects moving around + } + + // remove any slot hiliting, like user dragged out of it + leaveSlotHandler(ev); + + originalElement.classList.remove('original'); + + arenaElement.style.cursor = ''; // turns off grabbing hand + + ghostElement.remove(); + ghostElement = null; + } +} ++ + +
+Instead of having move, enter, leave, and up listeners in the arena set all the time, +you can add them in the mousedown handler, and then remove them in the mouseup handler.
+ ++Instead of putting mousemove and mouseup handlers on the page body, +you can use `setPointerCapture()` to funnel all the move and other mouse events to the original element. +You need to supply the pointer ID which is only available on pointer events, +so you have to switch to pointer event handling. +Then you can add pointermove and pointerup handlers to the original clickdown object. +Capture mode ends by itself when the user releases.
+ ++You can do exactly the same things with PointerEvents as with MouseEvents. +Simply global-replace `mouse` with `pointer`. +They work exactly the same, although pointer events have more features.
+ ++The animation for picking up an object, dragging it over the page, and setting it down, can do anything you want. +If you want to make the ghost transparent, set the `opaque` property on the top level to 0.5, or adjust to taste. +If the user is copying instead of moving, you can leave the original object in place.
+ ++In this program, whenever I use it, I get this awkward feeling when I drop a physicist into a new location, +because the others shuffle around instantly, but I can't see that motion. +It would be nice to have the items move around by themselves, slowly, so the user can see. +Like, 200ms or 500ms, not 10ms or 20ms. +The way it is now, the physicist boxes don't actually rearrange, just the text inside them. +So the user is actually rearranging the data in the physicists table. +Instead, you could move around the <li> elements themselves; and leave the text in each. +In fact, you don't even have to use <ul> and <li> elements, +but for disabled people, it's better to keep the 'list' semantics.
+ ++One way to do this is, instead of having the physicists and slots laid out normally, make them all absolutely positioned. +You can use the Transitions CSS properties to smooth out motions to nice animations; +in this case the transition-properties would be top and/or bottom. +(If you also use left or right, you can make the paths curve out some - cool!) +You must do the calculations for all the locations of items and slots. +When the user actually changes the order of the items, change the coordinates to the new locations. +Transitions will smooth out the motions and stop after the duration you chose. +Cool! + +
+ + +