Related
I'm trying make this image move 16px every click, but it's not moving. I've tried this:
But that only moves it once. So I tried something different that I found online.
Here is my code:
Html:
document.getElementById('player').style.left += "-16px";
function left() {
var left = parseInt(player.style.left);
player.style.left = (left +16) + "px";
}
<img id="player" src="characters/prisoner-down.png"></img>
<img id="leftbtn" src="icons/left.png" onclick="left();"></img>
Edit: if it's useful to know, there are gonna be three more buttons for the other directions and the image I want to move is in another DIV from the image that moves it. On my editor it says that the symbol ` is invalid, so I'd like to avoid it.
You can create a function that get the direction of the translation and the number of pixels to increment, then return a function that set style.transform of the element clicked, using a closure you can update the amount of pixels to translate, try this:
function transform(side, n) {
let translatePixels = 0;
if (side == 'right') {
return function(e) {
translatePixels += n;
e.target.style.transform = `translate(${translatePixels}px)`;
}
}
if (side == 'left') {
return function(e) {
translatePixels -= n;
e.target.style.transform = `translate(${translatePixels}px)`;
}
}
}
const translateRight = transform('right', 16);
const translateLeft = transform('left', 16);
<img onclick="translateRight(event);" src="https://images.rappi.com.ar/products/2311265-1622567743906.png?d=200x200&e=webp"></img>
<img onclick="translateLeft(event);" src="https://cdn.domestika.org/c_fill,dpr_auto,f_auto,h_256,pg_1,t_base_params,w_256/v1499705651/avatars/000/536/178/536178-original.jpg?1499705651" style="right: 0; position:absolute"></img>
Give position property to the img which you want to move.
Like position: relative;, position: absolute; etc.
By positioning them you can use top, left, right and top properties.
don't use position: static; on that img cause above property has no effect on static element.
document.getElementById('player').style.left = "-16px";
function left() {
let left = parseInt(player.style.left);
player.style.left = `${parseInt(left+16)}px`;
}
img{
/*important for using left, right, top and bottom properties*/
position: relative;
}
<img id="player" src="https://i.postimg.cc/44pXvSwD/pngwing-com.png"></img>
<img id="leftbtn" src="https://i.postimg.cc/FRkMDYvr/Pngtree-right-arrow-flat-multi-color-3777297.png" onclick="left();"></img>
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;
}
Thanks to a really helpful user on this website (whose name I do not know, but I wish to thank and credit him!), I got the following tip on how to store area elements in an array so that when I mouse over a coordinate, I could display all of the overlay id's of the area elements that existed at that coordinate (even if the area elements were not at the same z-level):
I'm just stuck on one thing- once I have gathered all the elements that exist at the coordinate in the hoveredElements array, how do I show their overlay ids?
EDIT:
Here is an example of the full code (the overlay still does not display when I mouse over)
The file test.txt contains:
cscCSL1A15 700 359 905 318
cscCSL1A14 794 400 905 318
I use the maphilight plugin available online, and blanketaphi.png is the plot I use as a background.
<!DOCTYPE html>
<html>
<head>
<title>Detector Elements</title>
<script type="text/javascript"
src="Demo_imagemap_highlight_files/jquery-1.js"></script>
<!-- add maphilight plugin -->
<script type="text/javascript"
src="Demo_imagemap_highlight_files/jquery_002.js"></script>
</head>
<body>
<div class="content">
<div class="map"
style='display: block; background: transparent
url("Demo_imagemap_highlight_files/blanketaphi.png")
repeat scroll 0% 0%; position: relative; padding: 0px; width: 1037px;
height: 557px;'>
<canvas width="1037" height="557" style="width: 1037px; height: 557px;
position: absolute; left: 0px; top: 0px; padding: 0px; border: 0px none;
opacity: 1;"></canvas>
<img style="opacity: 0; position: absolute; left: 0px; top: 0px; padding: 0px;
border: 0px none;" src="Demo_imagemap_highlight_files/blanketaphi.png"
alt="foo" class="map maphilighted" usemap="#demo" height="557" width="1037"
border="0" />
</div>
</div>
<map name="demo" id="demo"></map>
</body>
</html>
<script type="text/javascript">
window.onload = function(){
var f = (function(){
var xhr = [];
var files = [ "test.txt"];
for (i = 0; i < 1; i++) {
(function (i){
xhr[i] = new XMLHttpRequest();
xhr[i].open("GET", files[i], true);
xhr[i].onreadystatechange = function () {
if (xhr[i].readyState == 4 && xhr[i].status == 200) {
// get text contents
j=20000*i + 50000;
var coords = xhr[i].responseText.split("\n");
coords = coords.filter(Boolean) //prevents extra rect with 0 coords
coords.forEach(function(coord) {
var area = document.createElement("area");
var att = document.createAttribute("data-maphilight");
if (i == 0) { //green
att.value = '{"strokeColor":"000000","strokeWidth":2,' +
'"fillColor":"009900","fillOpacity":0.5}';
}
area.setAttributeNode(att);
area.id = "r"+j;
area.shape = "rect";
area.coords = coord.substring(10,coord.length).trim()
.replace(/ +/g,","); // replaces spaces in txt file with commas
area.href = "#";
area.alt = "r"+j;
// create overlay with first term in string
var div = document.createElement("div");
div.id ="overlayr"+j;
div.innerHTML = coord.substring(0,10);
div.style.display = "none";
//increase j
j++;
// get map element
document.getElementById("demo").appendChild(area);
document.getElementById("demo").appendChild(div);
});
$('.map').maphilight();
//display overlay ids by mousing over
var elementPositions = [];
var hoveredElements = [];
if($('#demo')) {
$('#demo area').each(function() {
var offset = $(this).offset();
var top = offset.top;
var left = offset.left;
var bottom = $(window).height() - top - $(this).height();
var right = $(window).width() - left - $(this).width();
elementPositions.push({
element: $(this),
top: top,
bottom: bottom,
left: left,
right: right
});
//alert(top + "," + left + "," + right + "," + bottom);
});
$("body").mousemove(function(e) {
hoveredElements = [];
var yPosition = e.pageX;
var xPosition = e.pageY;
for (var i = 0; i < elementPositions.length; i++) {
if (xPosition >= elementPositions[i].left &&
xPosition <= elementPositions[i].right &&
yPosition >= elementPositions[i].top &&
yPosition <= elementPositions[i].bottom) {
// The mouse is within the element's boundaries
$("#hovers").append(elementPositions[i].element);
}
}
for (var i = 0; i < hoveredElements.length; i++) {
// The element as a jQuery object
var elem = hoveredElements[i];
var id = hoveredElements[i].attr('id');
$('#overlay'+id).show();
}
});
};
}
};
xhr[i].send();
})(i);
}
})();
};
</script>
Why not just something like this:
var elementPositions = [];
var hoveredElements = [];
if($('#demo')) {
$('#demo area').each(function() {
var offset = $(this).offset();
var top = offset.top;
var left = offset.left;
var bottom = $(window).height() - top - $(this).height();
var right = $(window).width() - left - $(this).width();
elementPositions.push({ element: $(this), top: top, bottom: bottom, left: left, right: right });
//alert(top + "," + left + "," + right + "," + bottom);
});
$("body").mousemove(function(e) {
hoveredElements = [];
var yPosition = e.pageX;
var xPosition = e.pageY;
for (var i = 0; i < elementPositions.length; i++) {
if (xPosition >= elementPositions[i].left &&
xPosition <= elementPositions[i].right &&
yPosition >= elementPositions[i].top &&
yPosition <= elementPositions[i].bottom) {
// The mouse is within the element's boundaries
hoveredElements.push(elementPositions[i].element);
$("#hovers").append(elementPositions[i].element);
}
} //end of for loop over all elements
console.log(hoveredElements);
for (var i = 0; hoveredElements.length; i++)
{ //for loop over all hovered elements
// The element as a jQuery object
var elem = hoveredElements[i];
var id = hoveredElements[i].attr('id');
console.log(id);
$('#overlay'+id).show();
// Do stuff to that jQuery element:
//??? something like elem.show();
}
You've got a lot of stuff here that doesn't make sense to me but here's what I can gather so far.
Your areas need to be in a container called demo area. Not sure how the space in the ID works so in my case I switched it to demoarea. Also somewhere in the page, there has to be another element called demo for anything to even happen.
Once that's done, the script loads demoarea into the elementPositions array. Judging from your description that's not what you want to do, you probably want to load all the elements inside demoareainto the array. So the first change is
$('#demo area').each(function() {
Becomes
$('#demoarea').children().each(function() {
Now what becomes confusing to me is that this script for whatever reason decides that you need to have another element called hover so it can move the element out of demoarea into hover when you mouse over it. If that is what you want, then you can do your show trick with some simple CSS.
<div style="display:none" id="overlayr6064"> Example Overlay ID name </div>
Becomes
<div id="overlayr6064"> Example Overlay ID name </div>
And then you add:
<style>
#demoarea div {
display: none;
}
#hover div {
display: block;
}
</style>
Assuming that is not what you wanted, what #liamEgan did to add the elements to the hoveredElements array is good, but you have an infinite loop here
for (var i = 0; hoveredElements.length; i++)
it should be
for (var i = 0; i < hoveredElements.length; i++)
Then the rest works... except one last thing, you want to load these listeners to your script when the page loads in a document ready method.
So in all it looks a bit like:
//display overlay ids by mousing over (my map is called 'demo')
var elementPositions = [];
var hoveredElements = [];
if($('#demo')) {
$('#demoarea').children().each(function() {
var offset = $(this).offset();
var top = offset.top;
var left = offset.left;
var bottom = $(window).height() - top - $(this).height();
var right = $(window).width() - left - $(this).width();
elementPositions.push({ element: $(this), top: top, bottom: bottom, left: left, right: right });
});
console.log('After Scanning demoarea elementPositions looks like:')
console.log(elementPositions);
$(document).ready(function () {
$("body").mousemove(function(e) {
hoveredElements = [];
var yPosition = e.pageX;
var xPosition = e.pageY;
for (var i = 0; i < elementPositions.length; i++) {
if (xPosition >= elementPositions[i].left &&
xPosition <= elementPositions[i].right &&
yPosition >= elementPositions[i].top &&
yPosition <= elementPositions[i].bottom) {
// The mouse is within the element's boundaries
if (typeof elementPositions[i].element != "undefined") {
hoveredElements.push(elementPositions[i].element);
$("#hovers").append(elementPositions[i].element);
}
}
} //end of for loop over all elements
for (var i = 0; i < hoveredElements.length; i++) { //for loop over all hovered elements
// The element as a jQuery object
console.log(hoveredElements[i]);
if (typeof hoveredElements[i] != "undefined") {
var elem = hoveredElements[i];
var id = elem.attr('id');
$('#overlay'+id).show();
}
// Do stuff to that jQuery element:
//??? something like elem.show();
}
});
});
}
#demoarea {
border: 2px blue dotted;
}
/* Border added so I can see where to mouse over */
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="demo">
<div id="demoarea">
<area shape="rect" coords="431,499,458,491" href="#" id="r6064" alt="r6064">
<div style="display:none" id="overlayr6064"> Example Overlay ID name </div>
</div>
<div id="hovers">
</div>
</div>
Edit: sorry I added the undefined tests while fixing this because of the infinite loop but I think they're not really needed. Still nice to have though. Also since the area also gets moved into the hover area this script does try to show an element called overlayoverlayr6064r6064 which fortunately doesn't exist. But ya, again, probably not what you had in mind.
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'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