draggable div problem - javascript

I found this function to make my divs draggable and it works fine. right now if i have my mouse anywhere on the div it drags, but i only want it to drag the div if my mouse is on the rd_header div. here is the function
var makeDraggable = function(element) {
element = jQuery(element);
var move = function(event) {
if(element.data('mouseMove')) {
var changeX = event.clientX - element.data('mouseX');
var changeY = event.clientY - element.data('mouseY');
var newX = parseInt(element.css('left')) + changeX;
var newY = parseInt(element.css('top')) + changeY;
element.css('left', newX);
element.css('top', newY);
element.data('mouseX', event.clientX);
element.data('mouseY', event.clientY);
}
}
element.mousedown(function(event) {
element.data('mouseMove', true);
element.data('mouseX', event.clientX);
element.data('mouseY', event.clientY);
});
element.parents(':last').mouseup(function() {
element.data('mouseMove', false);
});
element.mouseout(move);
element.mousemove(move);
}
and i call it with this
makeDraggable(jQuery('#rd_main'));
i tried calling iot on the header div instead and changing the function variables element. to element.closest('div') but had no luck. i would appreciate any help
here is my divs
<div id="rd_main">
<div class="rd_header">Action Log</div>
<div id="rd_logdiv" class="sb"></div>
</div>

makeDraggable('#rd_main', 'rd_header');
Then update your function with this:
var makeDraggable = function(element, triggerClass) {
element = $(element);
var move = function(event) {
if (element.data('mouseMove') && isAvailable) {
var changeX = event.clientX - element.data('mouseX');
var changeY = event.clientY - element.data('mouseY');
var newX = parseInt(element.css('left')) + changeX;
var newY = parseInt(element.css('top')) + changeY;
element.css('left', newX);
element.css('top', newY);
element.data('mouseX', event.clientX);
element.data('mouseY', event.clientY);
}
},
isAvailable = false;
element.mousedown(function(event) {
var target = $(event.target);
isAvailable = target.hasClass(triggerClass) || target.closest('.' + triggerClass); //Check if we over wanted element
element.data('mouseMove', true);
element.data('mouseX', event.clientX);
element.data('mouseY', event.clientY);
});
element.parents(':last').mouseup(function() {
element.data('mouseMove', false);
});
element.mouseout(move);
element.mousemove(move);
}

try using element.children().first() because rd_header div is the first child of your rd_main
div.
Any way why don't you use Jquery draggable instead of your own function ? http://jqueryui.com/demos/draggable/#handle

Related

How to make text inside a draggable div selectable with a mouse in Javascript?

So, I have a div with a table inside and a form at the bottom. The div is a popup and has a draggable functionality, but when I select any text inside the div using the mouse, the div moves. Currently, the only way to select text is through double-click.
What's the way to achieve this in Javascript?
Here's the code for the drag functionality:
var drag = new Object();
drag.obj = editTable;
drag.obj.addEventListener('mousedown', function(e) {
drag.top = parseInt(drag.obj.offsetTop);
drag.left = parseInt(drag.obj.offsetLeft);
drag.oldx = drag.x;
drag.oldy = drag.y;
drag.drag = true;
});
window.addEventListener('mouseup', function() {
drag.drag = false;
});
window.addEventListener('mousemove', function(e) {
drag.x = e.clientX;
drag.y = e.clientY;
var diffw = drag.x - drag.oldx;
var diffh = drag.y - drag.oldy;
if (drag.drag) {
drag.obj.style.left = drag.left + diffw + 'px';
drag.obj.style.top = drag.top + diffh + 'px';
e.preventDefault();
}
});
Thanks in advance!

Jquery multiple div same class, drag just "this" div

I'm using a drag function (not jquery UI, I don't want Jquery UI) on a div with a class, but I have more div with the same class, How can I intercept the click on this div
Here my code:
$(function(){
var dragging = false;
var iX, iY;
// how can I intercept $(this, ".marker") ??
$(".marker").mousedown(function(e) {
dragging = true;
iX = e.clientX - this.offsetLeft;
iY = e.clientY - this.offsetTop;
this.setCapture && this.setCapture();
return false;
});
document.onmousemove = function(e) {
if (dragging) {
var e = e || window.event;
var oX = e.clientX - iX;
var oY = e.clientY - iY;
// how can I find the ID related to this div ?
$('.marker').css({"left":oX + "px", "top":oY + "px"});
return false;
}
};
$(document).mouseup(function(e) {
dragging = false;
// $(".marker")[0].releaseCapture();
e.cancelBubble = true;
})
})
And here the original codePen https://codepen.io/Jonh/pen/jgyLB
Here the JS that create the div, they are created by JS dynamically
for (i = 0; i < theFeat.length; i++) {
markerText = theFeat[i].value;
mk_totalHeight += 50;
$('<div>', { id: 'd' + i, style:"top:" + mk_totalHeight + "px;"}).addClass('marker').appendTo('#map');
$('<div>', { id: i, style:"background-image:url('../../../symbols/marker" + (i + 1) + ".png');" }).addClass('markerIcon').appendTo('#d'+ i);
$('<span>', { id: 's' + i, text: markerText}).appendTo('#d' + i);
} // end for loop
It's useful if you have just one div but how can I implement that on a class that is on a various div, I have to copy the function and add as a select the ID? (the max allowed div are 4, so it will be #d0, #d1, #d2, #d3)
So one of your listeners,
// how can I intercept $(this, ".marker") ??
$(".marker").mousedown(function(e) {
dragging = true;
iX = e.clientX - this.offsetLeft;
iY = e.clientY - this.offsetTop;
this.setCapture && this.setCapture();
return false;
});
...won't, in fact, work as you expect. Because the .marker elements are being created dynamically, event listeners on them will fail. Rather, listen at the nearest parent node:
// Try something like this:
$("#map").on("mousedown", ".marker", function(e){
// Either of the following should get to the element that
// triggered the mousedown
var target = $(this);
// OR YOU CAN USE
var target = $(e.currentTarget);
// ... whatever other processing you need...
});
After the comment of #Snowmonkey I notice that the problem was on document.onmousemove where do you change the CSS property, this is my solution.
I created a var to get the selected div
$(".marker").mousedown(function(e) {
dragging = true;
mdiv = $(this);
iX = e.clientX - this.offsetLeft;
iY = e.clientY - this.offsetTop;
this.setCapture && this.setCapture();
return false;
});
Then on the onmousemove function, I have the target stored on a var and I can get the ID
document.onmousemove = function(e) {
if (dragging) {
var e = e || window.event;
var oX = e.clientX - iX;
var oY = e.clientY - iY;
$("#" + mdiv.attr('id')).css({"left":oX + "px", "top":oY + "px"});
return false;
}
};

Detect container, while moving an element over it

I making a simple drag'n'drop interface. I have a bunch of containers ("wrapper") and some dynamically added items ("dragElement") in one of them. So I need, when I move item over another container, JS detect it and move the item there when the drag is finished.
I tried to detect container with "onmouseover" and "mouseup" when dragging item, but had no success, because, actually, mouse always was over the dragged element.
So how can I detect container when drag item? In pure JS please...
document.onmousedown = function(e) {
var dragElement = e.target;
if (!dragElement.classList.contains('draggable')) return;
var coords, shiftX, shiftY, detectPage;
startDrag(e.clientX, e.clientY);
document.onmousemove = function(e) {
moveAt(e.clientX, e.clientY);
};
wrapper.onmouseover = function(e) {
detectPage = e.target;
console.log(detectPage);
};
dragElement.onmouseup = function() {
finishDrag();
};
function startDrag(clientX, clientY) {
shiftX = clientX - dragElement.getBoundingClientRect().left;
shiftY = clientY - dragElement.getBoundingClientRect().top;
dragElement.style.position = 'fixed';
document.body.appendChild(dragElement);
moveAt(clientX, clientY);
};
function finishDrag() {
dragElement.style.top = parseInt(dragElement.style.top) - wrapper.getBoundingClientRect().top + 'px';
dragElement.style.position = 'absolute';
wrapper.onmouseup = function(e) {
var selectPage = e.target;
}
wrapper.appendChild(dragElement);
document.onmousemove = null;
dragElement.onmouseup = null;
};
function moveAt(clientX, clientY) {
var newX = clientX - shiftX;
var newY = clientY - shiftY;
if (newX < 0) newX = 0;
if (newX > wrapper.offsetWidth - dragElement.offsetWidth) {
newX = wrapper.offsetWidth - dragElement.offsetWidth;
}
dragElement.style.left = newX + 'px';
dragElement.style.top = newY + 'px';
};
return false;
};
Well, no one help. So one free day gone to find the solution. All I can found is to delete function finishDrag() from dragElement.onmouseup and change it to the code below.
If in shorter, when onmouseup comes, dragElement must go to display:none and now we can get access to the object near the mouse cursor through elementFromPoint. When we done with it, we can easily detects container, bring an element back to display:block and put it to that container...
Hope, it helps to someone...
dragElement.onmouseup = function(e) {
dragElement.style.display = 'none';
var selectPage = document.elementFromPoint(e.clientX, e.clientY);
dragElement.style.display = 'block';
dragElement.style.top = parseInt(dragElement.style.top) - selectPage.getBoundingClientRect().top + 'px';
dragElement.style.position = 'absolute';
selectPage.appendChild(dragElement);
document.onmousemove = null;
dragElement.onmouseup = null;
};

Position of click within div

I'm trying to get the position of a click inside a div so that when I position a window as the mouse drag moves it, the mouse cursor will be exactly on the spot (relative to the moving window) where the initial click occurred.
Here's the window:
<div id="PopUp" class="Popup">
<div id="PopUpTitleBar"><img class="xOut" onclick="ClosePopUp();" src="/images/xOut.png"></div>
<div class="InnerPopup">
<!-- <p>Here is the body of a pop up element.</p> -->
<p id="PopUpBody"></p>
</div>
</div>
And I have these methods to handle the clicking and dragging:
var firstClick = true;
var offsetX = 0;
var offsetY = 0;
function mouseUp()
{
window.removeEventListener('mousemove', divMove, true);
firstClick = true;
}
function mouseDown(e){
window.addEventListener('mousemove', divMove, true);
}
function divMove(e){
var div = document.getElementById('PopUp');
if (firstClick == true) {
offsetX = $('#PopUpTitleBar').offset().left;
offsetY = $('#PopUpTitleBar').offset().top;
firstClick = false;
}
var spotX = e.pageX - offsetX;
var spotY = e.pageY - offsetY;
div.style.top = spotY + 'px';
div.style.left = spotX + 'px';
}
This sorta works, except that my offsetX and offsetY are returning the position of the top left corner of PopUpTitleBar instead of the position of the click within PopUpTitleBar.
How can I correct this so my offsets are relative to the inside top left corner of PopUpTitleBar?
Thanks.
You have the click position on the screen. And then you have the position of the div. Substract the main position with the div position and youll have the finger position relative to that div.
$('#PopUpTitleBar').click(function (e) {
var posX = $(this).offset().left, posY = $(this).offset().top;
alert("e.pageX: " + e.pageX + " posX:" + posX + " e.pageY:" + e.pageY + " posY:" + posY);
});
To get the value of the Position of click within div use the event e parameter in your callback.
function divMove(e){
var div = document.getElementById('PopUp');
if (firstClick == true) {
// use $(e).target
offsetX = $( $(e).target ).offset().left;
offsetY = $( $(e).target ).offset().top;
firstClick = false;
}
var spotX = e.pageX - offsetX;
var spotY = e.pageY - offsetY;
div.style.top = spotY + 'px';
div.style.left = spotX + 'px';
}
This is what ultimately worked:
function divMove(e){
var div = document.getElementById('PopUp');
var titleBar = document.getElementById('PopUpTitleBar');
if (firstClick == true) {
offsetX = e.pageX - $('#PopUpTitleBar').offset().left;
offsetY = e.pageY - $('#PopUpTitleBar').offset().top;
firstClick = false;
}
var spotX = e.pageX - offsetX;
var spotY = e.pageY - offsetY;
div.style.top = spotY + 'px';
div.style.left = spotX + 'px';
}
First, I calculate the position in the titlebar by subtracting the top left corner of the title bar from the point at which the first click occurred. Then, as the mouse moves, the subsequent positions of the mouse must all subtract that offset so that the top left corner remains the same distance from the original click. Thanks everyone for the suggestions - they helped me wrap my head around the problem better.

1000 DOM elements on a single page

For a project of big "text map" BigPicture, I need to have more than 1000 text inputs.
When you click + drag, you can "pan" the displayed area.
But the performance is very poor (both on Firefox and Chrome) : rendering 1000+ DOM elements is not fast at all.
Of course, another solution with better performance would be : work on a <canvas>, render text as bitmap on it, and each time we want to edit text, let's show a unique DOM <textarea>, that disappears what editing is finished, and text is rendered as bitmap again... It works (I'm currently working in this direction) but it needs much more code in order to provide editing on a canvas.
Question : Is it possible to improve performance for rendering of 1000+ DOM elements on a HTML page, so that I don't need to use <canvas> at all ?
Or will it be impossible to have good performance when panning a page with 1000+ DOM elements ?
Notes :
1) In the demo here I use <span contendteditable="true"> because I want multiline input + autoresize, but the rendering performance is the same with standard <textarea>.*
2) For reference, this is how I create the 1000 text elements.
for (i=0; i < 1000; i++)
{
var blax = (Math.random()-0.5)*3000;
var blay = (Math.random()-0.5)*3000;
var tb = document.createElement('span');
$(tb).data("x", blax / $(window).width());
$(tb).data("y", blay / $(window).height());
$(tb).data("size", 20 * currentzoom);
tb.contentEditable = true;
tb.style.fontFamily = 'arial';
tb.style.fontSize = '20px';
tb.style.position = 'absolute';
tb.style.top = blay + 'px';
tb.style.left = blax + 'px';
tb.innerHTML="newtext";
document.body.appendChild(tb);
}
For something like this you could make use of document fragment, these are DOM nodes that are not part of the actually DOM tree (more info can be found here https://developer.mozilla.org/en-US/docs/Web/API/document.createDocumentFragment), so you can do all your setup on the fragment and then append the fragment which will only be causing the one re flow rather than 1000.
So here is an example -http://jsfiddle.net/leighking2/awzoz7bj/ - a quick check on run time it takes around 60-70ms to run
var currentzoom = 1;
var docFragment = document.createDocumentFragment();
var start = new Date();
for (i=0; i < 1000; i++)
{
var blax = (Math.random()-0.5)*3000;
var blay = (Math.random()-0.5)*3000;
var tb = document.createElement('span');
$(tb).data("x", blax / $(window).width());
$(tb).data("y", blay / $(window).height());
$(tb).data("size", 20 * currentzoom);
tb.contentEditable = true;
tb.style.fontFamily = 'arial';
tb.style.fontSize = '20px';
tb.style.position = 'absolute';
tb.style.top = blay + 'px';
tb.style.left = blax + 'px';
tb.innerHTML="newtext";
docFragment.appendChild(tb);
}
document.body.appendChild(docFragment);
var end = new Date();
console.log(end-start)
compared to the original which took around 645ms to run http://jsfiddle.net/leighking2/896pusex/
UPDATE So for improving the dragging speed again keep the individual edits out of the DOM to avoid the cost of the reflow 1000 times every mouse drag
so here is one way using jquery's detach() method (example http://jsfiddle.net/sf72ubdt/). This will remove the elements from the DOM but give them to you with all their properties so you can manipulate them and reinsert them later on
redraw = function(resize) {
//detach spans
var spans = $("span").detach();
//now loop other them, because they are no longer attached to the DOM any changes are
//not going to cause a reflow of the page
$(spans).each(function(index) {
var newx = Math.floor(($(this).data("x") - currentx) / currentzoom * $(window).width());
var newy = Math.floor(($(this).data("y") - currenty) / currentzoom * $(window).height());
if (resize) {
displaysize = Math.floor($(this).data("size") / currentzoom);
if (displaysize) {
$(this).css({
fontSize: displaysize
});
$(this).show();
} else
$(this).hide();
}
//changed this from offset as I was getting a weird dispersing effect around the mouse
// also can no longer test for visible but i assume you want to move them all anyway.
$(this).css({
top: newy + 'px',
left: newx + 'px'
});
});
//reattach to the body
$("body").append(spans);
};
UPDATE 2 -
So to get a little more performance out of this you can cache the window width and height, use a vanilla for loop, use vanilla js to change the css of the span. Now each redraw (on chrome) takes around 30-45 ms (http://jsfiddle.net/leighking2/orpupsge/) compared to my above update which saw them at around 80-100ms (http://jsfiddle.net/leighking2/b68r2xeu/)
so here is the updated redraw
redraw = function (resize) {
var spans = $("span").detach();
var width = $(window).width();
var height = $(window).height();
for (var i = spans.length; i--;) {
var span = $(spans[i]);
var newx = Math.floor((span.data("x") - currentx) / currentzoom * width);
var newy = Math.floor((span.data("y") - currenty) / currentzoom * height);
if (resize) {
displaysize = Math.floor(span.data("size") / currentzoom);
if (displaysize) {
span.css({
fontSize: displaysize
});
span.show();
} else span.hide();
}
spans[i].style.top = newy + 'px',
spans[i].style.left = newx + 'px'
}
$("body").append(spans);
};
SNIPPET EXAMPLE -
var currentzoom = 1;
var docFragment = document.createDocumentFragment();
var start = new Date();
var positions = []
var end = new Date();
console.log(end - start);
var currentx = 0.0,
currenty = 0.0,
currentzoom = 1.0,
xold = 0,
yold = 0,
button = false;
for (i = 0; i < 1000; i++) {
var blax = (Math.random() - 0.5) * 3000;
var blay = (Math.random() - 0.5) * 3000;
var tb = document.createElement('span');
$(tb).data("x", blax / $(window).width());
$(tb).data("y", blay / $(window).height());
$(tb).data("size", 20 * currentzoom);
tb.contentEditable = true;
tb.style.fontFamily = 'arial';
tb.style.fontSize = '20px';
tb.style.position = 'absolute';
tb.style.top = blay + 'px';
tb.style.left = blax + 'px';
tb.innerHTML = "newtext";
docFragment.appendChild(tb);
}
document.body.appendChild(docFragment);
document.body.onclick = function (e) {
if (e.target.nodeName == 'SPAN') {
return;
}
var tb = document.createElement('span');
$(tb).data("x", currentx + e.clientX / $(window).width() * currentzoom);
$(tb).data("y", currenty + e.clientY / $(window).height() * currentzoom);
$(tb).data("size", 20 * currentzoom);
tb.contentEditable = true;
tb.style.fontFamily = 'arial';
tb.style.fontSize = '20px';
tb.style.backgroundColor = 'transparent';
tb.style.position = 'absolute';
tb.style.top = e.clientY + 'px';
tb.style.left = e.clientX + 'px';
document.body.appendChild(tb);
tb.focus();
};
document.body.onmousedown = function (e) {
button = true;
xold = e.clientX;
yold = e.clientY;
};
document.body.onmouseup = function (e) {
button = false;
};
redraw = function (resize) {
var start = new Date();
var spans = $("span").detach();
var width = $(window).width();
var height = $(window).height();
for (var i = spans.length; i--;) {
var span = $(spans[i]);
var newx = Math.floor((span.data("x") - currentx) / currentzoom * width);
var newy = Math.floor((span.data("y") - currenty) / currentzoom * height);
if (resize) {
displaysize = Math.floor(span.data("size") / currentzoom);
if (displaysize) {
span.css({
fontSize: displaysize
});
span.show();
} else span.hide();
}
spans[i].style.top = newy + 'px',
spans[i].style.left = newx + 'px'
}
$("body").append(spans);
var end = new Date();
console.log(end - start);
};
document.body.onmousemove = function (e) {
if (button) {
currentx += (xold - e.clientX) / $(window).width() * currentzoom;
currenty += (yold - e.clientY) / $(window).height() * currentzoom;
xold = e.clientX;
yold = e.clientY;
redraw(false);
}
};
$(function () {
$('body').on('mousedown', 'span', function (event) {
if (event.which == 3) {
$(this).remove()
}
})
});
zoomcoef = function (coef) {
middlex = currentx + currentzoom / 2
middley = currenty + currentzoom / 2
currentzoom *= coef
currentx = middlex - currentzoom / 2
currenty = middley - currentzoom / 2
redraw(true)
}
window.onkeydown = function (event) {
if (event.ctrlKey && event.keyCode == 61) {
zoomcoef(1 / 1.732);
event.preventDefault();
}
if (event.ctrlKey && event.keyCode == 169) {
zoomcoef(1.732);
event.preventDefault();
}
if (event.ctrlKey && event.keyCode == 48) {
zoomonwidget(1 / 1.732);
event.preventDefault();
}
};
html, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
A solution was given by #Shmiddty which is much faster to all previous attempts : all elements should be wrapped, and only the wrapper has to be moved (instead of moving each element) :
http://jsfiddle.net/qhskacsw/
It runs smooth and fast even with 1000+ DOM elements.
var container = document.createElement("div"),
wrapper = document.createElement("div"),
dragging = false,
offset = {x:0, y:0},
previous = {x: 0, y:0};
container.style.position = "absolute";
wrapper.style.position = "relative";
container.appendChild(wrapper);
document.body.appendChild(container);
for (var i = 1000, span; i--;){
span = document.createElement("span");
span.textContent = "banana";
span.style.position = "absolute";
span.style.top = (Math.random() * 3000 - 1000 | 0) + 'px';
span.style.left = (Math.random() * 3000 - 1000 | 0) + 'px';
wrapper.appendChild(span);
}
// Don't attach events like this.
// I'm only doing it for this proof of concept.
window.ondragstart = function(e){
e.preventDefault();
}
window.onmousedown = function(e){
dragging = true;
previous = {x: e.pageX, y: e.pageY};
}
window.onmousemove = function(e){
if (dragging){
offset.x += e.pageX - previous.x;
offset.y += e.pageY - previous.y;
previous = {x: e.pageX, y: e.pageY};
container.style.top = offset.y + 'px';
container.style.left = offset.x + 'px';
}
}
window.onmouseup = function(){
dragging = false;
}
IMHO, I would go with your current thinking to maximize performance.
Reason: 1000+ DOM elements will always limit performance.
Yes, there is slightly more coding but your performance should be much better.
create one large offscreen canvas containing all 1000 texts.
Use context.textMeasure to calculate the bounding box of all 1000 texts relative to the image.
Save the info about each text in an object
var texts=[];
var texts[0]={ text:'text#0', x:100, y:100, width:35, height:20 }
...
context.drawImage that image on a canvas using an offset-X to 'pan' the image. This way you only have 1 canvas element instead of 1000 text elements.
In the mousedown handler, check if the mouse position is inside the bounding box of any text.
If the mouse is clicked inside a text bounding box, absolutely position an input-type-text directly over the text on the canvas. This way you only need 1 input element which can be reused for any of the 1000 texts.
Use the abilities of the input element to let the user edit the text. The canvas element has no native text editing abilities so don't "recreate the wheel" by coding canvas text editing.
When the user is done editing, recalculate the bounding box of the newly edited text and save it to the text object.
Redraw the offscreen canvas containing all 1000 texts with the newly edited text and draw it to the onscreen canvas.
Panning: if the user drags the onscreen canvas, draw the offscreen canvas onto the onscreen canvas with an offset equal to the distance the user has dragged the mouse. Panning is nearly instantaneous because drawing the offscreen canvas into the onscreen canvas-viewport is much, much faster than moving 1000 DOM input elements
[ Addition: full example with editing and panning ]
**Best Viewed In Full Screen Mode**
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var texts=[];
var fontSize=12;
var fontFace='arial';
var tcanvas=document.createElement("canvas");
var tctx=tcanvas.getContext("2d");
tctx.font=fontSize+'px '+fontFace;
tcanvas.width=3000;
tcanvas.height=3000;
var randomMaxX=tcanvas.width-40;
var randomMaxY=tcanvas.height-20;
var panX=-tcanvas.width/2;
var panY=-tcanvas.height/2;
var isDown=false;
var mx,my;
var textCount=1000;
for(var i=0;i<textCount;i++){
var text=(i+1000);
texts.push({
text:text,
x:parseInt(Math.random()*randomMaxX),
y:parseInt(Math.random()*randomMaxY)+20,
width:ctx.measureText(text).width,
height:fontSize+2,
});
}
var $textbox=$('#textbox');
$textbox.css('left',-200);
$textbox.blur(function(){
$textbox.css('left',-200);
var t=texts[$textbox.textsIndex]
t.text=$(this).val();
t.width=ctx.measureText(t.text).width;
textsToImage();
});
textsToImage();
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUpOut(e);});
$("#canvas").mouseout(function(e){handleMouseUpOut(e);});
// create one image from all texts[]
function textsToImage(){
tctx.clearRect(0,0,tcanvas.width,tcanvas.height);
for(var i=0;i<textCount;i++){
var t=texts[i];
tctx.fillText(t.text,t.x,t.y)
tctx.strokeRect(t.x,t.y-fontSize,t.width,t.height);
}
redraw();
}
function redraw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(tcanvas,panX,panY);
}
function handleMouseDown(e){
e.preventDefault();
e.stopPropagation();
mx=parseInt(e.clientX-offsetX);
my=parseInt(e.clientY-offsetY);
// is the mouse over a text?
var hit=false;
var x=mx-panX;
var y=my-panY;
for(var i=0;i<texts.length;i++){
var t=texts[i];
if(x>=t.x && x<=t.x+t.width && y>=t.y-fontSize && y<=t.y-fontSize+t.height){
$textbox.textsIndex=i;
$textbox.css({'width':t.width+5, 'left':t.x+panX, 'top':t.y+panY-fontSize});
$textbox.val(t.text);
$textbox.focus();
hit=true;
break;
}
}
// mouse is not over any text, so start panning
if(!hit){isDown=true;}
}
function handleMouseUpOut(e){
e.preventDefault();
e.stopPropagation();
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
e.preventDefault();
e.stopPropagation();
var mouseX=parseInt(e.clientX-offsetX);
var mouseY=parseInt(e.clientY-offsetY);
panX+=mouseX-mx;
panY+=mouseY-my;
mx=mouseX;
my=mouseY;
redraw();
}
body{ background-color: ivory; padding:10px; }
#wrapper{position:relative; border:1px solid blue; width:600px; height:600px;}
#textbox{position:absolute;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Click on #box to edit.<br>Tab to save changes.<br>Drag on non-text.</h4><br>
<div id=wrapper>
<input type=text id=textbox>
<canvas id="canvas" width=600 height=600></canvas>
</div>
<button></button>
I just run couple tests and it seems that moving absolutely positioned (position:absolute;) DOM elements (divs) with CSS transform:translate is even faster (by about 30%) than doing it via Canvas. But I was using CreateJS framework for the canvas job so my results may not hold for other use cases.

Categories

Resources