I have a DIV in my page that is scrollable, while no other elements on the page are. (The page layout is fixed with controls above and below the DIV itself.) I would like the arrow keys and page up/page down to scroll the DIV under all circumstances, but I cannot seem to do so unless the DIV actually has the focus. There are other input fields in other DIVs which often have focus. I have tried capturing the arrow keys and page/up down 'keydown' event at the document level and simulating the same event directly (using answers from this question) to the DIV that needs to scroll, but no scrolling occurs. I know the event is being dispatched because if I attach an event handler I see it, but for some reason it doesn't cause any scrolling. I have also tried setting the "tabIndex" attribute of the DIV with no difference.
How can I designate a specific element to receive specific keys like this? It is extremely user unfriendly to require a specific element to be focused for certain keys to work, when those keys only make sense for a single element on the page. Having to constantly switch focus from another element to the scrollable area to scroll and back to enter data just isn't acceptable.
I have seen suggestions that scrolling can be simulated by other means, but I want to avoid that route because this doesn't always produce identical results, and I also want to generalize it to other kinds of key events and action besides scrolling.
You can scroll any element by adjusting its scrollTop DOM property.
If you capture all the keydown events on the document and then decide on what action you want to take depending on the key pressed (use the which property of the event object) and maybe some other circumstances (inputs focused, controls checked etc.) you can easily scroll your div. Check out this fiddle for a simple demo.
you can you keypress or keyDown events on the document and trigger actions on your DIV.
$(document).ready(function(){
$(document).on("keydown", handleKeyDown);
function handleKeyDown(evt) {
var code = parseInt(evt.keyCode); // the key Code of the key which was pressed during the event /and parses it and returns a 'integer'.
if(code == 38){ // 38 is the keycode for UP key on the keyboard
alert("up");
} else if(code == 40) // 40 is the keycode for down key on the keyboard.
alert("down");
}
}
});
and as explained in breif by Tadeáš Peták you can use the scrollTop DOM property to scroll any element, enjoy.
Is it possible to prevent the default behaviour of scrolling the document when a popstate event occurs?
Our site uses jQuery animated scrolling and History.js, and state changes should scroll the user around to different areas of the page whether via pushstate or popstate. The trouble is the browser restores the scroll position of the previous state automatically when a popstate event occurs.
I've tried using a container element set to 100% width and height of the document and scrolling the content inside that container. The problem with that I've found is it doesn't seem to be nearly as smooth as scrolling the document; especially if using lots of css3 like box-shadows and gradients.
I've also tried storing the document's scroll position during a user initiated scroll and restoring it after the browser scrolls the page (on popstate). This works fine in Firefox 12 but in Chrome 19 there is a flicker due to the page being scrolled and restored. I assume this is to do with a delay between the scroll and the scroll event being fired (where the scroll position is restored).
Firefox scrolls the page (and fires the scroll event) before popstate fires and Chrome fires popstate first then scrolls the document.
All the sites I've seen that use the history API either use a solution similar to those above or just ignore the scroll position change when a user goes back/forward (e.g. GitHub).
Is it possible to prevent the document being scrolled at all on popstate events?
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
}
(Announced by Google on September 2, 2015)
Browser support:
Chrome: supported (since 46)
Firefox: supported (since 46)
IE: not supported
Edge: supported (since 79)
Opera: supported (since 33)
Safari: supported
For more info, see Browser compatibility on MDN.
This has been a reported issue with the mozilla developer core for more than a year now. Unfortunately, the ticket did not really progress. I think Chrome is the same: There is no reliable way to tackle the scroll position onpopstate via js, since it's native browser behaviour.
There is hope for the future though, if you look at the HTML5 history spec, which explicitly wishes for the scroll position to be represented on the state object:
History objects represent their browsing context's session history as a flat list of session history entries. Each session history entry consists of a URL and optionally a state object, and may in addition have a title, a Document object, form data, a scroll position, and other information associated with it.
This, and if you read the comments on the mozilla ticket mentioned above, gives some indication that it is possible that in the near future scroll position will not be restored anymore onpopstate, at least for people using pushState.
Unfortunately, until then, the scroll position gets stored when pushState is used, and replaceState does not replace the scroll position. Otherwise, it would be fairly easy, and you could use replaceState to set the current Scroll position everytime the user has scrolled the page (with some cautious onscroll handler).
Also unfortunately, the HTML5 spec does not specify when exactly the popstate event has to be fired, it just says: «is fired in certain cases when navigating to a session history entry», which does not clearly say if it's before or after; if it was always before, a solution with handling the scroll event occuring after the popstate would be possible.
Cancel the scroll event?
Furthermore, it would also be easy, if the scroll event where cancelable, which it isn't. If it was, you could just cancel the first scroll event of a series (user scroll events are like lemmings, they come in dozens, whereas the scroll event fired by the history repositioning is a single one), and you would be fine.
There's no solution for now
As far as I see, the only thing I'd recommend for now is to wait for the HTML5 Spec to be fully implemented and to roll with the browser behaviour in this case, that means: animate the scrolling when the browser lets you do it, and let the browser reposition the page when there's a history event. The only thing you can influence position-wise is that you use pushState when the page is positioned in a good way to go back to. Any other solution is either bound to have bugs, or to be too browser-specific, or both.
You're going to have to use some kind of horrible browser sniffing here. For Firefox, I would go with your solution of storing the scroll position and restoring it.
I thought I had a good Webkit solution based on your description, but I just tried in Chrome 21, and it seems that Chrome scrolls first, then fires the popstate event, then fires the scroll event. But for reference, here's what I came up with:
function noScrollOnce(event) {
event.preventDefault();
document.removeEventListener('scroll', noScrollOnce);
}
window.onpopstate = function () {
document.addEventListener('scroll', noScrollOnce);
};
Black magic such as pretending the page is scrolling by moving an absolute positioned element is ruled out by the screen repainting speed too.
So I'm 99% sure that the answer is that you can't, and you're going to have to use one of the compromises you've mentioned in the question. Both browsers scroll before JavaScript knows anything about it, so JavaScript can only react after the event. The only difference is that Firefox doesn't paint the screen until after the Javascript has fired, which is why there's a workable solution in Firefox but not in WebKit.
Now you can do
history.scrollRestoration = 'manual';
and this should prevent browser scroll. This only works right now in Chrome 46 and above, but it seems that Firefox is planning to support it too
The solution is to use position: fixed and specify top equal to scroll position of page.
Here is an example:
$(window).on('popstate', function()
{
$('.yourWrapAroundAllContent').css({
position: 'fixed',
top: -window.scrollY
});
requestAnimationFrame(function()
{
$('.yourWrapAroundAllContent').css({
position: 'static',
top: 0
});
});
});
Yes, you instead receive flickering scrollbar, but it is less evil.
The following fix should work in all browsers.
You can set scroll position to 0 on the unload event. You can read about this event here: https://developer.mozilla.org/en-US/docs/Web/Events/unload. Essentially, the unload event fires right before you leave the page.
By setting scrollPosition to 0 on unload means when you leave the page with a set pushState it sets scrollPosition to 0. When you return to this page by refreshing or pressing back it will not autoscroll.
//Listen for unload event. This is triggered when leaving the page.
//Reference: https://developer.mozilla.org/en-US/docs/Web/Events/unload
window.addEventListener('unload', function(e) {
//set scroll position to the top of the page.
window.scrollTo(0, 0);
});
Setting scrollRestoration to manual not worked for me, here is my solution.
window.addEventListener('popstate', function(e) {
var scrollTop = document.body.scrollTop;
window.addEventListener('scroll', function(e) {
document.body.scrollTop = scrollTop;
});
});
Create a 'span' element somewhere at the top of the page and set focus to this on load. The browser will scroll to the focussed element. I understand that this is a workaround and focus on 'span' doesn't work in all browsers ( uhmmm.. Safari ). Hope this helps.
Here is what I have implemented on a site that wanted the scroll position to focus to a specific element when the poststate is fired (back button):
$(document).ready(function () {
if (window.history.pushState) {
//if push supported - push current page onto stack:
window.history.pushState(null, document.title, document.location.href);
}
//add handler:
$(window).on('popstate', PopStateHandler);
}
//fires when the back button is pressed:
function PopStateHandler(e) {
emnt= $('#elementID');
window.scrollTo(0, emnt.position().top);
alert('scrolling to position');
}
Tested and works in firefox.
Chrome will scroll to position but then repositions back to original place.
Like others said, there is no real way to do it, only ugly hacky way. Removing the scroll event listener didn't work for me, so here's my ugly way to do it:
/// GLOBAL VARS ////////////////////////
var saveScrollPos = false;
var scrollPosSaved = window.pageYOffset;
////////////////////////////////////////
jQuery(document).ready(function($){
//// Go back with keyboard shortcuts ////
$(window).keydown(function(e){
var key = e.keyCode || e.charCode;
if((e.altKey && key == 37) || (e.altKey && key == 39) || key == 8)
saveScrollPos = false;
});
/////////////////////////////////////////
//// Go back with back button ////
$("html").bind("mouseout", function(){ saveScrollPos = false; });
//////////////////////////////////
$("html").bind("mousemove", function(){ saveScrollPos = true; });
$(window).scroll(function(){
if(saveScrollPos)
scrollPosSaved = window.pageYOffset;
else
window.scrollTo(0, scrollPosSaved);
});
});
It works in Chrome, FF and IE (it flashes the first time you go back in IE). Any improvement suggestions are welcome! Hope this helps.
Might want to try this?
window.onpopstate = function(event) {
return false;
};
I'm having a pretty big problem trying to create navigation on my page. If the mouse enters an element then it selects it, then if you use arrow keys it will select the elements relative to the selected one. However this is an issue when the arrow keys cause the page to scroll, because (depending on the position of the mouse) it will select the appropriate element then instantly select the item the mouse is now over after the page moved (even if you didn't move the mouse).
Does anyone know how to fix this problem? I tried tinkering with it but none of my solutions seemed to work. Any help is appreciated, thank you.
It sounds like you should bind the "select when mouse enters" event on mousemove and unbind said event on mousestop. mousestop does not exist on its own, so you will have to create it somehow or use a plugin (there are at least a few out there such as https://github.com/richardscarrott/jquery-mousestop-event/ ). I think this would be the simplest solution, but your UI seems a little bizarre (you want the arrow key to scroll the page normally and "select" an element that's possibly larger than the scroll size?)
Not sure I completely understand, but you should be able to use a combination of the mousemove and keypress events:
$("#element").mousemove(function(e){
alert("mouse moved");
});
$("#element").keypress(function(e){
if (e.keyCode == 38 || e.keyCode == 40){ //up & down arrow keys
e.preventDefault();
}
});
Try returning false from the keyboard event handler where you check for arrow keys:
element.onkeypress = function(ev) {
// ...
return false;
}
This will prevent the "default behavior" that the browser has for the event, which is to scroll. This works also for links, for example: if you return false from a click event handler for a link, clicking the link will not automatically follow it.
I have 3 selects that I am enabling keyboard navigation(right and left arrow keys) to move between them. The default behavior when pressing the right or left arrow key is to move up/down the list.
My solution works moving between the selects, but every time you move to a new select, the selected value in the select that you just moved from gets moved up or down depending on which arrow you pressed.
Is there a way to prevent the default right and left arrow keys for the selects? I have tried e.preventPropagation and e.preventDefault but that doesn't seem to work.
JSFIDDLE
I just figured it out. I was binding to keypress event, when I needed to bind to the keydown event. When binding to keydown then you can use e.preventDefault().
JSFIDDLE SOLUTION
Since OP's solution does not work in Firefox, I created a workaround for the known bug in Firefox.
Basically event.preventDefault doesn't prevent ← and → on <select> nodes in Firefox.
So here's the workaround in action.
For more information on this, see my detailed answer on similar question
No way to get it work in Firefox preventing event default.... And Ruud solution is too complicated. So I've implemented that simple and clean solution, based on the same idea:
$('select').on('keydown', function( e ){
switch(e.keyCode) {
// User pressed "left" or "right" arrow
case 37:
case 39:
var val = $(this).val();
var $slt = $(this).one('change', function(){
$slt.val( val ).change();
});
break;
}
});
The only inconvenient is that "change" event is triggered twice, so you're app may take care about that.
I am using arrow keys to move an object in my code. Everything works fine except that the mouse cursor disappears when I press any of the arrow keys. How can I make the cursor stay visible? I am using this code check for arrow keys pressed.
$(document).ready(function() {
$(document).keydown(function(event) {
checkKeys(event);
}).keyup(function(event) {
keyUp(event);
});
});
That's browser behavior (maybe even OS behavior!), you probably won't find a way to stop it with javascript.
It's intended to hide the cursor so you can see what you're typing. Try it on any website, keystrokes always make the mouse cursor go away.