I am trying to set up a project for use with mobile and desktop but the wheel event doesn't work on mobile, so I need to use scroll.
I know how to get the deltaY from the Wheel event:
window.addEventListener("wheel", event => console.info(event.deltaY));
How do I get the deltaY from the Scroll event?
You should memorize the last scroll position in a variable, and when the scroll event fires, you can compute the difference.
Here is a modified example for your use case from the MDN docs on scroll event:
let lastKnownScrollPosition = 0;
let deltaY = 0;
document.addEventListener('scroll', function(e) {
let ticking = false;
if (!ticking) {
// event throtteling
window.requestAnimationFrame(function() {
deltaY = window.scrollY - lastKnownScrollPosition;
lastKnownScrollPosition = window.scrollY;
console.log('deltaY', deltaY);
ticking = false;
});
ticking = true;
}
});
body {
background: lightgrey;
height: 8000px;
}
<body>
</body>
The code above uses throtteling in order to reduce the amount of fired events. Also see: Difference Between throttling and debouncing a function
Related
I have an animation in HTML5 Canvas that simulates scrolling when the user clicks and drags. It moves vertically when the screen is vertical and horizontally when horizontal.
this.scroll = function(e) {
// scroll behavior for canvas
// feed x movements into horizontal (true) comic, y movments into (false) vertical
// this number is zero or less, hence added to displacements
comic.readShift += (comic.orientation ? e.movementX : e.movementY);
// ... other code here
comic.drawPanels();
};
Listeners hook this up:
canvas.addEventListener("mousedown", function(e) {
canvas.addEventListener("mousemove", comic.scroll);
});
canvas.addEventListener("mouseup", function(e) {
canvas.removeEventListener("mousemove", comic.scroll);
});
canvas.addEventListener("mouseleave", function(e) {
canvas.removeEventListener("mousemove", comic.scroll);
});
This works, as far as I can tell, on desktop browsers. But now I'm trying to get it to work on mobile. With no equivalent for movement[X|Y] for touch events, I tried this for the scroll behavior...
this.drag = function(e) {
// scroll behavior on touch screen
var touch = e.changedTouches[0];
var movementX = comic.touchstart.screenX - touch.screenX;
var movementY = comic.touchstart.screenY - touch.screenY;
// decrement in this case
comic.readShift -= (comic.orientation ? movementX : movementY);
// ... other code here
comic.drawPanels();
};
comic.touchstart gets recorded by the touchstart listener:
canvas.addEventListener("touchstart", function(e) {
comic.touchstart = e.changedTouches[0];
canvas.addEventListener("touchmove", comic.drag);
});
canvas.addEventListener("touchend", function(e) {
canvas.removeEventListener("touchmove", comic.drag);
});
canvas.addEventListener("touchcancel", function(e) {
canvas.removeEventListener("touchmove", comic.drag);
});
Which isn't very good. It'll function if the user swipes really slowly, but normal swiping causes weird acceleration of the scrolling effect and finally borks the page. Do Touch events have movementX and movementY equivalents? If not, what's the right way to set this up?
When scroll position hit the top position of the document the function is called. How to do that?
Good way to do it is to use the HostListener
#HostListener("window:scroll", [])
onWindowScroll() {
let scroll = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0;
if (number = 0) {
// Do some stuff here
}
}
Now I just wanna add that you probably need to account for elastic scroll on mac desktops. Meaning that your scroll position can go in minus, and might not hit 0 exactly when you want this event to fire.
Here is a good blog post about this, if you want more reading material
Use addEventListener on body, and listen for 'scroll' event, then when event is fired check scrollY property of window, and if it is 0, then you are on top.
function yourFunction() {
console.log('you are on top');
}
window.addEventListener('scroll', () => {
if (window.scrollY == 0) yourFunction();
}, true);
You can use HostListener to listen to the scroll event
#HostListener("window:scroll", ["$event"])
onWindowScroll() {
// get scroll postion
let number = this.window.pageYOffset ||
this.document.documentElement.scrollTop || this.document.body.scrollTop ||
0;
// here you can check the scroll postion
if (number > 100) {
}
}
Using the latest Chrome, I have notice that event mousemove fires after mousedown or mouseup even if the mouse is left at the same position.
I have this odd behavior attaching an event listener on document.documentElement.
Same script on latest Firefox works fine, issue seems on Chrome only.
Why does this event fire?
Is there any reasonable solution?
http://jsbin.com/cefoteleqo/1/
document.documentElement.addEventListener('mousedown', function(event){
console.log('mousedown', event.pageX, event.pageY);
}.bind(this));
document.documentElement.addEventListener('mouseup', function(event){
console.log('mouseup', event.pageX, event.pageY);
}.bind(this));
document.documentElement.addEventListener('mousemove', function(event){
console.log('mousemove <<<<', event.pageX, event.pageY);
}.bind(this));
Issue appears on Win 8.1:
Chrome Version 42.0.2311.135 m
Chrome Version 44.0.2398.0 canary (64-bit)
I have notice that mousemove fire at the same time or within a really short distance (10 milliseconds) after mousedown pr mouseup are fired.
So a possible work is to use event.timeStamp on mousemove for comparisons.
The following script check if mousemove event fired "to soon" and print the result in console accordingly.
Another possible solution could be checking the position of the mouse when cb for mousemove is executed.
Both solution are just a work around to this Chrome Bug.
Solution based on timeStamp:
http://jsbin.com/jedotomoxu/1/
Solution based on mouse position:
http://jsbin.com/dinororaju/1/
<script>
var timeDownUp = null;
function start() {
document.documentElement.addEventListener('mousedown', function (event) {
timeDownUp = new Date().getTime();
console.log('mousedown', event.pageX, event.pageY, event.timeStamp);
}.bind(this));
document.documentElement.addEventListener('mouseup', function (event) {
timeDownUp = new Date().getTime();
console.log('mouseup', event.pageX, event.pageY, event.timeStamp);
}.bind(this));
document.documentElement.addEventListener('mousemove', function (event) {
var timeMove = new Date().getTime();
timeDownUp += 10;
if (timeMove > timeDownUp) {
console.log('mousemove', event.pageX, event.pageY, event.timeStamp);
if (event.which === 1) {
console.log('mousemove DRAG', event.pageX, event.pageY, event.timeStamp);
}
} else {
timeDownUp = null;
}
}.bind(this));
}
</script>
I came across this issue as well. The above solution was a bit too much for me. I am not sure if my solution works on all browsers but I can confirm it does work in the version of chrome I am currently running: Version 48.0.2564.109 m
svg.element.addEventListener('mousemove', mouseMoved);
svg.element.addEventListener('mousedown', mouseDown);
var mouseDownTriggered = false;
function mouseDown(evt)
{
console.log('mouse down');
mouseDownTriggered = true;
}
function mouseMoved(evt)
{
if (mouseDownTriggered)
{
mouseDownTriggered = false
}
else
{
console.log('mouse moved');
}
}
I noticed the order is always mouse (down -> up -> moved) when you mouse down.
So I just flag the mouse down and have the mouse move absorb the call.
I have some code which changes the class of a table. On a phone, sometimes the table will be too wide for the screen and the user will drag/scroll about to see the contents. However, when they touch and drag the table around, it triggers touchend on every drag.
How do I test to see whether the touchend came as a result of a touch-drag? I tried tracking dragstart and dragend but I couldn't get that to work and it seems an inelegant approach. Is there something I could add to below which would essentially determine, "Did this touchend come at the end of a drag?"
$("#resultTable").on("touchend","#resultTable td",function(){
$(this).toggleClass('stay');
});
My thanks in advance for your help.
PS - using latest jquery, and while a regular click works, it is very slow in comparison to touchend.
Use two listeners:
First set a variable to false:
var dragging = false;
Then ontouchmove set dragging to true
$("body").on("touchmove", function(){
dragging = true;
});
Then on drag complete, check to see if dragging is true, and if so count it as a dragged touch:
$("body").on("touchend", function(){
if (dragging)
return;
// wasn't a drag, just a tap
// more code here
});
The touch end will still fire, but will terminate itself before your tap script is run.
To ensure the next time you touch it isn't already set as dragged, reset it back to false on touch down.
$("body").on("touchstart", function(){
dragging = false;
});
Looks like one solution to my problem is found here:
http://alxgbsn.co.uk/2011/08/16/event-delegation-for-touch-events-in-javascript/
This bit of code detects any move after touchstart in order to abort tap behavior after tapend.
var tapArea, moved, startX, startY;
tapArea = document.querySelector('#list'); //element to delegate
moved = false; //flags if the finger has moved
startX = 0; //starting x coordinate
startY = 0; //starting y coordinate
//touchstart
tapArea.ontouchstart = function(e) {
moved = false;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
};
//touchmove
tapArea.ontouchmove = function(e) {
//if finger moves more than 10px flag to cancel
//code.google.com/mobile/articles/fast_buttons.html
if (Math.abs(e.touches[0].clientX - startX) > 10 ||
Math.abs(e.touches[0].clientY - startY) > 10) {
moved = true;
}
};
//touchend
tapArea.ontouchend = function(e) {
e.preventDefault();
//get element from touch point
var element = e.changedTouches[0].target;
//if the element is a text node, get its parent.
if (element.nodeType === 3) {
element = element.parentNode;
}
if (!moved) {
//check for the element type you want to capture
if (element.tagName.toLowerCase() === 'label') {
alert('tap');
}
}
};
//don't forget about touchcancel!
tapArea.ontouchcancel = function(e) {
//reset variables
moved = false;
startX = 0;
startY = 0;
};
More here:
https://developers.google.com/mobile/articles/fast_buttons
I would say you can't tell the difference when the user drags to see more content or drag the element arround. I think you should change the approach. You could detect if it's a mobile device and then draw a switch that will enable/disable the movement of the element.
To shorten the solution of #lededge, this might help.
$("body").on("touchmove", function(){
dragging = true;
}).on("touchend", function(){
if (dragging)
return;
}).on("touchstart", function(){
dragging = false;
});
I created a Fiddle to demonstrate my situation.
I want to not fire the click event when the user is panning--only if it's just a simple click. I've experimented with different placements of .off() and .on() to no avail.
Thanks in advance for your help.
http://jsfiddle.net/Waxen/syTKq/3/
Updated your fiddle to do what you want. I put the re-binding of the event in a timeout so it wouldn't trigger immediately, and adjusted the mousemove to
In on click event, you can detect whether mouse was pressed DOWN or UP. So let's analyse:
DRAG:
mouse down
mosue position changes
mouse up
CLICK:
mouse down
mouse up
You see - the difference is changed mouse position. You can record click coordinate in mouse down and then compare it when muse goes back up. If it is within some treshold, the action was a click.
The only way to tell between a "click" and a "pan" would be the time the mouse has spent held down. You could create a Date in the mousedown, then another in the mouseup, and only fire your click (zoom) event if the difference between the two dates is greater than some threshold (i would guess 1/10 of a second, but you may want to experiment)
I added a "panning" bool for a solution to your problem:
see http://jsfiddle.net/syTKq/4/
Basically, if the user has mousedown and mousemove, then panning is true. once mouseup panning is false. if just mousedown, panning is false, therefore zoom.
This solution solves your problem:
var bClicking = false,
moved = false;;
var previousX, previousY;
var $slider = $('#slider'),
$wrapper = $slider.find('li.wrapper'),
$img = $slider.find('img.foo');
$img.on('click', function()
{
if(!moved)
{
doZoom();
}
});
$wrapper.mousedown(function(e) {
e.preventDefault();
previousX = e.clientX;
previousY = e.clientY;
bClicking = true;
moved = false;
});
$(document).mouseup(function(e) {
bClicking = false;
});
$wrapper.mousemove(function(e) {
if (bClicking)
{
moved = true;
var directionX = (previousX - e.clientX) > 0 ? 1 : -1;
var directionY = (previousY - e.clientY) > 0 ? 1 : -1;
$(this).scrollLeft($(this).scrollLeft() + 10 * directionX);
$(this).scrollTop($(this).scrollTop() + 10 * directionY);
previousX = e.clientX;
previousY = e.clientY;
}
});
function doZoom() {
$img.animate({
height: '+=300',
width: '+=300'
}, 500, function() {
//animation complete
});
}
Basically, it calls doZoom() only when the mouse has not moved between the mousedown and the mouseup events.
You can use the mousemove/mousedown events to set a flag that can be used in the click event handler to determine if the user was clicking or panning. Something like:
//set a flag for the click event to check
var isClick = false;
//bind to `mousedown` event to set the `isClick` flag to true
$(document).on('mousedown', function (event) {
isClick = true;
//bind to `mousemove` event to set the `isClick` flag to false (since it's not a drag
}).on('mousemove', function () {
isClick = false;
//bind to `click` event, check to see if the `isClick` flag is set to true, if so then this is a click, otherwise this is a drag
}).on('click', function () {
if (isClick) {
console.log('click');
} else {
console.log('drag');
}
});
Here is a demo: http://jsfiddle.net/SU7Ef/