Making the scrolling keys scroll the proper page element - javascript

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.

Related

#keydown.ctrl and #keyup.ctrl event modifiers not responding to respective keyboard events vujs

I'm writing a VueJS application that will have a custom context menu that appears when the user mouses over a particular item on the page. The menu is dynamic meaning that it will change as the user hovers over different items. If the user holds down the control key the menu will be static so that they can interact with the items in the menu.
My issue is the while the #keydown.ctrl="..." event seems to respond as intended the #keyup.ctrl="..." event does not. I have looked around and tried different modifiers such as .exact and .caputure but they don't seem to solve the issue.
What does work however is changing #keyup.ctrl to #keyup.control. The .control works on the #keyup event but not for the #keydown event.
What is the difference between #keydown and #keyup that would require the use of different key modifiers?
Below is an example of what the root element looks like.
<div
tabindex="0"
#keydown.esc.exact="() => (displayContext = false)"
#keydown.ctrl.exact="() => (keepContext = true)"
#keyup.control="() => (keepContext = false)"
>
...
</div>
You can use this website to visually which keys are triggering which event: https://config.qmk.fm/#/test
or even on this one: http://keycode.info/
As you can see, you cannot have a listener on the ctrl key alone. You either need a ctrl + a or another key paired with it to effectively track it.
Here is the best implementation that we can have to track the ctrl event without any other key pressed:
hold ctrl and hover the div (#mouseover.ctrl="holdCtrlAndHover")
while holding ctrl, leave the div (#mouseleave.ctrl="holdCtrlAndLeave")
hold ctrl, hover on the div, then mouse click (#click.ctrl="hoverAndHoldCtrlThenMouseClick")
Example of the implementation: https://codesandbox.io/s/lingering-violet-q5gct?file=/src/App.vue
For a quick reference on MouseEvent.ctrlKey, here it is: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/ctrlKey
Here is a quick explanation of the differences on the event key listeners: onKeyPress Vs. onKeyUp and onKeyDown
Good to know: keypress event is now deprecated
There may be a couple of problems related to this.
Capturing keydown events (from vue template) is most reliable when you are trying to capture keydowns of an input (input, select or textarea) element. Because when you focus on the element all keyboard events are triggered on the this element. In your example you have a few divs on the page and these may not be the active element. A suitable workaround for this would be to capture all keydown or key keyup events on the document.body and checking if the event.target matches your criteria. Another benefit is you can keep your template code simpler (especially when you have many contextmenu areas.)
mounted() {
this.keepContext = false;
document.body.addEventListener('keydown', event => {
if (event.key == 'Control') {
this.keepContext = true;
}
});
document.body.addEventListener('keyup', event => {
if (event.key == 'Control') {
this.keepContext = false;
} else if (event.key == 'Escape') {
// hide the contextmenu
}
});
}
Another subject that you raised, the difference between #keydown.ctrl and #keydown.control.
The first: the .ctrl event modifier is usually used in combination with another key, (example: '#keydown.ctrl.s="autoSave"')
The second: .control is fired when you press the key control and cannot be used in combination with another key.
I have experimented with these two setups:
<input
#keydown.control="logKeydown"
#keyup.control="logKeyup">
>
and this
<input
#keydown.ctrl="logKeydown"
#keyup.ctrl="logKeyup">
>
And the setup with #keydown.control does works reliably. The #keyup.ctrl does not work reliably (sometimes it misses the keydown, sometimes it misses the keyup).
All in all I'd recommend listening to keyboard events on the document.body when using non-input elements. Second best is using the .control modifier (instead of the .ctrl) modifier.

Prevent focused div from scrolling with arrow keys

I have a div component with an inner scrollbar and I'd like to prevent the up/down arrow keys from scrolling when the element is focused (mouse click on the element), as they are used for other events (e.g. zoom).
The only solution I found was to attach an event listener to the document, however, it disables all default arrow keys events, such as moving the cursor in an input field.
Here is an example (in React):
https://codesandbox.io/s/rsc-live-example-fze6z
How to reproduce:
Click with the mouse on the inner div (with the text)
Click the down arrow key -> div scrolls down
Is there a way to prevent the scroll without disabling it on the document level?
Thanks!
UPDATE:
This missing piece was adding tab-index='0' to the component (I updated the code above). Thanks #Jarvan
You can filter the events which you don't want to prevent, such as arrow event in input.
if (e.target.tagName.toLowerCase()!=="input" &&(e.key === "ArrowUp" || e.key === "ArrowDown")) {
e.preventDefault();
}
Then in input the cursor move will not be prevented.
But it won't work if the event you want to keep is some behavior in same element.
E.g. you have some children can be focus in the 'prevent arrow up/down scroll' div, you want to keep the ability to move focus between children by using arrow up/down. In that case I guess you have to implement you customize scrollbar then you have the full control, because in browser behavior I don't see a way to separate the single 'scroll' action.
Update:
If you know which component or area you want to effect, just add to that area. Like in your demo code:
<div tabindex="0"
onKeyDown={e => {
console.log(e);
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
e.stopPropagation();
e.preventDefault();
console.log(e.key);
return false;
}
e.stopPropagation();
}}
>
{result}
</div>

Trigger click event on keypress for any focused element

Is there a default way to trigger a click event anytime enter is pressed on a focused element?
What I am referencing is the differences from pressing enter while focused on a <button> vs. pressing enter while focused on a <div> <li> <span>, etc. The <button> keypress enter triggers as expected. However, <div> keypress enter does nothing. I believe you have to specifically write the event in for that keypress.
$('li').click(function() {
//toggles something
});
$("li").keydown(function(e){
if(e.which === 13){
$(this).click();
}
});
Is there a default way to trigger a click event anytime enter is pressed on a focused element?
There's no solution without javascript.
Although the onclick has a special behavior as the W3C mentions
While "onclick" sounds like it is tied to the mouse, the onclick event is actually mapped to the default action of a link or button.
this does not work with other elements than links and buttons.
You could be tempted to add role="button" to a div with a tabindex="0". This does not work.
The Mozilla documentation explicitely says that you have to define handler, even with the button role.
This is easily understandable as this is a browser feature. When you define role=link on an element, you can't right click on it, hoping that it will open your browser context menu. For the same reason, defining the role=button attribute won't affect the default behavior. From this discussion:
ARIA simply conveys the accessibility semantics
intended by the author that are conveyed to an assistive technology.
Also do not forget to handle the space key. Read Karl Groves article and example about the subject.
If the elements on your page already have click events, and are in the tab order, and you just want to make the enter key trigger a click on whichever element has focus for accessibility, try the following:
<head>
<script>​
function handleEnter(e){​
var keycode = (e.keyCode ? e.keyCode : e.which);​
if (keycode == '13') {​
document.activeElement.click();​
}​
}​
</script>​
</head>​
​
<body onkeypress="handleEnter(event)">
This is especially useful for adding play/pause and making other image based controls accessible in banner ads made with Google Web Designer.
Certain HTML Elements such as span, li, div doesn't not have really have a focused state hence the focus won't trigger. What can be done in order to give the element a possible focus state is to add a tabindex and it will work, e.g:
<div tabindex="0"></div>
When I listen for keyboard events on a list, I put the handler on the <ul> instead of the <li> and it works great. It might work on the <li> as well but your example above sounds like it doesn't.

Detect when mouse actually moves, not just when the page moves

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.

Disabling Javascript event fall-throughs when dealing with z-ordered HTML elements

I have a silly (and hopefully easily fixed) problem, which I will now attempt to describe.
The scenario-> I am trying to create a context menu using HTML / CSS / JS. Just a DIV with a high z-order that appears where a user right-clicks. Simple, and that portion works. The portion which does not is my attempt to make the menu disappear if the user clicks somewhere where a context menu is not supported; I am attempting to achieve this end with a general function in the BODY tag that fires onclick. Since the BODY tag is given a z-order of -1, and any other tags which might trigger the context menu to appear are given a higher z-order value, my hope was that if I right-clicked an element with a z-order of, say, 3, then it would fire the showMenu() function; instead, it appears that it does this, as well as passes the event to the underlying BODY tag, which causes the menu to become hidden again.
As you might imagine, it is incredibly frustrating. Does anyone know how to make prevent events from being passed down? (The INPUT button is what you may want to look at, the A anchor is something similar, but not coded to work just yet).
Here's the HTML code:
http://pastebin.com/YeTxdHYq
And here's my CSS file:
http://pastebin.com/5hNjF99p
This appears to be a problem with IE, Firefox, and Chrome.
A lot of DOM events "bubble" from the bottom object up through container objects, which means they'll eventually reach the body. But you can stop this - try adding the following code to the click handler on your element:
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
...where e is the variable you already have in your function representing the event object.
event.stopPropagation(); should work in modern browsers, but the old IE way was event.cancelBubble = true; - to be safe you can just do both (but as shown above check that .stopPropagation is defined before trying to call it).
With the above code added, if you click on the element your function will stop container objects (include the body) from seeing the click. If you click somewhere else your function isn't called so then the body will process the click.
There's more info about this at MDN and QuirksMode.org.
Note: I've ignored the z-order issue because in this case I think it is a non-issue - all elements are descendents of the body so (unless you stop it) I would expect events to bubble to the body regardless of z-order.

Categories