I have a Javascript that scrolls an UL from left to right depending on where the mouse is positioned over it: A demo can be seen HERE (site still under construction) I would like it to work with touchscreen devices also. Whereby touching and "dragging" ones finger would scroll the UL in a similar manner, tapping on the list would then "click" on an image.
How easy/hard would that be to modify the JS:
$(function(){
$(window).load(function(){
var $gal = $("#gallerylist.top"),
galW = $gal.outerWidth(true),
galSW = $gal[0].scrollWidth,
wDiff = (galSW/galW)-1, /// widths difference ratio
mPadd = 200, // Mousemove Padding
damp = 20, // Mousemove response softness
mX = 0, // Real mouse position
mX2 = 0, // Modified mouse position
posX = 0,
mmAA = galW-(mPadd*2), // The mousemove available area
mmAAr = galW/mmAA; /// get available mousemove fidderence ratio
$gal.mousemove(function(e) {
mX = e.pageX - $(this).parent().offset().left - this.offsetLeft;
mX2 = Math.min( Math.max(0, mX-mPadd), mmAA ) * mmAAr;
});
setInterval(function(){
posX += (mX2 - posX) / damp; /// zenos paradox equation "catching delay"
$gal.scrollLeft(posX*wDiff);
}, 10);
});
});
There are touch events similar to mouse events:
$gal.touchstart(function(e) {});
$gal.touchend(function(e) {});
$gal.touchmove(function(e) {});
Usually you don't need to implement touch events for 'click'... they are usually already in sync.
For the mousemove event, it may be more desirable from the user's perspective to have this functionality disabled on a touch device. If the page needs to scroll, this could interfere with that interaction. An alternative may be to have some animation on a window scroll event.
Related
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.
I have a page on a website i am working on, that includes many images in a div in a grid (map). I made the div show a scroll bar at overflow and used jquery to enable scrolling via dragging and it works as intented with only a hundred or so showing at a time.
My only issue is, that since there are thousands of small images, moving the mouse only a bit will already result in blowing past a lot of objects.
My question now is, how can i modify my code, so that moving the mouse over the screen once will only scroll about one tenth of the div's width. So basically i want to reduce the scrolling speed.
I am super new to javascript etc. so please be patient.
<div id="map" class="center unselectable overflow">
lots of images here in a grid</div>
<script>
var clicked = false, clickY, clickX;
var map = document.getElementById('map');
$(document).on({
'mousemove': function(e) {
clicked && updateScrollPos(e);
},
'mousedown': function(e) {
clicked = true;
clickY = e.pageY;
clickX = e.pageX;
},
'mouseup': function() {
clicked = false;
$('html').css('cursor', 'auto');
}
});
var updateScrollPos = function(e) {
$('html').css('cursor', 'row-resize');
$(map).scrollTop($(map).scrollTop() + (clickY - e.pageY));
$(map).scrollLeft($(map).scrollLeft() + (clickX - e.pageX));
}
</script>
TLDR: how to I reduce the drag to scroll speed in jQuery?
A little more elabouration from my comment: it seems like you are trying to dampen the scrolling speed. Mathematically, this means all you need is to reduce the value you feed to the .scrollTop() and .scrollLeft() functions. This can be done by dividing them by a set, arbitrarily determined factor, so that the transformation is linear. An example will be, if you want to dampen your scrolling speed by a factor of 10×, then you simply divide the values by 10:
var updateScrollPos = function(e) {
var scrollTop = $(map).scrollTop() + (clickY - e.pageY);
var scrollLeft = $(map).scrollLeft() + (clickX - e.pageX);
$('html').css('cursor', 'row-resize');
$(map).scrollTop(scrollTop / 10);
$(map).scrollLeft(scrollLeft / 10);
}
Pro-tip: since you are accessing $(map) several times, you can (micro)optimize your code by caching it:
var updateScrollPos = function(e) {
var $map = $(map);
var scrollTop = $map.scrollTop() + (clickY - e.pageY);
var scrollLeft = $map.scrollLeft() + (clickX - e.pageX);
$('html').css('cursor', 'row-resize');
$map.scrollTop(scrollTop / 10);
$map.scrollLeft(scrollLeft / 10);
}
I am translating ( via jQuery / CSS3 ) a #wrapper div, by updating Y axis.
I've attached mouseenter / mouseleave events to child elements of #wrapper.
When #wrapper translates, its child comes under mouse one by one ( even if mouse does not move ). And this does not fire the mouseenter , mouseleave events.
However, events are fired when element has scrollbar and scrolled by mousewheel ( instead of translating ).
Is this a default behavior ? If yes, any workaround ?
Demo
Try scrolling with mousewheel, without moving mouse. I expect to change the background of .block to red color, but it's not happening.
Example:
document.elementFromPoint(x, y);
Definition from Here:
Returns the element from the document whose elementFromPoint method is
being called which is the topmost element which lies under the given
point. To get an element, specify the point via coordinates, in CSS
pixels, relative to the upper-left-most point in the window or frame
containing the document.
Browser support from Here:
Internet Explorer 5.5+
Mozilla FireFox 3+
Safari Win 5+
Google Chrome 4+
Opera 10.53+
Solution 1 Working Example*:
var currentMousePos = { x: -1, y: -1 };
$(document).mousemove(function(event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
});
$(containerElement).mousewheel(function(event) {
$(elementClass).trigger('mouseleave');
var element = document.elementFromPoint(currentMousePos.x, currentMousePos.y);
$(element).trigger('mouseenter');
});
* there are some bugs, but you get the idea
Solution 2:
Use debounce from lodash or underscore libraries, to reduce load on client.
var currentMousePos = { x: -1, y: -1 };
$(document).mousemove(function (event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
});
var debounced = _.debounce(function () {
$(elementClass).trigger('mouseleave');
var element = document.elementFromPoint(currentMousePos.x, currentMousePos.y);
$(element).trigger('mouseenter');
}, 150);
$(containerElement).mousewheel(function (event) {
debounced();
});
I've got a home-made slider made from jQuery UI's draggable() function:
$("#petrolGauge .fuelBar .slider").draggable({
containment: "parent",
axis: "x",
drag:function(){
updValues();
},
start:function(){
$(this).css("background-color","#666");
},
stop:function(){
//checkForm();
$(this).css("background-color","#AAA");
}
});
This is for the following markup:
<div id="petrolGauge">
<input id="endPet" name="endPet" type="hidden" value="0">
How much fuel was left in the tank when you were finished? (Use the slider) <b>(~<span class="petLeft">0</span>%)</b>
<span class="mandatory">*</span><br />
<div class="fuelBar">
<div title="Drag" class="slider"></div>
</div>
This works a treat, when I click on the slider. But I'd like it so that when I click the fuel bar (the slider's parent) the slider not only starts dragging but also jumps to the cursor. I've achieved it by doing this:
$("#petrolGauge .fuelBar").on("mousedown",function(e){
slider = $("#petrolGauge .fuelBar .slider");
left = e.pageX-($(this).offset().left)-(slider.width()/2);
updValues();
slider.css("left",left).trigger(e);
});
Two problems with this:
Firstly, when clicking on the parent I get a couple of second's delay before the slider starts to drag? I've tried and tested this in Chrome and IE and both do it. Secondly if the cursor is less than half of the slider's width away from the edge of the parent, the slider will move to the outside of the parent. Wouldn't be hard to fix this with a couple of checking, but was wondering if there was another way? I'm suprised that draggable() doesn't have any parameters for this to be honest. I didn't want to use slider() if I could help it but if it's the only way, then it's the only way.
Here's a fiddle to work with.
The reason you get the delay is because you use .trigger() inside the .on() event which creates a big loop. As a result the loop slows down the moving process.
$("#petrolGauge .fuelBar").click(function (e) { // use click instead of mousedown
slider = $("#petrolGauge .fuelBar .slider");
left = e.pageX - ($(this).offset().left) - (slider.width() / 2);
if(left > 570) { left = 570; } else if(left < 0) { left = 0; }
// it looks like a draggable bug due to the manual position change, so use a small check
slider.css("left", left); // change the position first
updValues(); // then calculate and update the div
// no need to trigger the event a second time because it will loop until jQuery exceeds it's trigger limit.
});
Here's an updated FIDDLE
Updated answer
To make .slider move accordingly to the mouse movement when not directly dragged, bind a mousemove event to the mousedown and unbind it when mouseup. Then in .mousemove() you change the position of .slider.
var move = function (e) {
left = e.pageX - ($('#petrolGauge .fuelBar').offset().left) - (slider.width() / 2);
if (left > 570) {
left = 570;
} else if (left < 0) {
left = 0;
}
slider.css("left", left);
updValues();
};
var slider = $("#petrolGauge .fuelBar .slider");
$("#petrolGauge .fuelBar").mousedown(function (e) {
e.preventDefault();
left = e.pageX - ($(this).offset().left) - (slider.width() / 2);
if (left > 570) {
left = 570;
} else if (left < 0) {
left = 0;
}
slider.css("left", left)
$(this).bind('mousemove', move);
updValues();
}).mouseup(function () {
$(this).unbind('mousemove');
});
I have a div with a javascript touchmove event listener that scrolls the image inside the div horizontally on iOS6 Mobile Safari. I'd like allow vertical scrolling of the page to bubble up to the browser but when this occurs, jQuery.animate no longer works.
I've posted a simplified version of the code that demonstrates the problem at
https://gist.github.com/4047733
The steps I take to recreate the problem are:
Swipe picture left/right and notice how it animates back to the left edge
Touch the picture and scroll the page up/down
Repeat left/right swipe and notice the picture does NOT animate back to the left edge. It appears jQuery animate fails after touchmove occurs without e.preventDefault
Here is the javascript inside jQuery document ready from the gist link above
var el = document.getElementById("swipebox"),
$slider = $('#swipebox').find('img'),
startX, startY, dx, dy,
startLeft,
animateH = false,
animateV = false;
var onTouchStart = function(e) {
startLeft = parseInt($slider.css('left'), 10) || 0;
startX = e.touches[0].pageX;
startY = e.touches[0].pageY;
};
var onTouchMove = function(e) {
dx = e.touches[0].pageX - startX;
dy = e.touches[0].pageY - startY;
if (
animateH ||
(!animateV && Math.abs(dx) > 5)
) {
// prevent default, we are scrolling horizontally,
animateH = true;
$slider.stop().css({'left': startLeft+dx*2});
e.preventDefault();
} else if (Math.abs(dy) > 5) {
// do NOT prevent default, we are scrolling the page vertically
animateV = true;
} else {
// direction of scroll is undetermined at this time
// we've moved less than 5px in any direction
e.preventDefault();
}
return false;
};
var onTouchEnd = function(e) {
$slider.stop().animate({'left': 0}); // animate image back to left
e.preventDefault();
animateH = false;
animateV = false;
};
var onTouchCancel = function(e) {
console.log('onTouchCancel');
};
el.addEventListener('touchstart', onTouchStart, false);
el.addEventListener('touchmove', onTouchMove, false);
el.addEventListener('touchend', onTouchEnd, false);
el.addEventListener("touchcancel", onTouchCancel, false);
Any insight would be greatly appreciated.
This is bug in iOS6. jQuery animate timers fail when scrolling window in iOS6.
Currently there are few workarounds on this:
Create your own timer functions like someone did: https://gist.github.com/3755461
Use CSS3 transition instead of jQuery.animate. This is preffered way - css3 transitions doesn't have such problem. You can use this jquery plugin http://ricostacruz.com/jquery.transit/ to easily manipulate CSS transitions in JavaScript.