I have a div with webkit-overflow-scrolling set to touch. On iOS this then gives me an updated position during the touchmove event, but once the user lets go this event ends and a final call to touchend is made before events all stop, but the div continues to momentum scroll.
This is the behaviour I want, but I also want to update the page during this momentum scrolling.
I trigger a call to requestAnimationFrame when the touchend event happens, and I can loop this while the momentum scroll occurs. But when I get DOM information, it's frozen until after the mometnum scroll ends.
I've tried using both the scroll position of the scrolling element and elementFromPoint, but both just have the position the scrolled div was in at the time touchend was triggered, and don't update until the momentum scroll ends.
Does anyone know of any way to get real time DOM information on iOS (6+, not worried about 5)
Here's some code I'm using:
var glideStart;
var bird_scanner = document.getElementById('bird-scanner');
bird_scanner.addEventListener('touchend',function()
{
glideStart = null;
requestAnimationFrame(glide);
});
function glide(timestamp)
{
// if we need to reset the timestamp
if( glideStart === null )
{
glideStart = timestamp;
}
// determine if we've moved
var bird_scanner = document.getElementById('bird-scanner');
console.log( document.elementFromPoint(337,568) );
// calculate progress (keep running for a very long time so we see what happens when momentum ends)
var progress = timestamp - glideStart;
if( progress < 10000 )
{
requestAnimationFrame(App.Controller.bird.glide);
}
}
Update
After a lot of attempts at this, I think it really is impossible without using some library to try and mimic the momentum scroll instead of using the built in option (something I find never really gives as smooth results). Apple are clearly very worried about things interfering with their momentum scroll animation and preventing it rendering properly.
I ended up removing the momentum scroll and just detecting swipes and moving through a bunch of elements at once when that's triggered.
I did notice some particularly strange behaviour. When I had webkit-overflow-scrolling: touch set on an element that was scrolling the page up/down and added a setTimeout(some_func,0) to the touchend event, the function wasn't triggered until the momentum scroll ended. When I tried the same thing on a scroll going left/right it triggered the function straight away. No clue why this happens, decided it must just be some strange webkit quirk.
Related
I've got a custom scrollbar solution (view on CodePen).
The obvious idea is dragging the custom scrollbar should scroll the page.
Try it and see what happens... it's bizarrely janky, and the scrollbar and page scrolling will suddenly snap between points.
The scrolling process is currently in a mousemove handler:
update the scrollbar position visually
window.scrollTo(...) the new position, calculated as viewport top relative to the new scrollbar position
If I comment out the window.scrollTo(...) line, the scrollbar itself then moves perfectly smoothly and sticks with the cursor.
Pertinent code
mousemove(e) {
if (!this.active) return;
this.update(this.getScrollDeltaPositional(e.pageY));
window.scrollTo({top: this.getWindowScrollTop()});
}
update(position, show=true, timer=true, time=0) {
let track = this.getTrackHeight();
this.trackPosition = Math.min(Math.max(position, 0), track);
this.track.style.transform = `translateY(${this.trackPosition}px)`;
}
getWindowScrollTop() {
let scroll = this.getDocumentScroll();
let position = (this.trackPosition / this.root.clientHeight);
return Math.round(scroll * position);
}
(Recommended you view the full source on CodePen)
I presume the scrolling each mousemove is blocking the mousemove events, resulting in the sudden snaps being observed.
How to achieve a smooth scrolling effect on window using a custom scrollbar?
I finally found the answer
After far too many hours of trying everything conceivable to remedy this, I stumbled upon this identical problem: https://css-tricks.com/forums/topic/scrolltop-inexplicably-going-haywire/.
As that user eventually discovered, MouseEvent.pageY (which is what I was using to get scroll position) is
relative to the top edge of the viewport, including scroll offsets.
Therefore, the scroll movement effectively amplifies the mousemove events, causing the scrolling to accelerate exponentially as seen in the demo.
So after half a day of hacking about with this, the fix is a simple Ctrl+H... use MouseEvent.clientY instead.
I want to listen for a middle-button press (mousedown event) and conditionally either take an action (not relevant what specifically) or revert to the default, which would be to enter "autoscroll mode" with the autoscroll icon displayed until the user releases the middle button.
My code listens for the mousedown event and checks whether the pressed button is the middle button. Then determines whether to take the default action, and if not, prevents the default action by calling event.preventDefault. The problem is that there is a delay between the actual mouse press and executing the action, so when the user presses the button and holds it, the action is not taken immediately. Instead, mouse movement is observed for a short amount of time, and based on this movement, either a custom action is taken after the button is released, or the default action for mousedown is taken immediately. In the case the default action is taken, I want Firefox to behave as if the user pressed the middle button, eg. to start autoscroll. This is not happening, since the browser chrome does not react to synthetic events (events produced programmatically).
How do I go about triggering autoscroll in Firefox programmatically? Is there a way to change the behavior (eg. to make autoscroll listen for synthetic events) using userChrome.css?
var elem = document.getElementById('watchedElement')
var active = false
elem.onmousedown = function(event) {
if (event.button !== MIDDLE_BUTTON || active) { return }
event.preventDefault()
startParams = getStartParams()
active = true
}
elem.onmousemove = function(event) {
if (!active) { return }
if (shouldTakeDefaultAction()) {
elem.dispatchEvent(new MouseEvent('mousedown',
{'button': MIDDLE_BUTTON, 'which': MIDDLE_BUTTON}))
active = false
}
}
elem.onmouseup = function(event) {
if (event.button !== MIDDLE_BUTTON || !active) { return }
event.preventDefault()
takeCustomAction()
active = false
}
EDIT: More information
I have made a Greasemonkey plugin for YouTube, which does this:
when I click the middle button (without dragging), toggles fullscreen mode
when I drag left / right with the middle button pressed, it seeks in the video backward / forward
when I drag up / down, I want scrolling
So the default action has to be prevented, and when the mouse has been dragged over a certain distance, we can decide whether to seek or scroll. The angle of the trajectory is computed and evaluated once a certain amount of pixels has been "traveled" by the drag. If the drag is vertical, then I want to activate scrolling, otherwise wait for the button to be released and then seek. The video is sought only after the button is released, eg. no "live preview" while dragging.
As far as I know, there's no way to trigger Firefox's autoscroll mode from within JavaScript. However, it apparently is possible to interrupt it while it's active, e.g. by calling window.scrollTo().
Also, dragging the mouse cursor horizontally while autoscroll mode is active does nothing if the page doesn't in fact scroll horizontally. Most web pages don't.
Based on those observations, here's (a sketch of) an alternative solution:
Let the middle click propagate and trigger its default action, enabling autoscroll mode.
Do your horizontal drag detection as you normally do.
If you detect a horizontal drag, call window.scrollTo(window.scrollX, window.scrollY), which will turn off Firefox's autoscroll mode, and then proceed with the video seek.
The main disadvantages I see with this method are a) that the autoscroll indicator will be briefly visible while you're detecting the drag direction, and b) that if the user drags the mouse slightly diagonally, they may still end up scrolling the page a few pixels up or down. (This probably depends on how careful and coordinated your users are. The autoscroll feature itself seems to have a small built-in "dead zone", such that you need to move the mouse cursor up or down by at least a dozen or so pixels before the page actually starts scrolling vertically.)
The former seems like an unavoidable but minor interface quirk; your users might not even really notice it. To mitigate the latter issue, you might want to save the values of window.scrollX and window.scrollY in step 1, and use the saved values in step 3, thereby snapping the page back to the scroll position it had before the start of the drag. This might still result in an annoying "jerk" as the page scrolls a bit and then snaps back, but it may be less annoying than having the page move permanently down.
I have the problem, that I need to know if the user actually moved his mouse for real when entering fullscreen, or if it just is a programatically side effect of entering the fullscreen.
Because, when entering fullscreen, the mouse Y coordinates change automatically because the mouse moves upwards on the absolute screen position (since the top navigation of the browser disappears). And since every browser brings a notification in fullscreen mode, this very notification triggers a mousemove event.
So, this makes it very painful to find out, whether the user acually move the mouse, or not.
Is there a solution to identify REAL mouse movement?
$(document).on('mousemove', function(event){
/* gets also triggered when just entering fullscreen,
but without actual movement of the physical mouse..
how can this be identified/ignored?
*/
});
JS Fiddle
What I've tried so far
I tried already relativating the mouse position by using something like window.screen.top - but this seems not to be implemented yet by any browser so far.
I don't think there's anything formally implemented as yet to detect full screen. There's a fullscreenchange as part of the Fullscreen API but it's still experimental and requires vendor-specific prefixes.
So, basically you'll have to get around that limitation with some tricks, like intersecting the resize event and skipping whatever logic you are running on mousemove. Here's an example...
var resizing = false;
$(document).on('mousemove', function(event){
if(resizing == false){
$('p').text(event.pageX + ':' + event.pageY);
console.log("moving");
}
});
$(window).resize(function(){
resizing = true;
setTimeout(function(){
resizing = false;
}, 4000);
});
This example simply defines a flag that determines whether the window is resizing or not, if resizing the onmousemove logic is skipped. Particularly I hate to use setTimeout with an arbitrary time to switch off the resizing flag, but if your requirements are not so strict it can get the job done beautifully
Why don't you incorporate a delay (for example 0.5 seconds) where you ignore all mouse inputs. After the delay, any mouse movements are likely to be from the user...
I solved it now by saving the mouse coordinates, and check if they change - while I force one mousemove event after fullscreen in order to update the coordinates once.
$(document).on('mousemove', function(event){
if(event.pageX == $(this).data('mouseX') && event.pageY == $(this).data('mouseY'))
return;
$(this)
.data('mouseX', event.pageX)
.data('mouseY', event.pageY)
;
});
$(document).mousemove();
I want some js to automatically scroll just a slight bit down the page, however I also want this scroll to be interruptible by the user.
When using jquery to auto scroll, when you animate the scroll with .animate and then the user starts scrolling while the animation scroll is still going they interact with each other and create a strange jumping effect.
Is there a way to make so when the user scroll during a javascript scroll it just stop the javascript scroll?
It can't be done since you can't know if the end-user scrolled or you scrolled the page via javascript.
A scroll event is sent whenever the element's scroll position changes, regardless of the cause. A mouse click or drag on the scroll bar, dragging inside the element, pressing the arrow keys, or using the mouse's scroll wheel could cause this event.
Docs
What I tried to do but failed because the above:
// callback for the scroll event
$(document.body).scroll(function(){
// Stop the scrolling!
$('html, body').stop();
});
A not working demo...
The other users answer is actually incorrect, it is possible and has been answered before:
How can I differentiate a manual scroll (via mousewheel/scrollbar) from a Javascript/jQuery scroll?
Check the answer
"$('body,html').bind('scroll mousedown wheel DOMMouseScroll mousewheel keyup', function(e){
if ( e.which > 0 || e.type == "mousedown" || e.type == "mousewheel"){
$("html,body").stop();
}
})"
I've updated the previous users JS Fiddle to work with the proposed solution and it works perfectly.
http://jsfiddle.net/Lwvba/7/
Is there a way in javascript to bind an event handler to a horizontal scroll as opposed to the generic scroll event which is fired when the user scrolls horizontally and vertically? I want to trigger an event only when the user scrolls horizontally.
I searched around for an answer to this question, but couldn't seem to find anything.
Thanks!
P.S. My apologies if I'm using some terminology incorrectly. I'm fairly new to javascript.
UPDATE
Thanks so much for all your answers! In summary, it looks like you are all saying that this isn't supported in javascript, but I that I can accomplish the functionality with something like this (using jQuery) (jsFiddle):
var oldScrollTop = $(window).scrollTop();
$(window).bind('scroll', function () {
if (oldScrollTop == $(window).scrollTop())
//scrolled horizontally
else {
//scrolled vertically
oldScrollTop = $(window).scrollTop();
}
});
That's all I needed to know. Thanks again!
Answering from my phone, so unable to provide code at the moment.
What you'll need to do is subscribe to the scroll event. There isn't a specific one for vertical/horizontal.
Next, you'll need to get some measurements about the current display area. You'll need to measure the window.clientHeight and window.clientWidth.
Next, get window.top and window.left. This will tell you where position of the viewport is, ie if it's greater than 0 then scroll bars have been used.
It's pretty simple math from here to get what you need. If no one else has provided a code example in the next few hours I'll try to do so.
Edit:
A bit further information.
You must capture the scroll event. You also need to store the initial window.top and window.left properties somewhere. Whenever the scroll event happens, do a simple check to see if the current top/left values differ from the stores value.
At this point, if either are different you can trigger your own custom events to indicate vertical or horizontal scrolling. If you are using jQuery, this is very easy. If you are writing js without library assistance, it's easy too but a little more involved.
Do some searches for event dispatching in js.
You can then write any other code you want to subscribe to your custom events without needing to tie them together with method calls.
I wrote a jQuery plugin for you that lets you attach functions to the scrollh event.
See it in action at jsfiddle.net.
/* Enable "scrollh" event jQuery plugin */
(function ($) {
$.fn.enableHScroll = function() {
function handler(el) {
var lastPos = el
.on('scroll', function() {
var newPos = $(this).scrollLeft();
if (newPos !== lastPos) {
$(this).trigger('scrollh', newPos - lastPos);
lastPos = newPos;
}
})
.scrollLeft();
}
return this.each(function() {
var el = $(this);
if (!el.data('hScrollEnabled')) {
el.data('hScrollEnabled', true);
handler(el);
}
});
}
}(jQuery));
It's this easy to use:
$('#container')
.enableHScroll()
.on('scrollh', function(obj, offset) {
$('#info').val(offset);
});
Please note that scroll events come very fast. Even if you click in the scrollbar to jump to a new position, many scroll events are generated. You may want to adjust this code to wait a short time and accumulate all the changes in position during that time before firing the hscroll event.
You can use the same scroll event, but within your handler use the scrollLeft function to see if the scrollbar moved horizontally from the last time the event was fired. If the scrollbar did not move then just return from your handler. Otherwise update your variable to the new position and take action.
You can check if the the x value of the page changes and ignore your y value.
If the x value changes: There is your horizontal scroll.
With page-load, store the initial scrollbar positions for both in two variables (presumably both will be 0). Next, whenever a scroll event occurs, find the scrollleft and scrolltop properties. If the scrollleft property's value is different and scrolltop's value is same as compared to their earlier values, that's a horizontal scroll. Then set the values of the variables to the new scroll values.
No, there is no special event for scroll horizontal (it is for global scroll), but you can try to check the position of content by property .scrollLeft and if it's different from the previous value it means that the user scrolled content horizontally.