<html>
<head>
<meta charset=utf-8 />
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Drag demo - mouse+pointer</title>
</head>
<body>
<div id=cards>
<div class=card><div class=icon>1</div></div>
<div class=card><div class=icon>2</div></div>
<div class=card><div class=icon>3</div></div>
<div class=card><div class=icon>4</div></div>
<div class=card><div class=icon>5</div></div>
<div class=card><div class=icon>6</div></div>
</div>
<div id=circles>
<div class=circle></div>
<div class=circle></div>
<div class=circle></div>
</div>
<script>
// Hand.js pointer event polyfill: http://handjs.codeplex.com/
(function () {
// Polyfilling indexOf for old browsers
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement) {
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 0) {
n = Number(arguments[1]);
if (n != n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n != 0 && n != Infinity && n != -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
};
}
// Installing Hand.js
var supportedEventsNames = ["PointerDown", "PointerUp", "PointerMove", "PointerOver", "PointerOut", "PointerCancel", "PointerEnter", "PointerLeave",
"pointerdown", "pointerup", "pointermove", "pointerover", "pointerout", "pointercancel", "pointerenter", "pointerleave",
];
var POINTER_TYPE_TOUCH = "touch";
var POINTER_TYPE_PEN = "pen";
var POINTER_TYPE_MOUSE = "mouse";
// Touch events
var generateTouchClonedEvent = function (sourceEvent, newName) {
// Considering touch events are almost like super mouse events
var evObj;
if (document.createEvent) {
evObj = document.createEvent('MouseEvents');
evObj.initMouseEvent(newName, true, true, window, 1, sourceEvent.screenX, sourceEvent.screenY,
sourceEvent.clientX, sourceEvent.clientY, sourceEvent.ctrlKey, sourceEvent.altKey,
sourceEvent.shiftKey, sourceEvent.metaKey, sourceEvent.button, null);
}
else {
evObj = document.createEventObject();
evObj.screenX = sourceEvent.screenX;
evObj.screenY = sourceEvent.screenY;
evObj.clientX = sourceEvent.clientX;
evObj.clientY = sourceEvent.clientY;
evObj.ctrlKey = sourceEvent.ctrlKey;
evObj.altKey = sourceEvent.altKey;
evObj.shiftKey = sourceEvent.shiftKey;
evObj.metaKey = sourceEvent.metaKey;
evObj.button = sourceEvent.button;
}
// offsets
if (evObj.offsetX === undefined) {
if (sourceEvent.offsetX !== undefined) {
// For Opera which creates readonly properties
if (Object && Object.defineProperty !== undefined) {
Object.defineProperty(evObj, "offsetX", {
writable: true
});
Object.defineProperty(evObj, "offsetY", {
writable: true
});
}
evObj.offsetX = sourceEvent.offsetX;
evObj.offsetY = sourceEvent.offsetY;
}
else if (sourceEvent.layerX !== undefined) {
evObj.offsetX = sourceEvent.layerX - sourceEvent.currentTarget.offsetLeft;
evObj.offsetY = sourceEvent.layerY - sourceEvent.currentTarget.offsetTop;
}
}
// adding missing properties
if (sourceEvent.isPrimary !== undefined)
evObj.isPrimary = sourceEvent.isPrimary;
else
evObj.isPrimary = true;
if (sourceEvent.pressure)
evObj.pressure = sourceEvent.pressure;
else {
var button = 0;
if (sourceEvent.which !== undefined)
button = sourceEvent.which;
else if (sourceEvent.button !== undefined) {
button = sourceEvent.button;
}
evObj.pressure = (button == 0) ? 0 : 0.5;
}
if (sourceEvent.rotation)
evObj.rotation = sourceEvent.rotation;
else
evObj.rotation = 0;
// Timestamp
if (sourceEvent.hwTimestamp)
evObj.hwTimestamp = sourceEvent.hwTimestamp;
else
evObj.hwTimestamp = 0;
// Tilts
if (sourceEvent.tiltX)
evObj.tiltX = sourceEvent.tiltX;
else
evObj.tiltX = 0;
if (sourceEvent.tiltY)
evObj.tiltY = sourceEvent.tiltY;
else
evObj.tiltY = 0;
// Width and Height
if (sourceEvent.height)
evObj.height = sourceEvent.height;
else
evObj.height = 0;
if (sourceEvent.width)
evObj.width = sourceEvent.width;
else
evObj.width = 0;
// PreventDefault
evObj.preventDefault = function () {
if (sourceEvent.preventDefault !== undefined)
sourceEvent.preventDefault();
};
// Constants
evObj.POINTER_TYPE_TOUCH = POINTER_TYPE_TOUCH;
evObj.POINTER_TYPE_PEN = POINTER_TYPE_PEN;
evObj.POINTER_TYPE_MOUSE = POINTER_TYPE_MOUSE;
// Pointer values
evObj.pointerId = sourceEvent.pointerId;
evObj.pointerType = sourceEvent.pointerType;
switch (evObj.pointerType) {// Old spec version check
case 2:
evObj.pointerType = evObj.POINTER_TYPE_TOUCH;
break;
case 3:
evObj.pointerType = evObj.POINTER_TYPE_PEN;
break;
case 4:
evObj.pointerType = evObj.POINTER_TYPE_MOUSE;
break;
}
// If force preventDefault
if (sourceEvent.currentTarget && sourceEvent.currentTarget.handjs_forcePreventDefault === true)
evObj.preventDefault();
// Fire event
if (sourceEvent.target) {
sourceEvent.target.dispatchEvent(evObj);
} else {
sourceEvent.srcElement.fireEvent("on" + getMouseEquivalentEventName(newName), evObj); // We must fallback to mouse event for very old browsers
}
};
var generateMouseProxy = function (evt, eventName) {
evt.pointerId = 1;
evt.pointerType = POINTER_TYPE_MOUSE;
generateTouchClonedEvent(evt, eventName);
};
var handleOtherEvent = function (eventObject, name) {
if (eventObject.preventManipulation)
eventObject.preventManipulation();
for (var i = 0; i < eventObject.changedTouches.length; ++i) {
var touchPoint = eventObject.changedTouches[i];
var touchPointId = touchPoint.identifier + 2; // Just to not override mouse id
touchPoint.pointerId = touchPointId;
touchPoint.pointerType = POINTER_TYPE_TOUCH;
touchPoint.currentTarget = eventObject.currentTarget;
if (eventObject.preventDefault !== undefined) {
touchPoint.preventDefault = function () {
eventObject.preventDefault();
};
}
generateTouchClonedEvent(touchPoint, name);
}
};
var getMouseEquivalentEventName = function (eventName) {
return eventName.toLowerCase().replace("pointer", "mouse");
};
var getPrefixEventName = function (item, prefix, eventName) {
var newEventName;
if (eventName == eventName.toLowerCase()) {
var indexOfUpperCase = supportedEventsNames.indexOf(eventName) - (supportedEventsNames.length / 2);
newEventName = prefix + supportedEventsNames[indexOfUpperCase];
}
else {
newEventName = prefix + eventName;
}
// Fallback to PointerOver if PointerEnter is not currently supported
if (newEventName === prefix + "PointerEnter" && item["on" + prefix.toLowerCase() + "pointerenter"] === undefined) {
newEventName = prefix + "PointerOver";
}
// Fallback to PointerOut if PointerLeave is not currently supported
if (newEventName === prefix + "PointerLeave" && item["on" + prefix.toLowerCase() + "pointerleave"] === undefined) {
newEventName = prefix + "PointerOut";
}
return newEventName;
};
var registerOrUnregisterEvent = function (item, name, func, enable) {
if (enable) {
item.addEventListener(name, func, false);
} else {
item.removeEventListener(name, func);
}
};
var setTouchAware = function (item, eventName, enable) {
// If item is already touch aware, do nothing
if (item.onpointerdown !== undefined) {
return;
}
// IE 10
if (item.onmspointerdown !== undefined) {
var msEventName = getPrefixEventName(item, "MS", eventName);
registerOrUnregisterEvent(item, msEventName, function (evt) { generateTouchClonedEvent(evt, eventName); }, enable);
// We can return because MSPointerXXX integrate mouse support
return;
}
// Chrome, Firefox
if (item.ontouchstart !== undefined) {
switch (eventName.toLowerCase()) {
case "pointerdown":
registerOrUnregisterEvent(item, "touchstart", function (evt) { handleOtherEvent(evt, eventName); }, enable);
break;
case "pointermove":
registerOrUnregisterEvent(item, "touchmove", function (evt) { handleOtherEvent(evt, eventName); }, enable);
break;
case "pointerup":
registerOrUnregisterEvent(item, "touchend", function (evt) { handleOtherEvent(evt, eventName); }, enable);
break;
case "pointercancel":
registerOrUnregisterEvent(item, "touchcancel", function (evt) { handleOtherEvent(evt, eventName); }, enable);
break;
}
}
// Fallback to mouse
switch (eventName.toLowerCase()) {
case "pointerdown":
registerOrUnregisterEvent(item, "mousedown", function (evt) { generateMouseProxy(evt, eventName); }, enable);
break;
case "pointermove":
registerOrUnregisterEvent(item, "mousemove", function (evt) { generateMouseProxy(evt, eventName); }, enable);
break;
case "pointerup":
registerOrUnregisterEvent(item, "mouseup", function (evt) { generateMouseProxy(evt, eventName); }, enable);
break;
case "pointerover":
registerOrUnregisterEvent(item, "mouseover", function (evt) { generateMouseProxy(evt, eventName); }, enable);
break;
case "pointerout":
registerOrUnregisterEvent(item, "mouseout", function (evt) { generateMouseProxy(evt, eventName); }, enable);
break;
case "pointerenter":
if (item.onmouseenter === undefined) { // Fallback to mouseover
registerOrUnregisterEvent(item, "mouseover", function (evt) { generateMouseProxy(evt, eventName); }, enable);
} else {
registerOrUnregisterEvent(item, "mouseenter", function (evt) { generateMouseProxy(evt, eventName); }, enable);
}
break;
case "pointerleave":
if (item.onmouseleave === undefined) { // Fallback to mouseout
registerOrUnregisterEvent(item, "mouseout", function (evt) { generateMouseProxy(evt, eventName); }, enable);
} else {
registerOrUnregisterEvent(item, "mouseleave", function (evt) { generateMouseProxy(evt, eventName); }, enable);
}
break;
}
};
// Intercept addEventListener calls by changing the prototype
var interceptAddEventListener = function (root) {
var current = root.prototype ? root.prototype.addEventListener : root.addEventListener;
var customAddEventListener = function (name, func, capture) {
// Branch when a PointerXXX is used
if (supportedEventsNames.indexOf(name) != -1) {
setTouchAware(this, name, true);
}
if (current === undefined) {
this.attachEvent("on" + getMouseEquivalentEventName(name), func);
} else {
current.call(this, name, func, capture);
}
};
if (root.prototype) {
root.prototype.addEventListener = customAddEventListener;
} else {
root.addEventListener = customAddEventListener;
}
};
// Intercept removeEventListener calls by changing the prototype
var interceptRemoveEventListener = function (root) {
var current = root.prototype ? root.prototype.removeEventListener : root.removeEventListener;
var customRemoveEventListener = function (name, func, capture) {
// Release when a PointerXXX is used
if (supportedEventsNames.indexOf(name) != -1) {
setTouchAware(this, name, false);
}
if (current === undefined) {
this.detachEvent(getMouseEquivalentEventName(name), func);
} else {
current.call(this, name, func, capture);
}
};
if (root.prototype) {
root.prototype.removeEventListener = customRemoveEventListener;
} else {
root.removeEventListener = customRemoveEventListener;
}
};
// Hooks
interceptAddEventListener(document);
interceptAddEventListener(HTMLBodyElement);
interceptAddEventListener(HTMLDivElement);
interceptAddEventListener(HTMLImageElement);
interceptAddEventListener(HTMLSpanElement);
interceptAddEventListener(HTMLUListElement);
interceptAddEventListener(HTMLAnchorElement);
interceptAddEventListener(HTMLLIElement);
if (window.HTMLCanvasElement) {
interceptAddEventListener(HTMLCanvasElement);
}
if (window.SVGElement) {
interceptAddEventListener(SVGElement);
}
interceptRemoveEventListener(document);
interceptRemoveEventListener(HTMLBodyElement);
interceptRemoveEventListener(HTMLDivElement);
interceptRemoveEventListener(HTMLImageElement);
interceptRemoveEventListener(HTMLSpanElement);
interceptRemoveEventListener(HTMLUListElement);
interceptRemoveEventListener(HTMLAnchorElement);
interceptRemoveEventListener(HTMLLIElement);
if (window.HTMLCanvasElement) {
interceptRemoveEventListener(HTMLCanvasElement);
}
if (window.SVGElement) {
interceptRemoveEventListener(SVGElement);
}
// Extension to navigator
if (navigator.pointerEnabled === undefined) {
// Indicates if the browser will fire pointer events for pointing input
navigator.pointerEnabled = true;
// IE
if (navigator.msPointerEnabled) {
navigator.maxTouchPoints = navigator.msMaxTouchPoints;
}
}
// Handling touch-action css rule
if (document.styleSheets && document.addEventListener) {
document.addEventListener("DOMContentLoaded", function () {
var trim = function (string) {
return string.replace(/^\s+|\s+$/, '');
};
var processStylesheet = function (unfilteredSheet) {
var globalRegex = new RegExp(".+?{.*?}", "m");
var selectorRegex = new RegExp(".+?{", "m");
while (unfilteredSheet != "") {
var block = globalRegex.exec(unfilteredSheet)[0];
unfilteredSheet = trim(unfilteredSheet.replace(block, ""));
var selectorText = trim(selectorRegex.exec(block)[0].replace("{", ""));
// Checking if the user wanted to deactivate the default behavior
if (block.replace(/\s/g, "").indexOf("touch-action:none") != -1) {
var elements = document.querySelectorAll(selectorText);
for (var elementIndex = 0; elementIndex < elements.length; elementIndex++) {
var element = elements[elementIndex];
if (element.style.msTouchAction !== undefined) {
element.style.msTouchAction = "none";
}
else {
element.handjs_forcePreventDefault = true;
}
}
}
}
}; // Looking for touch-action in referenced stylesheets
try {
for (var index = 0; index < document.styleSheets.length; index++) {
var sheet = document.styleSheets[index];
if (sheet.href == undefined) { // it is an inline style
continue;
}
// Loading the original stylesheet
var xhr = new XMLHttpRequest();
xhr.open("get", sheet.href, false);
xhr.send();
var unfilteredSheet = xhr.responseText.replace(/(\n|\r)/g, "");
processStylesheet(unfilteredSheet);
}
} catch (e) {
// Silently fail...
}
// Looking for touch-action in inline styles
var styles = document.getElementsByTagName("style");
for (var index = 0; index < styles.length; index++) {
var inlineSheet = styles[index];
var inlineUnfilteredSheet = trim(inlineSheet.innerHTML.replace(/(\n|\r)/g, ""));
processStylesheet(inlineUnfilteredSheet);
}
}, false);
}
})();
</script>
<script>
%code%
</script>
</body>
</html>
.card {
background-color: blue;
border: 1px solid black;
width: 100px;
height: 50px;
display: inline-block;
margin: 10px;
cursor: hand;
touch-action: none;
}
.card.moving {
opacity: 0.7;
pointer-events: none;
}
.icon {
background-color: green;
width: 40px;
height: 40px;
margin: 5px;
box-sizing: border-box;
padding: 10px;
}
.circle {
background-color: yellow;
width: 50px;
height: 50px;
border: 1px solid black;
display: inline-block;
margin: 20px;
border-radius: 25px;
}
.circle.open {
background-color: orange;
width: 70px;
height: 70px;
margin: 10px;
border-radius: 35px;
}
var usePE = !!navigator.pointerEnabled;
var eventPrefix = usePE ? 'pointer' : 'mouse';
var activeCards = {};
function onStart(e) {
var card = e.currentTarget;
activeCards[usePE ? e.pointerId : 1] = card;
if (!card.classList.contains('moving'))
{
card.classList.add('moving');
card.startX = e.clientX;
card.startY = e.clientY;
}
e.preventDefault();
}
function onMove(e) {
var card = activeCards[usePE ? e.pointerId : 1];
if (card) {
card.style.webkitTransform = 'translate(' +
(e.clientX - card.startX) + 'px, ' +
(e.clientY - card.startY) + 'px)';
e.preventDefault();
}
}
function onEnd(e) {
var id = usePE ? e.pointerId : 1;
var card = activeCards[id];
if (card) {
card.classList.remove('moving');
card.style.webkitTransform = '';
e.preventDefault();
if (e.target.classList.contains('circle'))
removeCardFromCircle(card, e.target);
delete activeCards[id];
}
}
function onOver(e) {
var card = activeCards[usePE ? e.pointerId : 1];
if (card) {
addCardToCircle(card, e.currentTarget);
}
}
function onOut(e) {
var card = activeCards[usePE ? e.pointerId : 1];
if (card) {
removeCardFromCircle(card, e.currentTarget);
}
}
function addCardToCircle(card, circle)
{
if (!circle.cardsOver)
circle.classList.add('open');
circle.cardsOver++;
}
function removeCardFromCircle(card, circle)
{
circle.cardsOver--;
if (!circle.cardsOver)
circle.classList.remove('open');
}
var cards = document.querySelectorAll('.card');
for(var i = 0; i < cards.length; i++) {
var c = cards[i];
c.addEventListener(eventPrefix + 'down', onStart, false);
}
var circles = document.querySelectorAll('.circle');
for(var i = 0; i < circles.length; i++) {
var c = circles[i];
c.addEventListener(eventPrefix + 'over', onOver, false);
c.addEventListener(eventPrefix + 'out', onOut, false);
c.cardsOver = 0;
}
document.body.addEventListener(eventPrefix + 'move', onMove, false);
document.body.addEventListener(eventPrefix + 'up', onEnd, false);
if (usePE)
document.body.addEventListener(eventPrefix + 'cancel', onEnd, false);
document.body.addEventListener(eventPrefix + 'out', function(e) {
if (e.relatedTarget == document.documentElement)
onEnd(e);
}, false);
Output
300px
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |