Mouse:over event fires twice in Fabric.js - javascript

I'm trying to write a simple card game on canvas + Fabricjs. Need to make bigger player's card when mouse is on it and return to normal size if mouse is out.
Here is my js code:
canvas.on('mouse:over', function(e) {
if(e.target.mytype && e.target.mytype=='card'){
e.target.animate({
top:352,
left: e.target.left - (max_width - min_width)/2,
scaleX:0.4,
scaleY:0.4
} , {
duration: 500,
onChange: function(value) {
canvas.renderAll.bind(canvas);
},
easing: fabric.util.ease.easeInOut
});
canvas.renderAll();
}
});
canvas.on('mouse:out', function(e) {
if(e.target.mytype && e.target.mytype=='card'){
e.target.animate({
top:385,
left: e.target.left + (max_width - min_width)/2,
scaleX:0.3,
scaleY:0.3
} , {
duration: 500,
onChange: function(value) {
canvas.renderAll.bind(canvas);
},
easing: fabric.util.ease.easeInOut
});
canvas.renderAll();
}
});
if player hover the card for 200ms (animation duration is 500ms) and move out, the animation freeze and the card remains at a new position. Hovering again wil start animation from this new position.
Here is a fiddle
Just try to move mouse in/out on the object and you'll see the bug. Please, help me to fix this.

The problem is the way that your code has relative positioning changes within the animation action - in the jsFiddle, the "top" value, in your code above, in the "left" value.
What each of these do is move the element +X or -X from the position the event starts at. If the animation finishes before the mouseout event is fired, it's fine, because the amount it moves back is equal (but opposite) to the amount it moved in the mouseover event.
However, if the animation for mouseout starts Before the first is finished, it takes it's current position, not the position it will be at when it finishes the animation. This leads to a situation where the element drifts away from its original position. This is certainly what the issue is in the JSFiddle, I understand from your comments that this is the same issue in your own code.
How to resolve it? As long as you are using a fixed relative position value in the mouseout event you probably can't. You could try logging the initial positioning (i.e., the value of "left/top" you started at) in the mouseover function and returning specifically to that point in the mouseout event.

Related

Dragging element horizontally by 100px increments

I'm having a hard time figuring this out.
I have a bunch of divs (with some content inside them), and I want to be able to drag and drop them, horizontally. However, I want to move them by 100px increments (the left position needs to be 0, 100, 200 etc). Imagine having a table in the background with 100px wide cells and you can only move the element to another cell. Except there's no table.
jQuery is out of the question I think (I'm using Vue).
I won't write your code for you, but I'll help you figure it out by telling you where to start.
First, listen to the mousedown and mouseup events on the element:
<div v-on="{ mousedown, mouseup }">Some content</div>
Next register a mousemove listener on mousedown, and deregister it on mouseup:
methods: {
mousemove(e) {
const moved = e.offsetX - this.startX;
// The mouse has moved "moved" pixels.
// Now calculate whatever you want
},
mousedown(e) {
this.startX = e.offsetX;
e.currentTarget.addEventListener(this.mousemove);
},
mouseup(e) {
e.currentTarget.removeEventListener(this.mousemove);
},
}

Force Feeding not working in Velocity JS

I am trying to use VelocityJS in one of my React projects. The use case where I am facing the problem is:
There is pointer created in SVG, initially at the center of the screen. I want to move the pointer with the keyboard arrow keys. I have maintained a state variable for updating the X and Y values of the pointer on keydown event. On the callback of setState, I am calling a translate function which animates the translation. But the problem I faced is that The animation always happened from the initial position of the pointer i.e. center of the screen. So as a workaround I tried was that I maintained the previous position of the pointer in state too, using forcefeeding in velocityJS. Someone go through my code and see if I am doing something wrong.
$(".Aim").velocity(
{
translateX: [this.state.aimX, this.state.aimXOld],
translateY: [this.state.aimY, this.state.aimYOld]
},
{
duration: 500,
easing: [0.46, 0.46, 0.46, 0.46],
complete: () => {
console.log("completed");
}
}
);

JQuery Animate : Change the destination of a prop during the animation

I made a image scrolling with the mouse.
The image scroll to a position based on the mouse position percentage of the window height.
$(imageContainer).mouseenter(function(e){
var scrollingTo = ((e.pageY/$(this).height())-.083) * ( $(imageContainer).prop('scrollHeight') - $(imageContainer).height() );
hijacked = true;
$(imageContainer).animate({scrollTop:scrollingTo},300,function(){hijacked=false;});
}).mousemove(function(e){
if(hijacked) return;
var scrollingTo = ((e.pageY/$(this).height())-.083) * ( $(imageContainer).prop('scrollHeight') - $(imageContainer).height() );
$(imageContainer).scrollTop(scrollingTo);
});
So. in that line
$(imageContainer).animate({scrollTop:scrollingTo},300,function(){hijacked=false;});
I want that scrollingTo change. Because during the animation, the user can move the mouse, changing the scrollingTo variable.
Alright, I managed to cook together a hacky way of dynamically altering an animation. My understanding of the internal animation queue for jQuery is not great, but as far as I know there's no way to alter a queued animation, other than to make it stop. Anyway, here's the key code for an example that alters position, which should be adaptable to scrolling (in fiddle form):
$(document).ready(function () {
var last_update = 0;
$(document).on("mousemove", function (e) {
if (Date.now() - last_update > 50) {
$mover = $("#mover");
$mover.stop();
$mover.animate({ left: e.pageX, top: e.pageY}, 200, "linear");
last_update = Date.now();
}
});
});
There were a couple of tricks to make it work, I'll go through them and try to explain how I believe they could be adapted to scrolling:
The main idea is that on mousemove, the prior event is cancelled, and a new one is started.
I don't believe this will require any changes for scrolling.
Some forms of animation accelerate/decelerate over the course of the animation - it's too hard to preserve this in a constantly changing animation (at least without writing a custom animation function), so the animation easing is set to "linear".
rapidly changing animations takes time (especially for an event as common as mousemove), so there's a limit on how often the animation can change. Before a change to the animation is made, it's ensured that no changes have been made in the last .05 seconds (this is done with "last_update").
I believe if you just swap out the animation properties for your own (scrollTop), this should do what you're looking for.

Raphael JS - Start/Continue animating during mouseover. Pause animation on mouseout

Using Raphael JS, is there a way to make a circle move to the right (or any direction) during mouseover, and then pause/stop the movement when the cursor is no longer on the circle.
I've tried few different methods, but they have bugs. One of the main issues is: if the mouse cursor doesn't move after entering the circle, "mouseout" will not be triggered once the circle moves to a location where the mouse cursor is no longer over top of the circle.
You'll see what I mean in these different attempts:
1) Animate with pause / resume:
jsfiddle.net/fKKNt/
But the animation is jerky and unreliable. If you hover over the object, as the object moves outside of where the mouse cursor is, it doesn't trigger the "mouseout" listener.
2) Repositioning with mouseover & .attr("cx"):
jsfiddle.net/c4BFt/
But we want the animation to continue while the cursor is in the circle too.
3) Using setInterval (as suggested in:
An "if mouseover" or a "do while mouseover" in JavaScript/jQuery):
jsfiddle.net/9bBcm/
But "mouseout" is not called as the circle moves outside of where the cursor lies. I.e. the circle move to a location where "mouseout" should be called, but it is not called.
The same thing happens with "hover":
jsfiddle.net/STruB/
I'm sure there's a much more elegant way to do this, but off the top of my head, you could try something like this: http://jsfiddle.net/D6Ps4/2/
In case that disappears for some reason, I've included the code below. The solution simply initiates the animation, then checks to see if the mouse cursor (note the e.offsetX/e.offsetY) is within the bounding box of your Raphael Object (Element.getBBox()) at some set interval. If it is, do nothing and use setTimeout to check again in some time, if it's not, pause the animation.
var paper = Raphael("holder");
var animObject = Raphael.animation({cx: 400}, 5000);
circle = paper.circle(90, 90, 45).attr({fill: "#0E4"});
var timer;
circle.mouseover(function(e) {
var anim = function(shouldAnim) {
if (shouldAnim) {
circle.animate(animObject);
}
if (!mouseInsideCircle(e, circle)) {
circle.pause();
return;
} else {
timer = setTimeout(function() {anim(false)}, 20);
}
}
anim(true);
});
circle.mouseout(function() {
this.pause();
clearTimeout(timer);
});
var mouseInsideCircle = function(e, c) {
var bb = c.getBBox();
if (e.offsetX > bb.x && e.offsetY > bb.y) {
return true;
}
return false;
}
I'm sure the solution is flawed (it's checking the boundBox, not the circle itself; it also assumes the circle is moving right) and perhaps not ideal, but it seems to work reasonably smoothly and hopefully gets you on the right path.

jquery .animate() - this code sort of does what i want

I am trying to make a div slide down when the mouse moves over another div just above it. Basically the div above it is just the trigger that makes the div slide down. Mouseover of .trigger makes .slidedown expand, and mouseout of .slidedown makes itself slide back up. Here's the code i have so far:
$(document).ready(function() {
$('.slidedown').hide();
//When mouse rolls over
$('.trigger').mouseover(function(){
$('.slidedown').stop().animate({
height: ['toggle', 'swing'],
}, 600, function() {
// Animation complete.
});
});
//When mouse is removed
$('.slidedown').mouseout(function(){
$('.slidedown').stop().animate({
height:'0px'
}, 600, function() {
// Animation complete.
});
});
});
This works, but there are just two teaks i need help with. Firstly, after mouseout and the .slidedown div slides up and disappears, if i then mouse over the .trigger div again, nothing happens. It should make the .slidedown move down again. I need it to every time keep working. I tried removing the .stop() but it still doesn't work.
Also can i make it also slide back up if the mouse moves out of .trigger but only if it isn't moving out of .trigger into .slidedown? This is so incase the user doesn't move the mouse into .slidedown, it would remain forever which isn't good. Or just have a time limit that it can remain expanded if the mouse doesn't move over .slidedown.
Second, is there a way to make a delay of around 1 second between mouseout and the div sliding back up? Thanks for your help!
You might try using the jQuery hover event. For the delay, you can put the closing animation in setTimeout:
$(document).ready( function(){
$('.trigger').hover( function(){ // enter animation
$('.slidedown').stop(true,true).animate({
height: ['toggle', 'swing'],
}, 600, function() { /* animation done */ });
}, function(){ // leave animation
setTimeout( function(){
$('.slidedown').stop(true,true).animate({
height: '0px',
}, 600, function() { /* animation done */ });
}, 1000 );
});
});
You might also look into the hoverIntent plug-in for more nuanced control over the mouseenter/mouseleave behavior, including timing.
I think you'll find that setting a numerical height in $('.trigger').mouseover() may help the animation be repeatable. FYI, you can set an integer number for something like height or width in jQuery and it will automatically set the unit to px for you.
As Ken pointed out, setTimeout is useful for a delay in code, but keep it in your $('.slidedown').mouseout() event or the slideown div will hide after you mouseout of the trigger div instead of when you leave the slidedown div as you specified.

Categories