Raphael: How to unbind the mousemove function on mouseup? - javascript

I am drawing a line with Raphael. I have a mousedown event where I store the starting position. While the mouse is down, if the user moves it, I have a mousemove event where I keep drawing the line as the mouse moves.
Now when the mouse button is released, the line should stop. This does not happen and line keeps going on if the mouse moves even though button is released. This line must stop on mouseup. If the user does mousedown again, it should begin a new line.
I have tried many combinations with the unmouse* events, but I am missing something here.
JSFiddle at: http://jsfiddle.net/zaphod013/P33FA/5/
(This is my first date with JS/Raphael. So if you think I am totally off track here, please tell me so)
var g_masterPaper;
var g_startX;
var g_startY;
var g_line;
function initDrawing() {
g_masterPaper = Raphael(10,10,700,500);
var masterBackground = g_masterPaper.rect(10,10,600,400);
masterBackground.attr("fill", "#eee");
var drawit = function(event) {
x = event.pageX - 10;
y = event.pageY - 10;
var linepath = ("M"+g_startX+" "+g_startY+" L"+x+" "+y);
g_line.attr("path", linepath);
};
var startit = function (event) {
g_startX = event.pageX - 10;
g_startY = event.pageY - 10;
g_line = g_masterPaper.path("M"+g_startX+" "+g_startY+" L"+g_startX+" "+g_startY);
masterBackground.mousemove(drawit);
};
masterBackground.mousedown(startit);
masterBackground.mouseup(function (event) {
this.unmousedown(startit);
this.unmousemove(drawit);
});
return g_masterPaper;
}
window.onload = function () {
var paper=initDrawing();
paper.text(15,475,'Use your mouse to draw.').attr({fill:'#ff0000', 'font-size':24, 'stroke-width':1,'text-anchor':'start' });
}

I think you're on track and mostly looks fine, I would possibly just simplify your handlers, and not keep removing/adding them, it keeps handlers hard to track and debug. So I would just have one handler for down/up/move...
Edit:
jsfiddle here or here which gets around other elements capturing the mouseup event and stopping it working properly.
var drawit = function(event) {
event.preventDefault();
x = event.pageX - 10;
y = event.pageY - 10;
var linepath = ("M"+g_startX+" "+g_startY+" L"+x+" "+y);
if( g_line ) { g_line.attr("path", linepath) };
};
var startit = function (event) {
event.preventDefault();
g_startX = event.pageX - 10;
g_startY = event.pageY - 10;
g_line = g_masterPaper.path("M"+g_startX+" "+g_startY+" L"+g_startX+" "+g_startY);
};
var finish = function ( event ) {
g_line = '';
}

Related

Executing a line in a conditional AND by default on start

I have this fiddle: http://jsfiddle.net/zhaem/Lyzrtefo/7/
var orangeMode = true
var isTracking = true
getMouseXY = function(e) {
if (isTracking) {
var tempX = e.pageX
var tempY = e.pageY
if (tempX < 0){tempX = 0}
if (tempY < 0){tempY = 0}
document.getElementById("circle1").style.top = (tempY - 25) + "px";
document.getElementById("circle1").style.left = (tempX - 25) + "px";
}
return true
}
document.onmousemove = getMouseXY;
var toggleTrackCircle = function() {
isTracking = !isTracking;
console.log(isTracking);
}
document.getElementById("circle1").addEventListener("click", toggleTrackCircle);
flip = function() {
orangeMode = !orangeMode;
if (orangeMode) {
document.getElementById("circle1").style.backgroundColor = "orange";
document.getElementById("circle1").addEventListener('mouseover', function() {isTracking = true;})
// When the above line is executed the circle will stick to your cursor on HOVER after clicking and setting it down.
} else {
document.getElementById("circle1").style.backgroundColor = "blue";
}
}
document.getElementById("box3").addEventListener("click", flip);
There is this one line, that when present will change the behavior of the interaction. (You can always click to stop the cursor from tracking you, but when this line is there, the circle will re-stick to it on hover, and when it's not, it will only restick on hover + click.
document.getElementById("circle1").addEventListener('mouseover', function() {isTracking = true;})
I'm trying to wrap it in some conditional logic in the flip function (which you can control by tapping the red box in the corner) so that orangeMode == it sticks on hover and not orangeMode it only re-sticks on click.
The flip function works fine for changing the color, but this event listener isn't performing like I'd want. (once you cycle through it does work but for either state and it's not running orangeMode unload.
Any help would be much appreciated.
You need to put 'isTracking=true;' inside a named function, so you can use "removeEventListener" to take the function off. That means you add this function:
var trackCircle = function() {
isTracking = true;
}
Then you reference that on click instead of the anonymous function:
circle.addEventListener('mouseover', trackCircle)
Then you simply remove that event in your else:
circle.removeEventListener('mouseover', trackCircle)
Oh, and I'd add this at the top so you don't need to keep repeating the getElementById phrase:
circle = document.getElementById("circle1");
Hope that helps. Oh, and here's the edit to your fiddle:
http://jsfiddle.net/Lyzrtefo/9/

JavaScript setTimeout() can't trigger itself

How can a function, which is triggered by another function, get the mouse's position? Here's my code:
function myFunction(e){
setTimeout(function(){
if(isMouseDown == true){
mouseX = e.clientX;
mouseY = e.clientY;
document.getElementById('myElement').innerHTML = mouseX + ' , ' + mouseY;
myFunction(event);
} else {}
}, 100);
}
What this does is to display the coordinates when clicked. I need it to display them every 100ms if isMouseDown == true.
Thanks
There is no way in Javascript for a random Javascript function to get the mouse position. The current mouse position only comes from an event object for a mouse-related event. So, if you want to keep track of the mouse position, then you can register an event handler for the mousemove event and for mousedown and mouseup to keep track of the button state.
If you only want to display the mouse position, ever 100ms, then you can set a timer so that it is only displayed that often, but you will need to keep track of the current mouse position in a mousemove event handler.
Here's a simple code example:
var lastMouseX = 0, lastMouseY = 0;
document.addEventListener("mousemove", function(e) {
lastMouseX = e.clientX;
lastMouseY = e.clientY;
});
var mouseTimer;
document.addEventListener("mousedown", function() {
if (!mouseTimer) {
mouseTimer = setInterval(function() {
document.getElementById("x").innerHTML = lastMouseX;
document.getElementById("y").innerHTML = lastMouseY;
}, 100);
}
});
document.addEventListener("mouseup", function() {
clearInterval(mouseTimer);
mouseTimer = null;
});
<div id="x"></div>
<div id="y"></div>
It's a bit fuzzy what you're trying to achieve, however you're not going to get a periodic event if you're usingsetTimeout(). It sounds like you're looking for setInterval(). See the below example:
var mouseX = 0;
var mouseY = 0;
var isMouseDown = true;
document.onmousemove = function(e){
mouseX = e.pageX;
mouseY = e.pageY;
}
setInterval("myFunction()", 100);
function myFunction(){
if(isMouseDown) {
document.getElementById('myElement').innerHTML = mouseX + ' , ' + mouseY;
}
}
<div id="myElement"></div>

Do JavaScript event handlers get overwritten?

I am curious if binding an event to an object multiple times cause problems by stacking multiple event listeners on top of each other or do they override each other? For example if I have an attachHandlers function
function _attachHandlers() {
// Slider hover
var isDown = false;
$('.ui-slider-handle').mousedown(function () {
// Create tool tip
isDown = true;
var tt = $(document.createElement('span')).addClass('sk-tooltip');
var handle = $(this);
var left = parseInt(handle.css('left'));
var val = _convertSliderValue(Math.round(left / 5 / 14.2857));
tt.text(val);
handle.append(tt);
var newLeft = (handle.outerWidth() - tt.outerWidth()) / 2;
tt.css({
'left': newLeft
});
$(document).mousemove(function (event) {
var left = parseInt(handle.css('left'));
var val = _convertSliderValue(Math.round(left / 5 / 14.2857));
tt.text(val);
var newLeft = (handle.outerWidth() - tt.outerWidth()) / 2;
tt.css({
'left': newLeft
});
});
});
$(document).mouseup(function () {
if (isDown) {
$(document).unbind('mousemove');
$(this).find('.sk-tooltip').remove()
isDown = false;
}
});
}
I need to re-attach these handlers at some point in my code and therefore I was just going to call _attachHandlers() to re-bind them, however, I will also be re-binding the document listener for the mouseup event every time this happens. Therefore, is it alright to do this and will the event handler just get overwritten everytime or do I have to unbind the handler first before it can be re-bound?

Stop one event by another - Javascript/jQuery

I'm implementing drag and drop with jQuery.
So, after mousedown, event starts working:
On mousemove selected element moves with mouse.
Now, I want it to stop working on mouseup.
here's my code:
$(document).on("mousemove", widget, function() {
var newMouseX = event.pageX - mouseX;
var newMouseY = event.pageY - mouseY;
var newElemX = elemX + newMouseX;
var newElemY = elemY + newMouseY;
$(widget).offset({ top: newElemY , left: newElemX });
$(document).on("mouseup", widget, function(widget) {
// what to do here?
});
});
any suggestions?

Is there something like Jquery memory or cache?

I've wrote a Script with jQuery for 360deg product preview. It works just fine. But if I "play" with it for a long time (dragging, zooming in, zooming out etc.) it becames slower and slower. If I'm dragging mouse slowly it works ok, but it freezes on fast mousemoves. After page reload it works again fine for several minutes and then become slower.
What can cause such behaviour? Is there something like jQuery memory that becomes full?
Per request, some parts of code:
Loading images:
$.getJSON("load.php", {dir: 'images/'}, function(output) {
var imagelist = jQuery.makeArray(output.imagelist);
var zoomlist = jQuery.makeArray(output.zoomlist);
var cache = [];
function preload(arrayOfImages) {
$(arrayOfImages).each(function(){
var im = $("<img>").attr("src",this);
cache.push(im);
image.attr('src', this);
});
}
preload(imagelist);
Rotation part
holder.mousedown(function(e){
var enterPosition = e.pageX - this.offsetLeft;
isDown = true;
$(document).mousemove(function(e){
if(isDown && !isZoom){
var cursorPosition = e.pageX - contOffset.left;
var xOffset = cursorPosition - enterPosition;
var step = Math.round(contWidth/countFrames);
var frameOffset = Math.round(xOffset/step);
var cycles = Math.abs(Math.floor((frameOffset+startFrame)/countFrames));
currentFrame = startFrame + frameOffset;
if(currentFrame >= countFrames){
currentFrame = currentFrame - countFrames*cycles;
}
if(currentFrame < 0){
currentFrame = countFrames*cycles + currentFrame;
}
image.attr('src', imagelist[currentFrame]);
$('#info').html(currentFrame);
var corner = Math.floor(360/countFrames);
var degrees = corner*currentFrame;
var radians=degrees*Math.PI/180;
var sine=Math.sin(radians);
var cose=Math.cos(radians);
var poinx = rotCenter+rotRadius*sine*-1;
var poiny = rotCenter+rotRadius*cose
$('#pointer').css('left',poinx);
$('#pointer').css('top',poiny);
};
});
$(document).mouseup(function(){
isDown = false;
startFrame = currentFrame;
});
});
Zooming part
$('#zoom').click(function(e){
var isZoom = true;
var offset = holder.offset();
var startXpos = e.pageX - offset.left;
var startYpos = e.pageY - offset.top;
var zoomImg = new Image();
zoomImg.onload = function() {
zoomHeight = zoomImg.height;
zoomWidth = zoomImg.width;
var leftOverflow = (zoomWidth - contWidth)/-2;
var topOverflow = (zoomHeight - contHeight)/-2;
image.attr('src', zoomlist[currentFrame]);
image.css('left', leftOverflow);
image.css('top', topOverflow);
$('#round').fadeOut();
$('#zoom').fadeOut();
holder.addClass('zoomout');
holder.mousemove(function(e){
if(isZoom){
var currentXpos = e.pageX - offset.left;
var currentYpos = e.pageY - offset.top;
var xlimit = (zoomWidth-contWidth)*-1;
var ylimit = (zoomHeight-contHeight)*-1;
var xSpeedCoeff = Math.floor(zoomWidth/contWidth);
var ySpeedCoeff = Math.floor(zoomHeight/contHeight);
var moveLeft = startXpos - currentXpos;
var moveTop = startYpos - currentYpos;
var leftOffset = leftOverflow + moveLeft*xSpeedCoeff;
var topOffset = topOverflow + moveTop*ySpeedCoeff;
var hMoveLock = false;
var vMoveLock = false;
if(leftOffset >= 0){
hMoveLock = true;
startXpos = startXpos - leftOffset;
}
if(leftOffset <= xlimit){
hMoveLock = true;
startXpos = startXpos - leftOffset + xlimit;
}
if(topOffset >= 0){
vMoveLock = true;
startYpos = startYpos - topOffset;
}
if(topOffset <= ylimit){
vMoveLock = true;
startYpos = startYpos - topOffset + ylimit;
}
if(!hMoveLock) {
image.css('left', leftOffset);
}
if(!vMoveLock) {
image.css('top', topOffset);
}
holder.mousedown(function(){
image.attr('src', imagelist[currentFrame]);
image.css('left', 0);
image.css('top', 0);
$('#round').fadeIn();
$('#zoom').fadeIn();
holder.removeClass('zoomout');
pan = false;
isZoom = false;
});
}
});
}
zoomImg.src = zoomlist[currentFrame];
});
I know, the code is not clear, and as it here now, I would be thankful for any advice.
There are lots of reasons this might happen, it's impossible to say without seeing the code see below for an update now that you've posted the code:
A couple of possibilities off the top of my head:
Yes, you could be allocating lots of objects and then either not releasing them, or the garbage collector is being slow.
You could be inadvertently re-attaching event handlers over and over, and so the events (which end up triggering all attached handlers) slow down because of the number of (redundant) handlers attached.
Update after you posted your code:
It's #2, this is the offending code (it may not be the only offending code):
holder.mousedown(function(e){
var enterPosition = e.pageX - this.offsetLeft;
isDown = true;
$(document).mousemove(function(e){
// ...
});
$(document).mouseup(function(){
// ...
});
});
What you're doing there is when the mousedown event fires on the holder element(s), you're adding a new handler for mousemove and mouseup to the document, on top of any handlers that are already there. So every mousedown introduces a new handler to the chain. Since mousemove happens a lot, that ever-increasing chain of handlers gets called a lot.
You should either only be attaching the mousemove and mouseup handlers once, not on every mousedown, or you should be sure to remove them on mouseup. (The latter will require that you don't use anonymous functions as you are currently, because you need to pass the same function reference into unbind that you passed [indirectly] into bind. Edit: Or you can use jQuery's "namespaced" event stuff.)
FWIW, this should get you started on the attach-it-once version:
(function() { // Scoping function so isDown and enterPosition aren't globals
var isDown = false,
enterPosition;
// I don't know where `holder` or `startFrame` come from, but presumably you do
// Hook up mousedown on holder
holder.mousedown(function(e){
enterPosition = e.pageX - this.offsetLeft;
isDown = true;
});
// Hook up mousemove on document (just once)
$(document).mousemove(function(e){
// Flag controls whether we do anything
if(isDown && !isZoom){
var cursorPosition = e.pageX - contOffset.left;
var xOffset = cursorPosition - enterPosition;
var step = Math.round(contWidth/countFrames);
var frameOffset = Math.round(xOffset/step);
var cycles = Math.abs(Math.floor((frameOffset+startFrame)/countFrames));
currentFrame = startFrame + frameOffset;
if(currentFrame >= countFrames){
currentFrame = currentFrame - countFrames*cycles;
}
if(currentFrame < 0){
currentFrame = countFrames*cycles + currentFrame;
}
image.attr('src', imagelist[currentFrame]);
$('#info').html(currentFrame);
var corner = Math.floor(360/countFrames);
var degrees = corner*currentFrame;
var radians=degrees*Math.PI/180;
var sine=Math.sin(radians);
var cose=Math.cos(radians);
var poinx = rotCenter+rotRadius*sine*-1;
var poiny = rotCenter+rotRadius*cose
$('#pointer').css('left',poinx);
$('#pointer').css('top',poiny);
};
});
// Hook mouseup on document (just once)
$(document).mouseup(function(){
isDown = false;
startFrame = currentFrame;
});
})();
If your code is already within a scoping function, you don't need the new one I introduced.
There is a cache - you can access it with $.cache. And as T.J. Crowder said - it's most likely cause of you aren't cleaning up after yourself properly.
Do a Object.keys($.cache).length; in your console to check the size of cache - play for a while and check again to confirm that the cache grows to confirm jquery based leaks
and you are leaking cause on mouseup you are not unbinding your mouseup and mmousemove events
$(document).mouseup(function(){
$(document).unbind('mouseup').unbind('mousemove');
isDown = false;
startFrame = currentFrame;
});
this should help a lot
Problem was that basically every time you were pressing mouse down you were binding mousemove and mouseup again and again so after clicking few times all the computation was multiplied by amount of times you've pressed mouse down. You could also namespace the mousemove and mouseup events to unbind by namespace rather then two events separately.
Also caching variables might help a bit - especially with this kind of heavy operations - mousemove fires a lot
edit:
to remove events with anonymous functions use namespaces
var doc = $(document);
doc.bind('mousedown', function(e) {
doc.bind('mousemove.namespace', function(e) { ... });
doc.bind('mouseup.namespace', function(e) {
doc.unbind('.namespace');
// do whatever else you need to do on mouseup
});
});
just change namespace to whatever fits you best! Check jQuery docs for more info on namespaced events http://docs.jquery.com/Namespaced_Events
apart of that if you don't pass any function to the event type you want to unbind it will unbind all the events of given type regardless of its namespace or if it was named function or anonymous one
So, the problem was caused by the zoom function. What i did to solve it:
I have moved this part
holder.mousedown(function(){
image.attr('src', imagelist[currentFrame]);
image.css('left', 0);
image.css('top', 0);
$('#round').fadeIn();
$('#zoom').fadeIn();
holder.removeClass('zoomout');
pan = false;
isZoom = false;
});
outside of
holder.mousemove(function(e){
if(isZoom){
I've used namespaces to unbind mouse events.
holder.on('mousemove.dragpan', (function(e){
}));
holder.mousedown(function(){
holder.off('mousemove.dragpan');
});
Thx again for all the tipps!

Categories