Draggable button in Firefox - javascript

Is it possible to make a <button> (<input type="button">) work with HTML5 drag and drop in Mozilla Firefox (while still being clickable)?
The following snippet works in Google Chrome but the button and div with button cannot be dragged Mozilla Firefox (unless the Alt key is pressed down, no idea about mobile):
document.getElementById("myDiv").addEventListener(
"dragstart",
function (e) {
e.dataTransfer.setData("Text", "myDiv")
}
);
document.getElementById("myButton").addEventListener(
"dragstart",
function (e) {
e.dataTransfer.setData("Text", "myButton")
}
);
document.getElementById("myDivWithButton").addEventListener(
"dragstart",
function (e) {
e.dataTransfer.setData("Text", "myDivWithButton")
}
);
<div id="myDiv" draggable="true">Div</div>
<button id="myButton" draggable="true">Button</button>
<div id="myDivWithButton" draggable="true"><button>Div with Button</button></div>
I used draggable="true" and dataTransfer.setData, is there something I missed? Is there some sensible workaround?
(If you want to know what I need this for: I have something which can be either dragged at a certain position or set at the default position [center of current view], my idea was to do both through the button [d&d → choose position, click → default position]. I know that I guess I could try to format a <div> to make it look like a <button> or simply split the control into two elements but I'd rather not.)

There is already a bug on Firefox where you can't drag a button.
https://bugzilla.mozilla.org/show_bug.cgi?id=568313
However you can drag your div containing button (which is not draggable right now) using 'after' pseudo class.
Example:
document.getElementById("myDivWithButton").addEventListener(
"dragstart",
function (e) {
e.dataTransfer.setData("Text", "myDivWithButton")
}
);
.frontdrop {
position: relative;
}
.frontdrop:after {
content: '';
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
}
<div id="myDivWithButton" class="frontdrop" draggable="true"><button>Div with Button</button></div>

I found a tricky solution:
<label draggable="true" ondragstart="event.dataTransfer.setData('text/plain', '')">
<input type="button" value="Click me!" style="pointer-events: none" onclick="console.log(event)">
</label>
Wrap input with a draggable label and set pointer-events CSS property to none. Using this method, your button will be interactive and draggable.

From others, I came to know that your issue is because of a bug exists in Firefox. I suggest you to implement custom listeners to imitate drag event.
My solution is as follows:
var btn = document.getElementById('btn');
var btnPressed = false;
btn.addEventListener('mousedown', function(e) {
btnPressed = true;
px = e.clientX;
py = e.clientY;
});
btn.addEventListener('mouseup', function(e) {
btnPressed = false;
})
window.addEventListener('mouseup', function(e) {
btn.style.MozTransform = "";
btn.style.WebkitTransform = "";
btn.style.opacity = 1;
})
window.addEventListener('mousemove', function(e) {
if(btnPressed) {
dx = e.clientX - px;
dy = e.clientY - py;
btn.style.opacity = 0.85;
btn.style.MozTransform = "translate(" + dx + "px, " + dy + "px)";
btn.style.WebkitTransform = "translate(" + dx + "px, " + dy + "px)";
}
})
<button id="btn">Drag Me</button>
I know its not perfect. Hard-coding is the only way to resolve this issue until Firefox fix this bug.

You can give a little ledge that is draggable next to the button.
.frontdrop:after {
content: '';
top: 0;
left: 0;
right: 20;
bottom: 0;
border-width="1";
border-style: solid;
position: absolute;
}

Related

Loop for onmousemove event

I've run into a problem with running a loop to trigger a mousemove event on my HTML/CSS.
I know I can go through and get every individual ID on the HTML tags to execute the code the way I want. But I know there is a better way to do it with a loop of some sort and use far less code.
The images should follow the mouse while moving over the div with class mycard. Any suggestions or ideas on how to get it working properly would be very much appreciated.
I've tried running a loop to add the classes to divs but had no luck.
var mouseHover = document.getElementById('moveHover');
window.onmousemove = function(e) {
var x = e.clientX;
var y = e.clientY;
mouseHover.style.top = (y + 20) + 'px';
mouseHover.style.left = (x + 20) + 'px';
};
.mycard span {
position: absolute;
display: none;
z-index: 99;
}
.mycard:hover span {
display: block;
position: fixed;
overflow: hidden;
}
.imgHover a {
position: relative;
}
.imgHover span {
position: absolute;
display: none;
z-index: 99;
}
.imgHover a:hover span {
display: block;
position: fixed;
overflow: hidden;
}
<div class="imgHover mycard">
<div class="cardcost">
<p class="cardcosttext">2</p>
</div>
<div class="hscardepic">
<a style="margin-left: 1000%;vertical-align: middle;">
Doomsayer
<span id="moveHover">
<img src="Classic_Set/doomsayer.png" height="300" width="300" />
</span>
</a>
</div>
<div class="cardamount">
<p class="cardamounttext">×2</p>
</div>
</div>
If I understand what you're asking, you could use querySelectorAll to get the elements and forEach to move them:
// get the div that responds to mouse movement
const mycard = document.querySelector('.mycard');
// add a mousemove listener
mycard.addEventListener('mousemove', function(e) {
// get the DOM element with the mousemove listener from the event
const {target} = e;
// get img child elements of the target.
// (use whatever css selector you need here. doesn't have to img)
const images = target.querySelectorAll('img');
// iterate over each item...
images.forEach(image => {
// ...and do whatever you need to do with it
const x = e.clientX;
const y = e.clientY;
image.style.top = (y + 20) + 'px';
image.style.left = (x + 20) + 'px';
})
});
I'm also not entirely sure what your end-goal is, but I'll take a stab at it.
I would recommend changing moveHover to being the class instead of the ID. Then you could do something like this:
var mouseHover = null;
window.onmousemove = function (e) {
if(mouseHover != null){
var x = e.clientX;
var y = e.clientY;
mouseHover.style.top = (y+20) + 'px';
mouseHover.style.left = (x+20) + 'px';
}
};
function onHover(e){
mouseHover = e.target.querySelector('.moveHover');
}
var elements = document.getElementsByClassName('imgHover');
for(var i = 0; i < elements.length; i++){
elements[i].onmouseenter = onHover;
}
The loop runs one time to set the onmouseenter event. Sure beats moving all .moveHover elements all the time.

How to make scroll work on nav click with scrollTop

I am looking for way to make page scroll on menu click.
My found place where to go. When I use translateY it work(but ofcouse I can't use it).
I need a scroll I tried to use scrollTop and scrollBy but it doesn't work for me.
What I do wrong?
function scroll(id: number) {
for (let k = 0; k < sectionsForScroll.length; k++) {
if (sectionsForScroll[k].dataset.navId == id) {
const bigginer = sectionsForScroll[0].getBoundingClientRect().top;
console.log( bigginer + 'px how far we from the start ');
const distanceToGo= sectionsForScroll[k].getBoundingClientRect().top;
console.log(sectionsForScroll[k].offsetTop);
const distanceToScroll = bigginer - distanceToGo;
console.log(distanceToGo + ' where we have to go ');
console.log(distanceToScroll + ' what the distanse we need to scroll ');
main.style.transform = 'translateY(' + distanceToScroll + 'px)';
// main.scrollTop=distanceToGo;
// window.scrollBy(0, distanceToGo);
}
}
}
window.scroll will work for you. Here is an example of scrolling 300px after clicking the scroll button.
Before using it you should checkout the compatibility table for the smooth option.
document.getElementById("myButton").addEventListener("click", scrollMe);
function scrollMe() {
window.scroll({
top: 300,
left: 0,
behavior: 'smooth'
});
}
.myDiv {
background-color: green;
height: 1000px;
}
<div class="myDiv">
<button id="myButton">Scroll</button>
</div>

jquery scrolltop() returns value of previous scroll position

I am using jquery function element.scrollTop() to get the current scroll position of the page using following line:
var currentScrollPosition= $('html').scrollTop() || $('body').scrollTop();
But it always return a value of previous scroll position. Please check the code below (which is same as is in here).
As you can try yourself and see in the code, after each tiny scroll, we get following series of values:
(1: when code is first run and no move is made yet)
delta:0
cumulativeDelta:0
functionCallCount:0
currentScrollPosition:0
(delta gives how much is scrolled, cumulativeDelta gives total amount of scroll, functionCallCount is how many times you scrolled and currentScrollPosition is value returned by scrolltop() )
(2: when scrolled a little)
delta:-120
cumulativeDelta:-120
functionCallCount:1
currentScrollPosition:0
(note here, currentScrollPosition is still not updated)
(3: when scrolled a little further)
delta:-120
cumulativeDelta:-240
functionCallCount:2
currentScrollPosition:90.90908893868948
(here, cumulativeDelta, which is addition of total scroll made so far, is doubled and currentScrollPosition is updated for the first time)
(4: when scrolled a little more)
delta:-120
cumulativeDelta:-360
functionCallCount:3
currentScrollPosition:181.81817787737896
(now, cumulativeDelta is tripled while currentScrollPosition is doubled. Hence, this is value after two scrolls but is updated adter 3 scrolls)
I apologize for long question, but would have been difficult to ask otherwise. I would like to know why is this happening and if I should use this function some other way there are any alternative to this function.
document.addEventListener("mousewheel", MouseWheelHandler);
var cumulativeDelta = 0,
functionCallCount = 0;
function MouseWheelHandler(e) {
e = window.event || e; // 'event' with old IE support
var delta = e.wheelDelta || -e.detail; // get delta value
cumulativeDelta += delta;
functionCallCount += 1;
currentScrollPosition = $('html').scrollTop() || $('body').scrollTop();
document.getElementById("info1").innerHTML = "delta:" + delta;
document.getElementById("info2").innerHTML = "cumulativeDelta:" + cumulativeDelta;
document.getElementById("info3").innerHTML = "functionCallCount:" + functionCallCount;
document.getElementById("info4").innerHTML = "currentScrollPosition:" + currentScrollPosition;
}
body {
height: 2000px;
border: solid red 3px;
}
.normalPart {
border: solid green 2px;
height: 900px;
}
.stationary {
position: fixed;
top: 0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<div class="stationary">
<div id="info1"></div>
<div id="info2"></div>
<div id="info3"></div>
<div id="info4"></div>
</div>
The issue is because the mousewheel event fires when the mousewheel movement starts. You are reading the scrollTop at the point before the update has happened. You need to use a timer to get the scrollTop after the mouse wheel scroll has finished. Try this:
document.addEventListener("mousewheel", MouseWheelHandler);
var cumulativeDelta = 0,
functionCallCount = 0,
currentScrollPosition = 0;
function MouseWheelHandler(e) {
e = window.event || e; // 'event' with old IE support
var delta = e.wheelDelta || -e.detail; // get delta value
cumulativeDelta += delta;
functionCallCount += 1;
setTimeout(function() {
currentScrollPosition = $(window).scrollTop();
document.getElementById("info4").innerHTML = "currentScrollPosition:" + currentScrollPosition;
}, 200); // update currentScrollPos 200ms after event fires
document.getElementById("info1").innerHTML = "delta:" + delta;
document.getElementById("info2").innerHTML = "cumulativeDelta:" + cumulativeDelta;
document.getElementById("info3").innerHTML = "functionCallCount:" + functionCallCount;
}
body {
height: 2000px;
border: solid red 3px;
}
.normalPart {
border: solid green 2px;
height: 900px;
}
.stationary {
position: fixed;
top: 0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<div class="stationary">
<div id="info1"></div>
<div id="info2"></div>
<div id="info3"></div>
<div id="info4"></div>
</div>
Alternatively you could read the currentScrollTop position directly on the scroll event of the window as that will always be in sync with its current position.
I would listen for the scroll event instead of wheel. Scroll activates after the scrollTop value has been updated.
target.onscroll = functionRef
Great example here

Javascript WYSIWYG removeEventListener then bring it back

I am creating a weird WYSIWYG from scratch using pure JS as an exercise. Here is my code thus far:
<style>
body {
height: 200px;
background: white;
}
.title1 {
height: 12px;
width: 100px;
background: white;
position: relative;
}
</style>
<script>
newTitle1 = function() {
var title1 = document.createElement("div")
title1.setAttribute("contentEditable", "true")
title1.innerHTML = "whatever";
title1.className = "title1";
title1.addEventListener("click", function() {
removeEventListener("click", newTitle1);
})
title1.style.left = (event.pageX -4) + "px";
title1.style.top = (event.pageY -4 ) + "px";
document.body.appendChild(title1)
}
addEventListener("click", newTitle1)
</script>
My question is specifically about
title1.addEventListener("click", function() {
removeEventListener("click", newTitle1);
})
I want removeEventListener to work while I click into the div that it just created. BUT, when I click outside of the div, I want newTitle1 to work.
Bottomline: I want newTitle1 listener to work outside of the div it just created, and not to work inside the div it creates. Any ideas how to make this happen? Here is a fiddle of my code to test if needed: https://jsfiddle.net/nirchernia/v3cLwb82/
You want to stop the event propagating. Solution shamelessly stolen from over here.
newTitle1 = function() {
var title1 = document.createElement("div")
title1.setAttribute("contentEditable", "true")
title1.innerHTML = "whatever";
title1.className = "title1";
title1.addEventListener("click", function(event) {
event.stopPropagation()
window.event.cancelBubble = true
})
title1.style.left = (event.pageX -4) + "px";
title1.style.top = (event.pageY -4 ) + "px";
document.body.appendChild(title1)
}
addEventListener("click", newTitle1)
/edit: And I think you want your .title1 CSS to have position:absolute; too :)

How do I make a function to find the coordinates of an element regardless of its positioning?

I'm making a drag and drop engine in JavaScript, and I don't know how to set the correct position of the dragObj because it changes depending on the parent element's positioning type (Does the dragObj also change depending on its parent's "parent element" ect.?).
So, my dragObj looks like this:
function makeObj(event) {
var obj = new Object();
var e = event.target;
obj.element = e;
obj.boundElement = null;
while(e = e.parentNode) {
if(~e.className.search(/bound/)) { //if(/bound/.test(e.className)) {
obj.boundElement = e;
break;
}
}
if(obj.boundElement == null)
obj.boundElement = document.body;
// I would like to find the correct minimum bounds with findPos(); however, I need
// findPos() to work with every type of positioning (absolute, relatice, ect.)
//var elemPos = findPos(obj.boundElement);
//obj.minBoundX = elemPos.x;
//obj.minBoundY = elemPos.y;
obj.minBoundX = obj.boundElement.offsetLeft + obj.boundElement.offsetWidth - obj.element.offsetWidth;
obj.minBoundY = obj.boundElement.offsetTop + obj.boundElement.offsetHeight - obj.element.offsetHeight;
obj.maxBoundX = obj.boundElement.offsetLeft;
obj.maxBoundY = obj.boundElement.offsetTop;
setHelperBoxPos(obj);
obj.posX = event.clientX - obj.element.offsetLeft;
obj.posY = event.clientY - obj.element.offsetTop;
return obj;
}
So, when I make a dragObj, I also set its "position" and its bounding element. In a comment portion right before I set the .minBoundX and .minBoundY attributes I explain how I would like to set them; however, it doesn't work because the findPos() function doesn't work.
Here is the findPos() function:
function findPos(obj) { // Donated by `lwburk` on StackOverflow
var curleft = curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
}
I believe this function works if the bounding element has position: absolute; set, but I want the user to be able to set its positioning type. Also, the bounding element is set by the .bound class, and the dragObj is set by the .drag class.
Here's the HTML:
<div id="min" class="helper-box" style="border: 1px solid blue;"></div>
<div id="max" class="helper-box" style="border: 1px solid red;"></div>
<div id="center">
<h1>Hello World! <hr /></h1>
<div id="box" class="bound">
<p class="drag square"> One </p>
<p class="drag square"> Two </p>
</div>
</div>
And here is the CSS:
#charset "utf-8";
/* CSS Document */
* {
padding: 0px;
margin: 0px;
}
body {
background-color:#DFDFDF;
}
.drag {
position: absolute;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.bound {
;
}
.square {
width: 100px;
height: 100px;
background: #1047A9;
cursor:move;
border-radius: 25px;
-moz-border-radius: 25px;
}
#center {
width: 500px;
margin: auto;
margin-top: 50px;
background-color: #29477F;
color: #E8E8E8;
text-align: center;
border-radius: 25px;
-moz-border-radius: 25px;
}
#box {
background-color: #009EBE;
height: 275px;
border-radius: 0 0 25px 25px;
-moz-border-radius: 0 0 25px 25px;
opacity: 1.0;
}
.helper-box {
position: absolute;
width: 5px;
height: 5px;
}
And here is the entire engine:
// JavaScript Document
var dragObj;
document.addEventListener("mousedown", down, false);
function down(event) {
if(~event.target.className.search(/drag/)) {
dragObj = makeObj(event);
dragObj.element.style.zIndex="100";
document.addEventListener("mousemove", freeMovement, false);
}
}
function freeMovement(event) {
if (typeof(dragObj.element.mouseup) == "undefined")
document.addEventListener("mouseup", drop, false);
//Prevents redundantly adding the same event handler repeatedly
dragObj.element.style.left = Math.max(dragObj.maxBoundX, Math.min(dragObj.minBoundX, event.clientX - dragObj.posX)) + "px";
dragObj.element.style.top = Math.max(dragObj.maxBoundY, Math.min(dragObj.minBoundY, event.clientY - dragObj.posY)) + "px";
}
function drop() {
dragObj.element.style.zIndex="1";
document.removeEventListener("mousemove", freeMovement, false);
document.removeEventListener("mouseup", drop, false);
//alert("DEBUG_DROP");
}
function makeObj(event) {
var obj = new Object();
var e = event.target;
obj.element = e;
obj.boundElement = null;
while(e = e.parentNode) {
if(~e.className.search(/bound/)) { //if(/bound/.test(e.className)) {
obj.boundElement = e;
break;
}
}
if(obj.boundElement == null)
obj.boundElement = document.body;
// I would like to find the correct minimum bounds with findPos(); however, I need
// findPos() to work with every type of positioning (absolute, relatice, ect.)
//var elemPos = findPos(obj.boundElement);
//obj.minBoundX = elemPos.x;
//obj.minBoundY = elemPos.y;
obj.minBoundX = obj.boundElement.offsetLeft + obj.boundElement.offsetWidth - obj.element.offsetWidth;
obj.minBoundY = obj.boundElement.offsetTop + obj.boundElement.offsetHeight - obj.element.offsetHeight;
obj.maxBoundX = obj.boundElement.offsetLeft;
obj.maxBoundY = obj.boundElement.offsetTop;
setHelperBoxPos(obj);
obj.posX = event.clientX - obj.element.offsetLeft;
obj.posY = event.clientY - obj.element.offsetTop;
return obj;
}
function findPos(obj) { // Donated by `lwburk` on StackOverflow
var curleft = curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
}
function setHelperBoxPos(obj) {
var minBox = document.getElementById('min');
minBox.style.left = obj.minBoundX + 'px';
minBox.style.top = obj.minBoundY + 'px';
var maxBox = document.getElementById('max');
maxBox.style.left = obj.maxBoundX + 'px';
maxBox.style.top = obj.maxBoundY + 'px';
}
I have also made a jsfiddle for your convenience: http://jsfiddle.net/2XGhK/
So, how do I make a findPos() function which allows for different kinds of positioning. Will I need to make another findPos() function to allow the dragObj to be any kind of positioning as well?
Important Please do not recommend using a library (unless you suggest looking at it for tips on how they deal with positioning).The reason is that I am just learning the language and building things helps me do just that. What's the point in learning a library before I even understand the language?
Most importantly, I greatly appreciate all of your help. Thank you.
In developing Javascript, jQuery really is your best friend, as it simplifies many of these (potentially annoying) tasks. I totally dig the desire to learn without them though; it's a great way to become proficient.
Take a look at jQuery's docs and implementation for position and offset, and hopefully that will give you a good idea for a starting place.
Docs:
http://api.jquery.com/position/
http://api.jquery.com/offset/
Source:
https://github.com/jquery/jquery/blob/master/src/offset.js
I don't know if this is exactly what you need but implementing drag&drop with javascript requires a lot of efforts and testing, especially when you are dealing with parent-children dragging.
Try this one:
http://interface.eyecon.ro/demos/drag.html
Maybe you can read its source code and use it to fit your needs.
Simply make drag object's position absolute -
obj.element = e;
obj.element.style.position = "absolute";
obj.boundElement = null;
btw, thanks for using jsfiddle, it helps.
How useful is this code fragment?
var position = {};
var dragObj = "";
function initDrag(e){
e = e || window.event;
position.objLeft = parseInt(dragObj.style.left);
position.objTop = parseInt(dragObj.style.top);
if( isNaN(position.objLeft) )
position.objLeft = 0;
if( isNaN(position.objTop) )
position.objTop = 0;
position.startX = e.clientX;
position.startY = e.clientY;
if( document.addEventListener )
{
e.preventDefault();
dragObj.addEventListener('mousemove',startDrag,false);
dragObj.addEventListener('mouseup',stopDrag,false);
dragObj.addEventListener('mouseout',stopDrag,false);
}
else
{
dragObj.attachEvent('onmousemove',startDrag);
dragObj.attachEvent('onmouseup',stopDrag);
dragObj.attachEvent('onmouseout',stopDrag);
}
}
function startDrag(e){
e = e || window.event;
dragObj.style.left = position.objLeft+e.clientX-position.startX;
dragObj.style.top = position.objTop+e.clientY-position.startY;
return false;
}
Actually when working with finding the elements position, you use its offsetLeft(/Top). And each element have an offsetParent from where that position origins. When you drag an element, it is good to know it's position according to the whole document (read ), so you get a more precise point. So to find an elementes position you must create the following function:
function findPos(elm) {
var testElm = elm, pos = {x:0, y:0};
while( !!testElm && testElm.tagName.toLowerCase() !== "body" ) {
pos.x += testElm.offsetLeft;
pos.y += testElm.offsetTop;
// important to use offsetParent instead of just parentNode,
// as offsetParent will be where the element gets its offset from.
// And that is not necesarily it parentNode!
testElm = testElm.offsetParent;
}
return pos;
}
But i agree with some of the others, that jQuery DO simplify alot of these tedious calculations. But I really think it is best to get to know JS before you use the API's as it give you an understading of what JS really is.
PS. Dont call this method for every mousemove event, as it will compromise performance. Intead save the value on the element (<your node>["startoffset"] = findPos(<your node>)), and then use it in the mousemove script to find its new position. Actually it is good practice, to save as many values you can upon mousedown, as mousemove will be called ALOT, and therefore should perform as little calculations/traversing as possible.
See it in action: http://jsfiddle.net/fnwxu/22/
Simplest solution:
Set the element to visibility:hidden. Clone the element, set the clone to position:absolute, perform dragging. Once dragging stops (i.e. the user has released the left mouse button), replace the clone with the original element.
This allows you to keep the element in flow for the duration of the draggable operation. That way if the element cannot be dropped in location y (for example), you can have the helper snap back to the original location.
I am a beginner in JavaScript. I found your question very interesting and thought of Googling it. I found these two links which may be of use to you:
http://www.codingforums.com/archive/index.php/t-84055.html
http://www.codeproject.com/KB/scripting/DragDrop_Part-2.aspx

Categories

Resources