Related
Good day all, I want to apply swipe to my loaded epub. I used javascript to load the epub into a div, I want to be able to do swipe left and swipe right on the div.
I got a jquery script to do that, but the challenge is that, it doesn't swipe on the div, it swipes when I do mouse swipe left or right on other controls on the page, such as input control and select control.
But on the main div with epub, it doesn't swipe or the swipe doesn't get called.
Thanks for your help
Edit: After checking the developer tools side, I noticed that after the epub is loaded in the area div, it is loaded into an iframe, i've been trying to target the iframe, for touchmove event, but not yet successful.
Below is the developer inspect code, thanks.
<div id="area" class="swipe-detector">
<iframe id="epubjs-iframe:2dc3e713-d94a-4f98-a8d2-0ec974ed045b"
scrolling="no" height="100%" width="100%" style="border: none;
visibility: visible;">
#document
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
style="transform: translate(0px, 0px); overflow: hidden; width: auto;
height: 609px; column-fill: auto; column-width: 361px; column-gap:
44px;">
<head>
<base href="OEBPS/table-of-contents.html">
<title>Table Of Contents -- The Problems of Philosophy</title>
<meta http-equiv="Content-Type"
content="application/xhtml+xml; charset=utf-8">
<meta name="EPB-UUID" content="">
<link rel="stylesheet" href="blob:null/64628d08-cd14-43f0-
93cf-4589c1eb1f9b" type="text/css">
</head>
<body style="margin: 0px;">
...
</body>
</html>
</iframe>
</div>
Please check my original code below:
<body class="swipe-detector">
<div id="reader">
<input type="file" id="bookChooser">
<select id="toc"></select>
<div id="area" class="swipe-detector"></div>
<button id="prev" type="button"><</button>
<button id="next" type="button">></button>
</div>
<script>
(function($) {
$.fn.swipeDetector = function(options) {
// States: 0 - no swipe, 1 - swipe started, 2 - swipe released
var swipeState = 0;
// Coordinates when swipe started
var startX = 0;
var startY = 0;
// Distance of swipe
var pixelOffsetX = 0;
var pixelOffsetY = 0;
// Target element which should detect swipes.
var swipeTarget = this;
var defaultSettings = {
// Amount of pixels, when swipe don't count.
swipeThreshold: 70,
// Flag that indicates that plugin should react only on touch events.
// Not on mouse events too.
useOnlyTouch: false
};
// Initializer
(function init() {
options = $.extend(defaultSettings, options);
// Support touch and mouse as well.
swipeTarget.on("mousedown touchstart", swipeStart);
$("html").on("mouseup touchend", swipeEnd);
$("html").on("mousemove touchmove", swiping);
})();
function swipeStart(event) {
if (options.useOnlyTouch && !event.originalEvent.touches) return;
if (event.originalEvent.touches) event = event.originalEvent.touches[0];
if (swipeState === 0) {
swipeState = 1;
startX = event.clientX;
startY = event.clientY;
}
}
function swipeEnd(event) {
if (swipeState === 2) {
swipeState = 0;
if (
Math.abs(pixelOffsetX) > Math.abs(pixelOffsetY) &&
Math.abs(pixelOffsetX) > options.swipeThreshold
) {
// Horizontal Swipe
if (pixelOffsetX < 0) {
swipeTarget.trigger($.Event("swipeLeft.sd"));
console.log("Left");
} else {
swipeTarget.trigger($.Event("swipeRight.sd"));
console.log("Right");
}
} else if (Math.abs(pixelOffsetY) > options.swipeThreshold) {
// Vertical swipe
if (pixelOffsetY < 0) {
swipeTarget.trigger($.Event("swipeUp.sd"));
console.log("Up");
} else {
swipeTarget.trigger($.Event("swipeDown.sd"));
console.log("Down");
}
}
}
}
function swiping(event) {
// If swipe don't occuring, do nothing.
if (swipeState !== 1) return;
if (event.originalEvent.touches) {
event = event.originalEvent.touches[0];
}
var swipeOffsetX = event.clientX - startX;
var swipeOffsetY = event.clientY - startY;
if (
Math.abs(swipeOffsetX) > options.swipeThreshold ||
Math.abs(swipeOffsetY) > options.swipeThreshold
) {
swipeState = 2;
pixelOffsetX = swipeOffsetX;
pixelOffsetY = swipeOffsetY;
}
}
return swipeTarget; // Return element available for chaining.
};
})(jQuery);
// Showcase
$("document").ready(function() {
$(".swipe-detector")
.swipeDetector()
.on("swipeLeft.sd swipeRight.sd swipeUp.sd swipeDown.sd", function(event) {
if (event.type === "swipeLeft") {
console.log("Swipe Left");
} else if (event.type === "swipeRight") {
console.log("Swipe Right");
} else if (event.type === "swipeUp") {
console.log("Swipe Up");
} else if (event.type === "swipeDown") {
console.log("Swipe Down");
}
});
});
</script>
</body>
So the hack is to place a div over the area div, then swipe over that to perform the previous and next moves.
The solution is at this link:
How to cover a div with another div as overlay
I want to tilt an item being dragged to show distinction. I have basic drag and drop fiddle here https://jsfiddle.net/igaurav/bqprc9p8/3/
My javascript looks like:
"use strict";
var source = null;
function listItemDragStartHandler(event){
// What is true there for?
source = event.currentTarget;
event.dataTransfer.setData("text/plain", event.currentTarget.innerHTML);
event.currentTarget.style.transform = 'rotate(15deg)';
event.dataTransfer.effectAllowed = "move";
}
function dragoverHandler(event) {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
}
function dropHandler(event) {
event.preventDefault();
event.stopPropagation();
var currentElement = event.currentTarget;
var listContainer = currentElement.parentNode;
listContainer.insertBefore(source, currentElement);
source.style.transform = 'rotate(0deg)';
}
function delete_item(event) {
var currentTarget = event.currentTarget;
var grandParentOfDelete = currentTarget.parentNode.parentNode;
grandParentOfDelete.remove();
}
function add_item() {
var item_text_node = document.getElementsByName("add-item-text")[0]
var item_text = item_text_node.value;
if (item_text.length > 0) {
var item_template = document.getElementById("item-template");
var item_clone = item_template.cloneNode(true);
item_clone.removeAttribute("id");
var clone_text = item_clone.getElementsByClassName("item-text")[0];
clone_text.textContent = item_text;
// reset the value
item_text_node.value = "";
var item_list = document.getElementById("item-list");
item_list.appendChild(item_clone);
} else {
alert("No text?? Add some text!");
}
}
function add_item_listener() {
var add_item_button = document.getElementById("add-item");
add_item_button.addEventListener("click", add_item);
}
function sample_data() {
for(var i=0;i<10;i++){
var item_text_node = document.getElementsByName('add-item-text')[0]
item_text_node.value = i;
add_item();
}
}
function init_app() {
add_item_listener();
sample_data();
}
window.onload = function () {
init_app()
}
Relevant HTML is:
<body>
<div id="container">
<div id="add-item-div">
<input type="text" name="add-item-text">
<button id="add-item">Add Item</button>
</div>
<div id="item-list">
</div>
<div id="item-template" class="item-list-element" draggable="true" ondragstart="listItemDragStartHandler(event);" ondrop="dropHandler(event);" ondragover="dragoverHandler(event);">
<div class="item-text"></div>
<div class="delete-item-div">
<button class="delete-item" onclick="delete_item(event);">Delete Item</button>
</div>
</div>
</div>
</body>
I am applying the transform on line 9 but the drag operation stops and drag doesn't start.
What am I doing wrong?
P.S: I don't want to use libraries.
Basically, we can use setDragImage for replacing the browser implementation of the ghost image it is rendering for the dragging operation, with one of our own.
We use a cloned node as you do in the fiddle, and we add it to the dom.
Then, you can do the transformation on an inner container in the cloned element.
document.getElementById("drag-with-create-add").addEventListener("dragstart", function(e) {
var crt = this.cloneNode(true);
crt.style.position = "absolute";
crt.style.top = "0px";
crt.style.left = "-100px";
var inner = crt.getElementsByClassName("inner")[0];
inner.style.backgroundColor = "orange";
inner.style.transform = "rotate(20deg)";
document.body.appendChild(crt);
e.dataTransfer.setDragImage(crt, 20, 20);
}, false);
<div id="drag-with-create-add" class="dragdemo" draggable="true">
<div class="inner">
drag me
</div>
</div>
This is partly based on an helpfull article by Stuart Langridge.
Note: Trick here is to apply transformation on inner element and not on the one which has drag events. Transformation would appear only if applied to inner elements.
I just created a sample fiddle. Which uses the setDragImage method, Please have a look and comment your needs so that we can upate the answer.
I have added a ghost image and added 'shake' feel while drag start. You can similarly rotate the dragging ghost image via css.
Also note, we are cloning the image while dragging to show ghost image. So we need to manually remove that from dom.
document.addEventListener("dragstart", function(e) {
var img = document.createElement("img");
img.src = "http://i.imgur.com/BDcvqmf.jpg";
e.dataTransfer.setDragImage(img, 5000, 5000); //5000 will be out of the window
drag(e)
}, false);
var crt, dragX, dragY;
//document.addEventListener('drag',drag)
function drag(ev) {
crt = ev.target.cloneNode(true);
crt.className = "face";
crt.style.position = "absolute";
crt.style.opacity = "0.9";
document.body.appendChild(crt);
ev.dataTransfer.setData("text", ev.target.id);
}
document.addEventListener("dragover", function(ev) {
ev = ev || window.event;
dragX = ev.pageX;
dragY = ev.pageY;
crt.style.left = dragX + "px";
crt.style.top = dragY + "px";
}, false);
document.addEventListener("dragend", function(event) {
crt.style.display = 'none';
crt.remove()
});
body {
padding: 10px
}
#draggable-element {
width: 100px;
height: 100px;
background-color: #666;
color: white;
padding: 10px 12px;
cursor: move;
position: relative;
/* important (all position that's not `static`) */
}
.face {
animation: shake 1s cubic-bezier(.36, .07, .19, .97) both;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000px;
}
#keyframes shake {
10%,
90% {
transform: translate3d(-1px, 0, 0);
}
20%,
80% {
transform: translate3d(4px, 0, 0);
}
30%,
50%,
70% {
transform: translate3d(-5px, 0, 0);
}
40%,
60% {
transform: translate3d(5px, 0, 0);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
See the tilt in action on drag start. <br>
<img draggable id="draggable-element" src="http://i.imgur.com/BDcvqmf.jpg">
Hello stack overflow community!
I am writing a script to make simple drag and drop able document maker. i wrote all my current code by myself, but i had no clue how to enable dragging of desktop files to the document. here is the code i have written so far(sorry for the messedup indenting, the codeblock feature messed it up):
<html>
<head>
<style>
#main { position:absolute; top:10px; left:50%; margin-left:-450px; width:900px; height:900px; border:dashed 1px lightgrey; }
#trash { margin-top:10px; width:200px; height:200px; border:dotted 4px black; position:absolute; top:0px; left:5px; }
#new { width:200px; height:200px; border:dotted 5px black; }
.new { float:right; }
.drag { float:left; resize:both; overflow:hidden; height:110px; width:110px; }
.square { background-color:none; width:10px; height:10px; float:left; }
.drag * { resize:none; width:100px; height:100px }
.block { background-color:red; }
</style>
<script src="jq/jquery-1.9.1.js"></script>
<script src="jq/jquery-ui.js"></script>
<script>
var blockcolors = ["red","blue","green","yellow","white","black","lightgrey","lightblue"];
var color = 1;
var id = 2;
function drag(ev)
{
ev.dataTransfer.setData("Text",ev.target.id);
}
function drop(ev)
{
ev.preventDefault();
ev.target.appendChild(document.getElementById(ev.dataTransfer.getData("Text")));
}
function droptrash(ev)
{
ev.preventDefault();
$("#"+ev.dataTransfer.getData("Text")).remove();
}
function change(div)
{
var divw=parseInt(div.style.width);
var divh=parseInt(div.style.height);
var imgw=divw-10;
var imgh=divh-10;
div.children[0].style.width=imgw;
div.children[0].style.height=imgh;
div.style.border="dotted 3px grey";
}
function makeGrid()
{
var main = $("#main");
for (var i = 0; i < 8100; i++)
{
var square = document.createElement('div');
square.setAttribute('class', 'square');
square.ondragover = function(event) { event.preventDefault(); };
square.ondrop = function(event) { drop(event); };
main.prepend(square);
}
}
function additem() {
var newbox = document.getElementById("new");
newbox.innerHTML = '<div id="div'+id+'" class="drag" onmouseout="this.style.border=0" draggable="true" ' +
'ondragstart="drag(event)" onmousemove="change(this)"></div>';
div = document.getElementById("div"+id);
div.innerHTML = $("#newtype").val();
id+=1;
}
function blockcolor(block) {
block.style.backgroundColor = blockcolors[color];
if (color == blockcolors.length-1)
color = 0;
color++;
}
</script>
</head>
<body onload="makeGrid()" class="new">
<div id="new" class="new"></div>
<div style="clear:both"></div>
<select id="newtype" class="new">
<option value="<img draggable='false' src='glider.jpg'/>">Image</option>
<option value="<textarea></textarea>">Text box</option>
<option value="<div onclick='blockcolor(this)' class='block'></div>">Block</option>
</select>
<button onclick="additem()" class="new">add an item</button>
<div style="clear:both"></div>
<center>
<div style="clear:both"></div>
<div ID="main" overflow="auto">
</div>
</center>
<div style="clear:both"></div>
<div id="trash" ondragover="event.preventDefault()" ondrop="droptrash(event)" overflow="auto"><big>Trash</big></div>
</body>
</html>
this works, but it has no functionality for dragging in files. i wanted to add this code i found online which works by itself:
<html>
<head>
<style>
#drop { min-height: 150px; width: 250px; border: 1px solid blue; margin: 10px; padding: 10px; }
</style>
<script>
if(window.FileReader) {
var drop;
addEventHandler(window, 'load', function() {
drop = document.getElementById('drop');
var list = document.getElementById('list');
function cancel(e) {
if (e.preventDefault) { e.preventDefault(); }
return false;
}
// Tells the browser that we *can* drop on this target
addEventHandler(drop, 'dragover', cancel);
addEventHandler(drop, 'dragenter', cancel);
addEventHandler(drop, 'drop', function (e) {
e = e || window.event; // get window.event if e argument missing (in IE)
if (e.preventDefault) { e.preventDefault(); } // stops the browser from redirecting off to the image.
var dt = e.dataTransfer;
var files = dt.files;
for (var i=0; i<files.length; i++) {
var file = files[i];
var reader = new FileReader();
//attach event handlers here...
reader.readAsDataURL(file);
addEventHandler(reader, 'loadend', function(e, file) {
var bin = this.result;
var img = document.createElement("img");
img.file = file;
img.src = bin;
drop.innerHTML = "";
drop.appendChild(img);
}.bindToEventHandler(file));
}
return false;
});
Function.prototype.bindToEventHandler = function bindToEventHandler() {
var handler = this;
var boundParameters = Array.prototype.slice.call(arguments);
//create closure
return function(e) {
e = e || window.event; // get window.event if e argument missing (in IE)
boundParameters.unshift(e);
handler.apply(this, boundParameters);
}
};
});
} else {
document.getElementById('status').innerHTML = 'Your browser does not support the HTML5 FileReader.';
}
function addEventHandler(obj, evt, handler) {
if(obj.addEventListener) {
// W3C method
obj.addEventListener(evt, handler, false);
} else if(obj.attachEvent) {
// IE method.
obj.attachEvent('on'+evt, handler);
} else {
// Old school method.
obj['on'+evt] = handler;
}
}
</script>
</head>
<body>
<DIV id="drop"></DIV>
<DIV id="list"></DIV>
</body>
</html>
my problem is combining them. the second script messes up all drag and drop functionality for the whole thing. i would much appreciate any help.
there is some minor change in the file drop script. Just rename the variable "drop" to "dropElement"( or any other name then "drop"), as it was conflicting with the function name "drop" in first script. so here is your second script.
<script>
if(window.FileReader) {
var dropElement;
addEventHandler(window, 'load', function() {
dropElement = document.getElementById('drop');
var list = document.getElementById('list');
function cancel(e) {
if (e.preventDefault) { e.preventDefault(); }
return false;
}
// Tells the browser that we *can* drop on this target
addEventHandler(dropElement, 'dragover', cancel);
addEventHandler(dropElement, 'dragenter', cancel);
addEventHandler(dropElement, 'drop', function (e) {
e = e || window.event; // get window.event if e argument missing (in IE)
if (e.preventDefault) { e.preventDefault(); } // stops the browser from redirecting off to the image.
var dt = e.dataTransfer;
var files = dt.files;
for (var i=0; i<files.length; i++) {
var file = files[i];
var reader = new FileReader();
//attach event handlers here...
reader.readAsDataURL(file);
addEventHandler(reader, 'loadend', function(e, file) {
var bin = this.result;
var img = document.createElement("img");
img.file = file;
img.src = bin;
dropElement.innerHTML = "";
dropElement.appendChild(img);
}.bindToEventHandler(file));
}
return false;
});
Function.prototype.bindToEventHandler = function bindToEventHandler() {
var handler = this;
var boundParameters = Array.prototype.slice.call(arguments);
//create closure
return function(e) {
e = e || window.event; // get window.event if e argument missing (in IE)
boundParameters.unshift(e);
handler.apply(this, boundParameters);
}
};
});
} else {
document.getElementById('status').innerHTML = 'Your browser does not support the HTML5 FileReader.';
}
function addEventHandler(obj, evt, handler) {
if(obj.addEventListener) {
// W3C method
obj.addEventListener(evt, handler, false);
} else if(obj.attachEvent) {
// IE method.
obj.attachEvent('on'+evt, handler);
} else {
// Old school method.
obj['on'+evt] = handler;
}
}
</script>
I am trying this code but i get: document.getElementsByName(...).style is undefined
I have also a problem with the delegation, i think. Any help?
<html>
<head>
<style type="text/css">
#toolTip {
position:relative;
width:200px;
margin-top:-90px;
}
#toolTip p {
padding:10px;
background-color:#f9f9f9;
border:solid 1px #a0c7ff;
-moz-border-radius:5px;-ie-border-radius:5px;-webkit-border-radius:5px;-o-border-radius:5px;border-radius:5px;
}
#tailShadow {
position:absolute;
bottom:-8px;
left:28px;
width:0;height:0;
border:solid 2px #fff;
box-shadow:0 0 10px 1px #555;
}
#tail1 {
position:absolute;
bottom:-20px;
left:20px;
width:0;height:0;
border-color:#a0c7ff transparent transparent transparent;
border-width:10px;
border-style:solid;
}
#tail2 {
position:absolute;
bottom:-18px;
left:20px;
width:0;height:0;
border-color:#f9f9f9 transparent transparent transparent;
border-width:10px;
border-style:solid;
}
</style>
<script type='text/javascript'>
function load () {
var elements = document.getElementsByName('toolTip');
for(var i=0; i<elements.length; i++) {
document.getElementsByName(elements[i]).style.visibility = 'hidden';
}
}
</script>
</head>
<body onload="load()">
<br><br><br><br><br><br><br><br><br><br><br><br>
<a class="hd"
onMouseOver="document.getElementsByName('toolTip')[0].style.visibility = 'visible'"
onmouseout ="document.getElementsByName('toolTip')[0].style.visibility = 'hidden'">aqui</a>
<div id="toolTip" name="toolTip">
<p>i can haz css tooltip</p>
<div id="tailShadow"></div>
<div id="tail1"></div>
<div id="tail2"></div>
</div>
<br><br><br>
<a class="hd"
onMouseOver="document.getElementsByName('toolTip')[0].style.visibility = 'visible'"
onmouseout ="document.getElementsByName('toolTip')[0].style.visibility = 'hidden'">aqui</a>
<div id="toolTip" name="toolTip">
<p>i can haz css tooltip</p>
<div id="tailShadow"></div>
<div id="tail1"></div>
<div id="tail2"></div>
</div>
</body>
</html>
demo
Try changing the id toolTip to a class:
<div class="toolTip">...</div>
And change your JS to use the display style-thing, rather than visibility, nd the onmouseover's are best dealt with using JS event delegation:
function load()
{
var i, tooltips = document.getElementsByClassName('toolTip'),
mouseOver = function(e)
{//handler for mouseover
e = e || window.event;
var i, target = e.target || e.srcElement,
targetToolTip = target.nextElementSibling || nextSibling;//gets the next element in DOM (ie the tooltip)
//check if mouse is over a relevant element:
if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/))
{//nope? stop here, then
return e;
}
targetToolTip.style.display = 'block';//make visible
for (i=0;i<tooltips.length;i++)
{//closures are neat --> you have a reference to all tooltip elements from load scope
if (tooltips[i] !== targetToolTip)
{//not the one you need to see
tooltips[i].style.display = 'none';
}
}
};
for (i=0;i<tooltips.length;i++)
{
tooltips[i].style.display = 'none';
}
//add listener:
if (document.body.addEventListener)
{//IE > 9, chrome, safari, FF...
document.body.addEventListener('mouseover',mouseOver,false);
}
else
{//IE8
document.body.attachEvent('onmouseover',mouseOver);
}
}
Google JavaScript event delegation and closures if this code isn't clear, but that's just how I would tackle this kind of thing. IMO, it's fairly efficient (you could use the closure scope to keep track of the tooltip that's currently visible and not loop through all of them, too, that would be even better:
function load()
{
var i, tooltips = document.getElementsByClassName('toolTip'),
currentToolTip,//<-- reference currently visible
mouseOver = function(e)
{
e = e || window.event;
var i, target = e.target || e.srcElement,
targetToolTip = target.nextElementSibling || nextSibling;
if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/) || targetToolTip === currentToolTip)
{//add check for currently visible TT, if so, no further action required
return e;
}
if (currentToolTip !== undefined)
{
currentToolTip.style.display = 'none';//hide currently visible
}
targetToolTip.style.display = 'block';//make new visible
currentToolTip = targetToolTip;//keep reference for next event
};
for (i=0;i<tooltips.length;i++)
{
tooltips[i].style.display = 'none';
}
if (document.body.addEventListener)
{
document.body.addEventListener('mouseover',mouseOver,false);
}
else
{
document.body.attachEvent('onmouseover',mouseOver);
}
}
And you're there.
Edit:
To hide the tooltip on mouseout, you can either add a second listener directly:
function load()
{
var i, tooltips = document.getElementsByClassName('toolTip'),
currentToolTip,//<-- reference currently visible
mouseOver = function(e)
{
e = e || window.event;
var i, target = e.target || e.srcElement,
targetToolTip = target.nextElementSibling || nextSibling;
if (target.tagName.toLowerCase() !== 'a' || !target.className.match(/\bhd\b/) || targetToolTip === currentToolTip)
{//add check for currently visible TT, if so, no further action required
return e;
}
if (currentToolTip !== undefined)
{
currentToolTip.style.display = 'none';//hide currently visible
}
targetToolTip.style.display = 'block';//make new visible
currentToolTip = targetToolTip;//keep reference for next event
},
mouseOut = function(e)
{
e = e || window.event;
var movedTo = document.elementFromPoint(e.clientX,e.clientY);//check where the cursor is NOW
if (movedTo === curentToolTip || currentToolTip === undefined)
{//if cursor moved to tooltip, don't hide it, if nothing is visible, stop
return e;
}
currentTooltip.style.display = 'none';
currentTooltip = undefined;//no currentToolTip anymore
};
for (i=0;i<tooltips.length;i++)
{
tooltips[i].style.display = 'none';
}
if (document.body.addEventListener)
{
document.body.addEventListener('mouseover',mouseOver,false);
document.body.addEventListener('mouseout',mouseOut,false);
}
else
{
document.body.attachEvent('onmouseover',mouseOver);
document.body.attachEvent('onmouseout',mouseOut);
}
}
Note, this is completely untested. I'm not entirely sure if IE < 9 supports elementFromPoint (gets the DOM element that is rendered at certain coordinates), or even if the IE event object has the clientX and clientY properties, but I figure a quick google will tell you more, including how to get the coordinates and the element that is to be found under the cursor in old, crummy, ghastly IE8, but this should help you on your way. Of course, if you don't want the contents of the tooltip to be selectable, just change the mouseOut function to:
mouseOut = function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (currentToolTip)
{
currentToolTip.style.diplay = 'none';
currentToolTip = undefined;
}
};
No need to check if the mouseout was on the correct element, just check if there is a current tooltip, and hide it.
Try using classes to mark the tooltips:
<div id="toolTip1" class="toolTip">
<p>i can haz css tooltip</p>
<div id="tailShadow"></div>
<div id="tail1"></div>
<div id="tail2"></div>
</div>
And JQuery to toggle the visibility using the class as the selector:
$('.toolTip').attr('visibility', 'hidden')
Definitely clean up the non-unique Id's - this will cause you no end of troubles otherwise
Your problem is likely because you're using the same id for both the tooltips. This is invalid; an id should be unique -- only one element in a given page should have a specific ID.
If you need a shared identifier for multiple objects, use a class instead.
I built a tooltip with a border in pure js that doesn't use hover.
html
<div id="infoId" class='info' style="font-variant:small-caps;text-align:center;padding-top:10px;">
<span id="innerspanid">
</span>
</div>
</div>
<input id="startbtn" class="getstartedbtn" type="button" value="Start >" />
</div>
js
function getTextWidth(text, font) {
// re-use canvas object for better performance
const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
const context = canvas.getContext("2d");
context.font = font;
const metrics = context.measureText(text);
return metrics.width;
}
function getCssStyle(element, prop) {
return window.getComputedStyle(element, null).getPropertyValue(prop);
}
function getCanvasFontSize(el = document.body) {
const fontWeight = getCssStyle(el, 'font-weight') || 'normal';
const fontSize = getCssStyle(el, 'font-size') || '16px';
const fontFamily = getCssStyle(el, 'font-family') || 'Times New Roman';
return `${fontWeight} ${fontSize} ${fontFamily}`;
}
let arrowDimensionWidth = 20;
let arrowDimensionHeight = 20;
let tooltipTextHorizontalMargin = 50;
function openTooltip(text) {
let innerSpan = document.getElementById("innerspanid");
innerSpan.innerHTML = text;
let computedW = getTextWidth(text, getCanvasFontSize(innerSpan)) + tooltipTextHorizontalMargin;
let pointer = document.getElementById('pointer')
pointer.style.right = (((computedW / 2) - (arrowDimensionWidth / 2)) - (0)) + 'px';
let elem = document.getElementById('tooltipHost').parentNode.querySelector('div.info_container');
elem.style.left = ((tooltipHost.getBoundingClientRect().width - computedW) / 2) + "px";
elem.style.width = computedW + "px";
elem.style.display = 'block';
}
function buildTooltip() {
let elements = document.querySelectorAll('div.tooltip');
// Create a canvas element where the triangle will be drawn
let canvas = document.createElement('canvas');
canvas.width = arrowDimensionWidth; // arrow width
canvas.height = arrowDimensionHeight; // arrow height
let ctx = canvas.getContext('2d');
ctx.strokeStyle = 'darkred'; // Border color
ctx.fillStyle = 'white'; // background color
ctx.lineWidth = 1;
ctx.translate(-0.5, -0.5); // Move half pixel to make sharp lines
ctx.beginPath();
ctx.moveTo(1, canvas.height); // lower left corner
ctx.lineTo((canvas.width / 2), 1); // upper right corner
ctx.lineTo(canvas.width, canvas.height); // lower right corner
ctx.fill(); // fill the background
ctx.stroke(); // stroke it with border
ctx.fillRect(0, canvas.height - 0.5, canvas.width - 1, canvas.height + 2); //fix bottom row
// Create a div element where the triangle will be set as background
pointer = document.createElement('div');
pointer.id = "pointer"
pointer.style.width = canvas.width + 'px';
pointer.style.height = canvas.height + 'px';
pointer.innerHTML = ' ' // non breaking space
pointer.style.backgroundImage = 'url(' + canvas.toDataURL() + ')';
pointer.style.position = 'absolute';
pointer.style.top = '2px';
pointer.style.zIndex = '1'; // place it over the other elements
let idx;
let len;
for (idx = 0, len = elements.length; idx < len; ++idx) {
let elem = elements[idx];
let text = elem.querySelector('div.info');
let info = document.createElement('div');
text.parentNode.replaceChild(info, text);
info.className = 'info_container';
info.appendChild(pointer.cloneNode());
info.appendChild(text);
}
}
window.addEventListener('load', buildTooltip);
window.addEventListener('load', wireup);
function wireup() {
document.getElementById('startbtn').addEventListener('click', function (evt1) {
openTooltip("bad email no # sign");
return false;
});
}
css
div.tooltip {
position: relative;
display: inline-block;
}
div.tooltip > div.info {
display: none;
}
div.tooltip div.info_container {
position: absolute;
left: 0px;
width: 100px;
height: 70px;
display: none;
}
div.tooltip div.info {
position: absolute;
left: 0px;
text-align: left;
background-color: white;
font-size: 18px;
left: 1px;
right: 1px;
top: 20px;
bottom: 1px;
color: #000;
padding: 5px;
overflow: auto;
border: 1px solid darkred;
border-radius: 5px;
}
I use jQuery.click to handle the mouse click event on Raphael graph, meanwhile, I need to handle mouse drag event, mouse drag consists of mousedown, mouseupand mousemove in Raphael.
It is difficult to distinguish click and drag because click also contain mousedown & mouseup, How can I distinguish mouse "click" & mouse "drag" then in Javascript?
I think the difference is that there is a mousemove between mousedown and mouseup in a drag, but not in a click.
You can do something like this:
const element = document.createElement('div')
element.innerHTML = 'test'
document.body.appendChild(element)
let moved
let downListener = () => {
moved = false
}
element.addEventListener('mousedown', downListener)
let moveListener = () => {
moved = true
}
element.addEventListener('mousemove', moveListener)
let upListener = () => {
if (moved) {
console.log('moved')
} else {
console.log('not moved')
}
}
element.addEventListener('mouseup', upListener)
// release memory
element.removeEventListener('mousedown', downListener)
element.removeEventListener('mousemove', moveListener)
element.removeEventListener('mouseup', upListener)
All these solutions either break on tiny mouse movements, or are overcomplicated.
Here is a simple adaptable solution using two event listeners. Delta is the distance in pixels that you must move horizontally or vertically between the up and down events for the code to classify it as a drag rather than a click. This is because sometimes you will move the mouse or your finger a few pixels before lifting it.
const delta = 6;
let startX;
let startY;
element.addEventListener('mousedown', function (event) {
startX = event.pageX;
startY = event.pageY;
});
element.addEventListener('mouseup', function (event) {
const diffX = Math.abs(event.pageX - startX);
const diffY = Math.abs(event.pageY - startY);
if (diffX < delta && diffY < delta) {
// Click!
}
});
Cleaner ES2015
let drag = false;
document.addEventListener('mousedown', () => drag = false);
document.addEventListener('mousemove', () => drag = true);
document.addEventListener('mouseup', () => console.log(drag ? 'drag' : 'click'));
Didn't experience any bugs, as others comment.
In case you are already using jQuery:
var $body = $('body');
$body.on('mousedown', function (evt) {
$body.on('mouseup mousemove', function handler(evt) {
if (evt.type === 'mouseup') {
// click
} else {
// drag
}
$body.off('mouseup mousemove', handler);
});
});
This should work well. Similar to the accepted answer (though using jQuery), but the isDragging flag is only reset if the new mouse position differs from that on mousedown event. Unlike the accepted answer, that works on recent versions of Chrome, where mousemove is fired regardless of whether mouse was moved or not.
var isDragging = false;
var startingPos = [];
$(".selector")
.mousedown(function (evt) {
isDragging = false;
startingPos = [evt.pageX, evt.pageY];
})
.mousemove(function (evt) {
if (!(evt.pageX === startingPos[0] && evt.pageY === startingPos[1])) {
isDragging = true;
}
})
.mouseup(function () {
if (isDragging) {
console.log("Drag");
} else {
console.log("Click");
}
isDragging = false;
startingPos = [];
});
You may also adjust the coordinate check in mousemove if you want to add a little bit of tolerance (i.e. treat tiny movements as clicks, not drags).
If you feel like using Rxjs:
var element = document;
Rx.Observable
.merge(
Rx.Observable.fromEvent(element, 'mousedown').mapTo(0),
Rx.Observable.fromEvent(element, 'mousemove').mapTo(1)
)
.sample(Rx.Observable.fromEvent(element, 'mouseup'))
.subscribe(flag => {
console.clear();
console.log(flag ? "drag" : "click");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/#reactivex/rxjs#5.4.1/dist/global/Rx.js"></script>
This is a direct clone of what #wong2 did in his answer, but converted to RxJs.
Also interesting use of sample. The sample operator will take the latest value from the source (the merge of mousedown and mousemove) and emit it when the inner observable (mouseup) emits.
As mrjrdnthms points out in his comment on the accepted answer, this no longer works on Chrome (it always fires the mousemove), I've adapted Gustavo's answer (since I'm using jQuery) to address the Chrome behavior.
var currentPos = [];
$(document).on('mousedown', function (evt) {
currentPos = [evt.pageX, evt.pageY]
$(document).on('mousemove', function handler(evt) {
currentPos=[evt.pageX, evt.pageY];
$(document).off('mousemove', handler);
});
$(document).on('mouseup', function handler(evt) {
if([evt.pageX, evt.pageY].equals(currentPos))
console.log("Click")
else
console.log("Drag")
$(document).off('mouseup', handler);
});
});
The Array.prototype.equals function comes from this answer
Using jQuery with a 5 pixel x/y theshold to detect the drag:
var dragging = false;
$("body").on("mousedown", function(e) {
var x = e.screenX;
var y = e.screenY;
dragging = false;
$("body").on("mousemove", function(e) {
if (Math.abs(x - e.screenX) > 5 || Math.abs(y - e.screenY) > 5) {
dragging = true;
}
});
});
$("body").on("mouseup", function(e) {
$("body").off("mousemove");
console.log(dragging ? "drag" : "click");
});
You could do this:
var div = document.getElementById("div");
div.addEventListener("mousedown", function() {
window.addEventListener("mousemove", drag);
window.addEventListener("mouseup", lift);
var didDrag = false;
function drag() {
//when the person drags their mouse while holding the mouse button down
didDrag = true;
div.innerHTML = "drag"
}
function lift() {
//when the person lifts mouse
if (!didDrag) {
//if the person didn't drag
div.innerHTML = "click";
} else div.innerHTML = "drag";
//delete event listeners so that it doesn't keep saying drag
window.removeEventListener("mousemove", drag)
window.removeEventListener("mouseup", this)
}
})
body {
outline: none;
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
overflow: hidden;
}
#div {
/* calculating -5px for each side of border in case border-box doesn't work */
width: calc(100vw - 10px);
height: calc(100vh - 10px);
border: 5px solid orange;
background-color: yellow;
font-weight: 700;
display: grid;
place-items: center;
user-select: none;
cursor: pointer;
padding: 0;
margin: 0;
}
<html>
<body>
<div id="div">Click me or drag me.</div>
</body>
</html>
Had the same problem recently with a tree list where the user can either click on the item or drag it, made this small Pointer class and put it in my utils.js
function Pointer(threshold = 10) {
let x = 0;
let y = 0;
return {
start(e) {
x = e.clientX;
y = e.clientY;
},
isClick(e) {
const deltaX = Math.abs(e.clientX - x);
const deltaY = Math.abs(e.clientY - y);
return deltaX < threshold && deltaY < threshold;
}
}
}
Here you can see it at work:
function Pointer(threshold = 10) {
let x = 0;
let y = 0;
return {
start(e) {
x = e.clientX;
y = e.clientY;
},
isClick(e) {
const deltaX = Math.abs(e.clientX - x);
const deltaY = Math.abs(e.clientY - y);
return deltaX < threshold && deltaY < threshold;
}
}
}
const pointer = new Pointer();
window.addEventListener('mousedown', (e) => pointer.start(e))
//window.addEventListener('mousemove', (e) => pointer.last(e))
window.addEventListener('mouseup', (e) => {
const operation = pointer.isClick(e)
? "Click"
: "Drag"
console.log(operation)
})
It's really this simple
var dragged = false
window.addEventListener('mousedown', function () { dragged = false })
window.addEventListener('mousemove', function () { dragged = true })
window.addEventListener('mouseup', function() {
if (dragged == true) { return }
console.log("CLICK!! ")
})
You honestly do not want to add a threshold allowing a small movement. The above is the correct, normal, feel of clicking on all desktop interfaces.
Just try it.
You can easily add an event if you like.
If just to filter out the drag case, do it like this:
var moved = false;
$(selector)
.mousedown(function() {moved = false;})
.mousemove(function() {moved = true;})
.mouseup(function(event) {
if (!moved) {
// clicked without moving mouse
}
});
Another solution for class based vanilla JS using a distance threshold
private initDetectDrag(element) {
let clickOrigin = { x: 0, y: 0 };
const dragDistanceThreshhold = 20;
element.addEventListener('mousedown', (event) => {
this.isDragged = false
clickOrigin = { x: event.clientX, y: event.clientY };
});
element.addEventListener('mousemove', (event) => {
if (Math.sqrt(Math.pow(clickOrigin.y - event.clientY, 2) + Math.pow(clickOrigin.x - event.clientX, 2)) > dragDistanceThreshhold) {
this.isDragged = true
}
});
}
Add inside the class (SOMESLIDER_ELEMENT can also be document to be global):
private isDragged: boolean;
constructor() {
this.initDetectDrag(SOMESLIDER_ELEMENT);
this.doSomeSlideStuff(SOMESLIDER_ELEMENT);
element.addEventListener('click', (event) => {
if (!this.sliderIsDragged) {
console.log('was clicked');
} else {
console.log('was dragged, ignore click or handle this');
}
}, false);
}
Pure JS with DeltaX and DeltaY
This DeltaX and DeltaY as suggested by a comment in the accepted answer to avoid the frustrating experience when trying to click and get a drag operation instead due to a one tick mousemove.
deltaX = deltaY = 2;//px
var element = document.getElementById('divID');
element.addEventListener("mousedown", function(e){
if (typeof InitPageX == 'undefined' && typeof InitPageY == 'undefined') {
InitPageX = e.pageX;
InitPageY = e.pageY;
}
}, false);
element.addEventListener("mousemove", function(e){
if (typeof InitPageX !== 'undefined' && typeof InitPageY !== 'undefined') {
diffX = e.pageX - InitPageX;
diffY = e.pageY - InitPageY;
if ( (diffX > deltaX) || (diffX < -deltaX)
||
(diffY > deltaY) || (diffY < -deltaY)
) {
console.log("dragging");//dragging event or function goes here.
}
else {
console.log("click");//click event or moving back in delta goes here.
}
}
}, false);
element.addEventListener("mouseup", function(){
delete InitPageX;
delete InitPageY;
}, false);
element.addEventListener("click", function(){
console.log("click");
}, false);
For a public action on an OSM map (position a marker on click) the question was: 1) how to determine the duration of mouse down->up (you can't imagine creating a new marker for each click) and 2) did the mouse move during down->up (i.e user is dragging the map).
const map = document.getElementById('map');
map.addEventListener("mousedown", position);
map.addEventListener("mouseup", calculate);
let posX, posY, endX, endY, t1, t2, action;
function position(e) {
posX = e.clientX;
posY = e.clientY;
t1 = Date.now();
}
function calculate(e) {
endX = e.clientX;
endY = e.clientY;
t2 = (Date.now()-t1)/1000;
action = 'inactive';
if( t2 > 0.5 && t2 < 1.5) { // Fixing duration of mouse down->up
if( Math.abs( posX-endX ) < 5 && Math.abs( posY-endY ) < 5 ) { // 5px error on mouse pos while clicking
action = 'active';
// --------> Do something
}
}
console.log('Down = '+posX + ', ' + posY+'\nUp = '+endX + ', ' + endY+ '\nAction = '+ action);
}
Based on this answer, I did this in my React component:
export default React.memo(() => {
const containerRef = React.useRef(null);
React.useEffect(() => {
document.addEventListener('mousedown', handleMouseMove);
return () => document.removeEventListener('mousedown', handleMouseMove);
}, []);
const handleMouseMove = React.useCallback(() => {
const drag = (e) => {
console.log('mouse is moving');
};
const lift = (e) => {
console.log('mouse move ended');
window.removeEventListener('mousemove', drag);
window.removeEventListener('mouseup', this);
};
window.addEventListener('mousemove', drag);
window.addEventListener('mouseup', lift);
}, []);
return (
<div style={{ width: '100vw', height: '100vh' }} ref={containerRef} />
);
})
If you want check the click or drag behavior of a specific element you can do this without having to listen to the body.
$(document).ready(function(){
let click;
$('.owl-carousel').owlCarousel({
items: 1
});
// prevent clicks when sliding
$('.btn')
.on('mousemove', function(){
click = false;
})
.on('mousedown', function(){
click = true;
});
// change mouseup listener to '.content' to listen to a wider area. (mouse drag release could happen out of the '.btn' which we have not listent to). Note that the click will trigger if '.btn' mousedown event is triggered above
$('.btn').on('mouseup', function(){
if(click){
$('.result').text('clicked');
} else {
$('.result').text('dragged');
}
});
});
.content{
position: relative;
width: 500px;
height: 400px;
background: #f2f2f2;
}
.slider, .result{
position: relative;
width: 400px;
}
.slider{
height: 200px;
margin: 0 auto;
top: 30px;
}
.btn{
display: flex;
align-items: center;
justify-content: center;
text-align: center;
height: 100px;
background: #c66;
}
.result{
height: 30px;
top: 10px;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.carousel.min.css" />
<div class="content">
<div class="slider">
<div class="owl-carousel owl-theme">
<div class="item">
click me without moving the mouse
</div>
<div class="item">
click me without moving the mouse
</div>
</div>
<div class="result"></div>
</div>
</div>
from #Przemek 's answer,
function listenClickOnly(element, callback, threshold=10) {
let drag = 0;
element.addEventListener('mousedown', () => drag = 0);
element.addEventListener('mousemove', () => drag++);
element.addEventListener('mouseup', e => {
if (drag<threshold) callback(e);
});
}
listenClickOnly(
document,
() => console.log('click'),
10
);
The following coding is to detect the movement of mouseup and mousedown.
It shall work for most of the cases. It also depends
on how you treat mouseevent as Click.
In JavaScript, the detection is very simple. It does not concern how
long you press or movement between the mousedown and mouseup.
Event.detail would not reset to 1 when your mouse is moved between
the mousedown and mouseup.
If you need to differentiate the click and long press, you need to
check the difference in event.timeStamp too.
// ==== add the code at the begining of your coding ====
let clickStatus = 0;
(() => {
let screenX, screenY;
document.addEventListener('mousedown', (event) => ({screenX, screenY} = event), true);
document.addEventListener('mouseup', (event) => (clickStatus = Math.abs(event.screenX - screenX) + Math.abs(event.screenY - screenY) < 3), true);
})();
// ==== add the code at the begining of your coding ====
$("#draggable").click(function(event) {
if (clickStatus) {
console.log(`click event is valid, click count: ${event.detail}`)
} else {
console.log(`click event is invalid`)
}
})
<!doctype html>
<html lang="en">
<!-- coding example from https://jqueryui.com/draggable/ -->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Draggable - Default functionality</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<style>
#draggable { width: 150px; height: 150px; padding: 0.5em; }
</style>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( "#draggable" ).draggable();
} );
</script>
</head>
<body>
<div id="draggable" class="ui-widget-content">
<p>Drag me around</p>
</div>
</body>
</html>
Is very easy,
el = document.getElementById("your_id");
var isDown = false;
el.addEventListener('mousedown', function () {
isDown = true;
});
el.addEventListener('mouseup', function () {
isDown = false;
});
el.addEventListener('mousemove', function () {
if (isDown) {
// your code goes here
}
});