mouseMove very laggy - javascript

I am trying to obtain the image effect that 99designs is obtaining when hovering a mouse over a design.. [99designs.ca] Logo design contest: Runningbug Needs Logo 220626
I am currently obtaining the position of the mouse on mousemove, then using that to move my popover <img>, and everything works fine, but it is very laggy.. and presumably its from so many calls being made.
To get the position of the mouse:
jQuery(document).ready(function(){
//$("#special").click(function(e){
$(".imgWrap").mousemove(function(e){
//$('#status2').html(e.pageX +', '+ e.pageY);
//alert(e.pageX + ', ' + e.pageY);
mouseX = e.pageX;
mouseY = e.pageY;
});
})
I'm not sure of another way I can do this.. any ideas?
On the full course of events is the following:
User mouses over an img tag.
I get the position of the mouse as per above.
The <img> tag also calls a js function which changes the position of an img tag to the position of the mouse.
Actually, you can check it here: pokemonsite
update: I see there is a bounty placed (thanks !). I'm a little busy at the moment and can't check all the other answers, but I'll make sure to check them asap

There are several ways to improve performance when using mousemove events.
Use backface-visibility: hidden on popover element to force hardware acceleration. Same thing can be achived with transform: translate3d(0,0,0) but that makes difficult to use CSS transform function (see point #2).
Use CSS transform function for absolute positioning to avoid repaints but keep popover element absolute or fixed positioned.
When setting inline CSS via JS use requestAnimationFrame to avoid unnecesary layout trashing.
(maybe, optionally) hide cursor when hovering and use popover element as position indicator.
Move everything you can from JS to CSS ie. :hover state can be used to toggle display of popover element.
I made demo example combining all things listed. There is still some latency between cursor position and popover image and none of example links in original question work so I can't compare against it but I hope someone finds this useful.
DEMO
<div id="imgHolder" class="imgHolder">
<img src="//placehold.it/200x200" alt="" />
</div>
<div id="bigImgHolder" class="imgHover">
<img src="//placehold.it/500x500" alt="" />
</div>
.imgHover {
display: none;
backface-visibility: hidden;
position: fixed;
top: 0; left: 0;
pointer-events: none;
}
.imgHolder:hover ~ .imgHover { display: block; }
// uncomment if it makes sense
//.imgHolder:hover { cursor: none; }
var posX, posY;
$('#imgHolder').mousemove(HoverImg);
function HoverImg(e) {
posX = e.pageX;
posY = e.pageY;
window.requestAnimationFrame(showBigImg);
}
function showBigImg() {
$('#bigImgHolder').css({'-webkit-transform': 'translateX(' + posX + 'px) translateY(' + posY + 'px)', 'transform': 'translateX(' + posX + 'px) translateY(' + posY + 'px)' });
}
references:
http://davidwalsh.name/translate3d
http://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/
https://css-tricks.com/using-requestanimationframe/

Try this:
jQuery(document).ready(function(){
$(".imgWrap").mousemove(function(e){
e.stopPropagation();
mouseX = e.pageX;
mouseY = e.pageY;
});
})

Use e.offsetX and e.offsetY or (recommended) e.clientX and e.clientY instead of pageX and pageY. Maybe this will be a better solution. Note: offsetx and offsety do not work in Firefox as far as I know.

If the absolute (x, y) position is not so important (meaning: some pixel-values can be omitted without destroying your logic), you could try to skip some frames of your mousemove-event.
var globalSkipCounter = 0;
var globalSkipRate = 5;
$(".imgWrap").mousemove(function(e){
if(globalSkipCounter >= globalSkipRate){
var mouseX = e.pageX;
var mouseY = e.pageY;
do_stuff(mouseX, mouseY);
globalSkipCounter = 0;
}
else{
globalSkipCounter+=1;
}
});
This way, you omit redrawing your image on every mousemove-event, instead your draw-routines (do_stuff) are only invoked, once every 5 events.

Cache the position and wrap the update in an if(oldpos !== newpos) type check (remembering to update oldpos inside it).
Use requestAnimationFrame to handle the update - if you have a normal function and pass that as the callback then it will only get called once per frame (ie, don't use an anonymous function).
Finally make use of transform:translate(x,y) to set the position and make better use of the GPU etc. Related to this, there's no harm in making use of the css will-change keyword if you want to use top/left instead.

try a maximum event per second based approach:
jQuery(document).ready(function(){
var now, then, delta, interval = 1000/60; //maximum 60 eps, you can change
//$("#special").click(function(e){
$(".imgWrap").mousemove(function(e){
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - delta % interval; //subtract extra waited time
//$('#status2').html(e.pageX +', '+ e.pageY);
//alert(e.pageX + ', ' + e.pageY);
mouseX = e.pageX;
mouseY = e.pageY;
// do your thing
}
});
})
EDIT: didn't know then is a reserved word in javascript, you can rename it.

I have a sortable listview in angular, but dragging items with the mouse in chrome was lagging a lot. I tried disabling all chrome extensions and now the lag is totally gone.
It turned out that TamperMonkey was causing it.

Instead of using "left" or "right" property of CSS rather try to use
"transform" I was trying to do the same thing as asked in question, and using "transform" worked for me like a charm.
for example:
transform: translate(30px);
this will move the element, 30px to the right.
by using transform we can move elements to left, right, top and bottom according to the need.

Related

Drag/Reposition Element On touchmove not working

I have a fixed window on my screen that I would like the user to be able to drag around the screen. While I have set this up to work for mouse inputs, I am having more difficulty getting this to work for touch screens.
So I have a basic box like so:
<div id="smallavatar" class="smallbox">
</div>
Which is then immediately followed by this JS (note that the first half is for the click events)
var divOverlay = document.getElementById ("smallavatar");
var isDown = false;
divOverlay.addEventListener('mousedown', function(e) {
isDown = true;
}, true);
document.addEventListener('mouseup', function() {
isDown = false;
}, true);
document.addEventListener('mousemove', function(event) {
event.preventDefault();
if (isDown) {
var deltaX = event.movementX;
var deltaY = event.movementY;
var rect = divOverlay.getBoundingClientRect();
divOverlay.style.left = rect.x + deltaX + 'px';
divOverlay.style.top = rect.y + deltaY + 'px';
}
}, true);
divOverlay.addEventListener('touchstart', e => {
clientX = e.touches[0].screenX;
clientY = e.touches[0].screenY;
});
divOverlay.addEventListener('touchmove', e => {
let x = divOverlay.style.left;
let y = divOverlay.style.top;
// Compute the change in X and Y coordinates.
// The first touch point in the changedTouches
// list is the touch point that was just removed from the surface.
deltaX = e.changedTouches[0].clientX - clientX;
deltaY = e.changedTouches[0].clientY - clientY;
divOverlay.style.left = divOverlay.style.left + deltaX;
divOverlay.style.top = divOverlay.style.top + deltaY;
});
Finally the following CSS is being used:
.smallbox{position:fixed; bottom:70px; right:0; width:360px; height:400px; z-index:20;}
Now while the touchmove event does work, the problem is that the box seems to fly off the screen very quickly. The idea is supposed to be that I'm trying to get the position the div was using before the touch event started, then work out how much movement has occured in the touchmove event and then from that update the left and top values with the new position it should be in.
Is there anything wrong that would explain why this isn't working as expected?
You have one major flaw in your code that causes the unexpected result:
At touchstart you store clientX and clientY, and than during touchmove you keep referencing those two values at deltaX = e.changedTouches[0].clientX - clientX; (and Y).
But while e.changedTouches[0].clientX keeps updating, clientX never gets updated and always keeps its initial start-value.
So for the next frame/iteration, deltaX = e.changedTouches[0].clientX - clientX; will actually be the delta-x of that last frame, plus the delta-x of the frame before, and the one before, and before, and so on till the very first frame.
So your divOverlay will be moving exponentially, and be out of sight in a fraction of a sec.
What the best choice is for getting the current position of divOverlay, depends a little on your situation, although I think in most cases, using pageX/pageY is probably your safest bet.
The other options are clientX/clientY and screenX/screenY, see this and this article for the differences between the three.
In any case, I wouldn't recommend using them interchangeably during one calculation, unless it's absolutely necessary.
See the live snippet below for the solution:
var divOverlay = document.getElementById("smallavatar");
/*MOUSE-LISTENERS---------------------------------------------*/
var isDown=false;
divOverlay.addEventListener('mousedown',function(){isDown=true;},true);
document.addEventListener('mouseup',function(){isDown=false;},true);
document.addEventListener('mousemove',function(e) {
e.preventDefault();
if (isDown) {
divOverlay.style.left = divOverlay.offsetLeft + e.movementX + 'px';
divOverlay.style.top = divOverlay.offsetTop + e.movementY + 'px';
}
},true);
/*TOUCH-LISTENERS---------------------------------------------*/
var startX=0, startY=0;
divOverlay.addEventListener('touchstart',function(e) {
startX = e.changedTouches[0].pageX;
startY = e.changedTouches[0].pageY;
});
divOverlay.addEventListener('touchmove',function(e) {
e.preventDefault();
var deltaX = e.changedTouches[0].pageX - startX;
var deltaY = e.changedTouches[0].pageY - startY;
divOverlay.style.left = divOverlay.offsetLeft + deltaX + 'px';
divOverlay.style.top = divOverlay.offsetTop + deltaY + 'px';
//reset start-position for next frame/iteration
startX = e.changedTouches[0].pageX;
startY = e.changedTouches[0].pageY;
});
.smallbox{position:fixed; bottom:70px; right:0; width:36px; height:40px; z-index:20; background:red;}
<div id="smallavatar" class="smallbox"></div>
jsfiddle: https://jsfiddle.net/2hdczkwn/4/
To address your major flaw, I added startX = e.changedTouches[0].pageX; and startY = e.changedTouches[0].pageY; to the end of your touchmove listener, resetting the start-position for each new frame/iteration to the end-position of the last frame/iteration.
I also added e.preventDefault(); to your touchmove listener, because otherwise any scrollable page would also start scrolling while you were dragging the divOverlay.
I converted your arrow functions into traditional ones, like your mouse listeners.
I added var startX=0, startY=0; outside the functions to make sure they are global vars.
I replaced var rect = divOverlay.getBoundingClientRect(); and rect.x and rect.y inside your mousemove listener, and divOverlay.style.left and divOverlay.style.top inside your touchmove listener, with divOverlay.offsetLeft and divOverlay.offsetTop.
This is actually quite important, since divOverlay.style.left returns a px value, e.g. 57px. When this value is used in a calculation, like 57px + 12, it will not result in 69 or 69px but instead in NaN.
I can't help but notice that your mouse and touch listeners are written in a different coding style, mouse uses traditional JS, while touch is written in the new ES6, using let and arrow functions.
As if you copied the touch listeners from someone else and didn't change it to match the rest of your code.
While it is fine to use either style and to use other people's code (as long as you have permission), I would discourage using them both in the same project, and it is never a good idea to simply copy someone else's code without rewriting it to fit your own style.
This will make sure your whole project retains its current style of coding, and has the added advantage of you actually understanding the code you're using.

Hover not working on animated div

I've made a codepen with a Tweenlite animated box. The box has a :hover in the css.
If you place your mouse in the path of the animated box so it 'hits' the mouse you see the hover effect doesn't happen. It only happens when the mouse moves on it.
How do I fix that?
http://codepen.io/anon/pen/EfAGn
.box {
width:50px;
height:50px;
position:relative;
margin-bottom:2px;
}
.red {
background-color:red;
}
.red:hover{
background-color: white;
}
Here's a continuation of Jcubed's answer:
Essentially it's calculating the mouses position and checking it against the objects position, and then seeing if the distance between the two is less than 25px every 100ms.
If the object is less than 25px (half of the object width) then it is inside of it and will add the hover class. If it is greater it will remove the hover class.
CodePen
(function() {
$("#restart").on("click", function() {
tl.restart();
})
var mX, mY, distance, mousePosition,
$distance = $('#distance span'),
$element = $('#redBox');
// Movement
var tl = new TimelineLite()
tl.to($element, 15, {x:550});
setInterval(function() {
function calculateDistance(elem, mouseX, mouseY) {
return Math.floor(Math.sqrt(Math.pow(mouseX - (elem.offset().left+(elem.width()/2)), 2) + Math.pow(mouseY - (elem.offset().top+(elem.height()/2)), 2)));
}
$(document).mousemove(function(e) {
mX = e.pageX;
mY = e.pageY;
mousePosition = (mX, mY);
$distance.text(distance);
});
distance = calculateDistance($element, mX, mY);
if(distance < 25){
console.log("Mouse Has Entered");
//adding hovered class
$($element).addClass('hovered');
}
else{
// removing hovered class
$($element).removeClass('hovered');
}
// Setting Timeout
}, 100);
})();
Here's some additional information from Chris Coyier
This is probably a browser bug. It seems that the browser is only reevaluating the hover state when the mouse moves, not when the animation changes.
What you'll need to do is probably to manually check where the box is and where the user's mouse is every frame, and update the css through JavaScript instead of relying on :hover.
Or, simply wait for browsers to fix this bug. As #Pondwater pointed out, it works in firefox 30.

Move image around (css3 transform) with touchmove on iPad

I have an image which I would like to move around on my iPad3
The problem is that it isn't going as good as I hoped for, the image doesn't move smooth and is moving behind the gesture.
What I have is as follows (3MB base64 image)
<img src="data:image/jpeg;base64,/9j/4AAQ....">
css:
img {
transform: translate3d(0,0,0);
}
And the javascript is
img.on('touchstart', function (e) {
var diffX = e.pageX;
var diffY = e.pageY;
img.on('touchmove', function (e) {
translateX += e.pageX - diffX;
translateY += e.pageY - diffY;
img.css({'-webkit-transform': 'translate(' + translateX + 'px, ' + translateY + 'px)'});
diffX = e.pageX;
diffY = e.pageY;
});
return false;
})
...
It works great on my laptop, but not my iPad3.
I also tried requestAnimationFrame, but then nothing happens until I stop moving. Any suggestion how to improve the performance on my iPad ?
This does most of the math outside of the touchmove function
and sets the webkittransform directly without passing by a very long jquery css function.
it also uses the 3d transition witch isn't properly set with just adding translate3d in css file with the ios7
var sx,sy,cx=0,cy=0,img=document.images[0];
img.addEventListener('touchstart',ts,false);
img.addEventListener('touchmove',tm,false);
img.addEventListener('touchend',te,false);
function ts(e){
e.preventDefault();
sx=e.pageX-cx;
sy=e.pageY-cy;
}
function tm(e){
this.style.webkitTransform='translate3d('+
(e.pageX-sx)+'px,'+(e.pageY-sy)+'px,0)';
}
function te(e){
cx=e.pageX-sx;
cy=e.pageY-sy;
}
another point is ... even if you have the ipad 3 it can't handle a big image like that so consider putting all inside a div with overflow:hidden this will increase the performance too.
div{
width:400px;/*try 100%*/
height:400px;/*try 100%*/
overflow:hidden;
}
Mouse move example (not touch but its the same except you don't need the isup check)
http://jsfiddle.net/AtBUh/

javascript (but not jQuery please) fixed position on x-axis but not y?

I've looked everywhere and so far have not found a non-jQuery js to handle this. I would like to avoid using a library for just this one simple task.
I would like to fix three navigation divs ("#header", "#tabs" and "#footer") to viewport left (or alternatively, to the x position of a div "#helper" with "position: fixed; left: 0; top: 0;") -- but not fix y. They can not be vertically fixed.
I've created a working js that forces the divs to reposition based on scrolling, but it's not smooth in the real page (too many dynamic and graphic elements) - I'd like it to either animate smoothly, or mimic fixed-left and not appear to reposition at all.
Anyone who can give pointers or a quick script, or review and modify the script I have made? I've noticed people tend to ask why an obvious solution is not used instead of answering the question... I will be glad to answer, but would prefer help with the actual problem.
Here is a jsFiddle with the problem: http://jsfiddle.net/BMZvt/6/
Thank you for any help!
Smooth animation example:
var box = document.getElementById('box');
var moveTo = function(obj, target) {
// start position
// you should obtain it from obj.style
var cpos = {
x: 0,
y: 0
}
var iv = setInterval(function(){
cpos.x += (target.x - cpos.x) * 0.3; // 0.3 is speed
cpos.y += (target.y - cpos.y) * 0.3; // 0.3 is speed
obj.style.left = Math.floor(cpos.x) + 'px';
obj.style.top = Math.floor(cpos.y) + 'px';
var dist = Math.abs(cpos.y - target.y); // distance (x+y) from destination
dist += Math.abs(cpos.x - target.x); // < 1 = object reached the destination
if(dist < 1) { // here we are checking is box get to the destination
clearInterval(iv);
}
}, 30); // this is also the speed
}
box.onclick = function(){
moveTo(box, {x: 90, y: 75}); // fire this function to move box to specified point
}
Demonstration: http://jsfiddle.net/Qwqf6/5/
Your script is your job, but this is a quick start how to solve animation problem
You can also do some fancy stuff with speed for example use sin(x) to set the speed
Demonstration #2 http://jsfiddle.net/Qwqf6/6/ (very smooth)
Full script here https://gist.github.com/3419179
I don't think there's a straight way to do this...
But here's a way.
First, You need to be able to detect the direction of the scrolling when window.onscroll event happens. You would do this by comparing the current page offsets with the newly acquired page offsets whenever the scroll event happens. (http://stackoverflow.com/questions/1222915/can-one-use-window-onscroll-method-to-include-detection-of-scroll-direction)
Now suppose you know the direction of the scroll, you want to change the styling for the divs depending on the direction of the scroll.
Let FixAtX be the value of the x coordinate that you want to fix your divs at.
Let OriginalY be the y coordinate of the divs.
Also whenever scrolling happens, despite of the direction, you want to remember the pageoffset X and Y. Let's call them OldX and OldY
If scrolling vertically:
Set position value for divs' style to be absolute.
Set top value for divs' style to be OriginalY
Set left value for divs' style to be OldX + FixAtX
If scrolling horizontally:
Set position value for divs' style to be fixed.
set top value for divs' style to be OriginalY - OldY (<- this may be different depending on how the browser computes pageOffset value,)
Set Left value for divs' style to be FixAtX
I think this should work...
Since you are just using browser's rendering for positioning, it should be very smooth!
hope I understood the question correctly.
This is for people who view this post - I wound up going with the solution I initially put together in the jsFiddle that used a simple javascript to mimic fixed x.
The javascript in the first answer was hefty and wound up buggy, and the second answer sounded good but did not work in practice. So, I'm recommending the javascript from the jsFiddle (below) as the best answer to fixed x and fluid y without a javascript library. It's not perfect and has a minimal delay but is the best answer I've found.
function fixLeft() {
function getScrollX() {
var x = 0, y = 0;
if( typeof( window.pageYOffset ) == 'number' ) {
x = window.pageXOffset;
} else if( document.body && ( document.body.scrollLeft) ) {
x = document.body.scrollLeft;
} else if( document.documentElement && ( document.documentElement.scrollLeft) ) {
x = document.documentElement.scrollLeft;
}
return [x];
}
var x = getScrollX();
var x = x[0];
// have to get and add horizontal scroll position px
document.getElementById('header').style.left = x + "px";
document.getElementById('tabs').style.left = x + "px";
document.getElementById('footer').style.left = x + "px";
}
window.onscroll = fixLeft;

How to drag a DIV so its width and height are negative values?

I'm a bit stumped here. I am developing a feedback utility that will allow the user to "draw" boxes on a web page to highlight problem areas. Right now I have an overlay DIV that fills the screen and jQuery allows you to draw red outlined DIVs by clicking and dragging.
Here is the JS:
{
var $feedbackOverlay = jQuery('#feedbackOverlay');
var $original = { top: 0, left:0 };
$feedbackOverlay.bind('mousedown', function (e)
{
jQuery('<div id="currentHighlight"></div>')
.css('width', '1px')
.css('height', '1px')
.css('border', 'solid 3px #ff0000')
.css('border-radius', '5px')
.css('position', 'absolute')
.css('left', e.pageX)
.css('top', e.pageY)
.css('z-index', '8000001')
.appendTo('body');
$original = { top: e.pageY, left: e.pageX };
});
$feedbackOverlay.bind('mousemove', function (e)
{
var $currentHighlight = jQuery('#currentHighlight');
if ($currentHighlight.length > 0)
{
var $pos = { top: e.pageY, left: e.pageX };
if($pos.top < $original.top) $currentHighlight.css('top', $pos.top);
if ($pos.left < $original.left) $currentHighlight.css('left', $pos.left);
$currentHighlight.height(Math.abs($pos.top - $original.top));
$currentHighlight.width(Math.abs($pos.left - $original.left));
}
});
$feedbackOverlay.bind('mouseup', function (e)
{
var $currentHighlight = jQuery('#currentHighlight');
$currentHighlight.removeAttr('id');
});
var $feedbackInstructions = jQuery('#feedbackInstructions');
$feedbackInstructions.fadeIn(1000, function ()
{
setTimeout(function ()
{
$feedbackInstructions.fadeOut(1000);
}, 3000);
});
$feedbackOverlay.height(jQuery(document).height());
});
Here is a jsFiddle for the above:
http://jsfiddle.net/Chevex/RSYTq/
The problem is that I can't drag the boxes up or left. The first click puts the top left corner where the mouse clicked. After that subsequent dragging will change the width of the box. Letting go of the mouse completes the box and you may then start drawing another one. If you try to drag the DIV left or up while drawing it's width will remain at 0 but won't go negative.
Here you can find working solution: http://jsfiddle.net/RSYTq/34/
Something like this will get you closer to what you want: http://jsfiddle.net/RSYTq/18/
Doesn't quite handle move up and to the left and then switching to moving down and to the right quite right yet but it gives you the idea.
There's no such thing a a negative width - these are not coorindinates. You need to reposition and recalculate the corner positions relative to the corner that's not being moved.
Sounds like you need to check if the click origin (x,y) is > than the current mouse position, and then swap which one you use for the CSS top-left.
You would need to track the original start point somewhere (variables, data attributes on #currentHighlight, wherever you want), and check for width or height < 0. When so, set the #currentHighlight left/top CSS to be offset by original + (e.pageX - $currentHighlight.position().left) (for example). Then set the #currentHighlight width/height to the same difference (but positive: (e.pageX - $currentHighlight.position().left) * -1).

Categories