You've said you want to put a box or something on the page and move it around with arrow keys. So that breaks down into:
- Having an element to put on the page
- Being able to access that element
- Being able to set its position
- Capturing keyboard events
1 - Having an element to put on the page
You can position an element on the page with full control over its location by making it an "absolutely positioned" element: Settings its position
style property to absolute
:
<div style="position: absolute;"></div>
or better
<style>
#target {
position: absolute;
}
</style>
<div id="target"></div>
For that div to actually appear, you want to give it some size and probably some visible aspect:
<style>
#target {
position: absolute;
width: 10px; /* Or whatever */
height: 10px; /* Or whatever */
border: 1px solid black;
background-color: #ddd;
}
</style>
2 - Being able to access the element
Above we gave the element an id. That means we can look it up using document.getElementById
. This isn't the only way to access an element, but it's simple and straightforward:
var element;
element = document.getElementById("target");
3 - Being able to set its position
This is done with the left
, top
, right
, and bottom
properties, which you can set via the style
property on the element instance. Let's put it 10 pixels from the top of the page and 50 pixels from the left:
element.style.top = "10px";
element.style.left = "50px";
Note that style values are always strings, and you must include the unit, just like in a stylesheet.
4 - Capturing keyboard events
For arrow keys, your best bet is the keydown
event, since it fires for non-printable keys (like arrows) and repeats if the key is held down. Here we get into one of the many places where a good library like jQuery, Prototype, YUI, Closure, or any of several others can help you, because the best way to hook up an event handler is to use the DOM2 style of handler, but although Microsoft created them, when they were standardized the name and parameters were different from Microsoft's and Microsoft took a long time to support the standard. So you have to handle both Microsoft's way (attachEvent
) and the standard way (addEventListener
). Any good library provides a simple, unified way to hook up event handlers.
This answer has a cross-browser hookEvent
function we can use for this example (full source for it is in the final example at the bottom of this answer as well). We'd use it like this:
hookEvent(document, "keydown", function(event) {
// This function gets called when there's a keydown event
// If we want to prevent the event from propagating (bubbling),
// we can use `event.stopPropagation();` here. Similarly, if
// we want to prevent any default action, we can use
// `event.preventDefault();` here
});
To find out what key was pressed, we use keyCode
or which
— another cross-browser hassle that any decent library would handle for you. So:
hookEvent(document, "keydown", function(event) {
var key = event.which || event.keyCode;
});
That sets key
to event.which
if it isn't falsey, and event.keyCode
if which
is falsey (via JavaScript's Curiously Powerful ||
operator). All that's left is to handle the keys and move the element:
hookEvent(document, "keydown", function(event) {
var element, left, top;
element = document.getElementById("target");
left = parseInt(element.style.left, 10);
top = parseInt(element.style.top, 10);
switch (event.which || event.keyCode) {
case 37: // Left
left = Math.max(0, left - 10);
break;
case 39: // Right
left += 10;
break;
case 38: // Up
top = Math.max(0, top - 10);
break;
case 40: // Down
top += 10;
break;
}
element.style.left = left + "px";
element.style.top = top + "px";
// Stop propagation and prevent default
event.stopPropagation();
event.preventDefault();
});
There we're re-getting the element every time and we're parsing the existing left
and top
values out of the style
property, which is fine as keyboard events don't happen all that often, but isn't necessary. Ideally you want to put all your code inside a "scoping function" so you don't create global symbols, and if we do that we can just get the element once and then use it in the event handler because the event handler we'll create will be a closure over the context of the scoping function (don't worry, closures are not complicated). Similarly we can use local variables within that scoping function to track left
and top
. So:
(function() {
var element = document.getElementById("target"),
left = 0,
top = 0;
element.style.left = left + "px";
element.style.top = top + "px";
hookEvent(document, "keydown", function(event) {
switch (event.which || event.keyCode) {
case 37: // Left
left = Math.max(0, left - 10);
break;
case 39: // Right
left += 10;
break;
case 38: // Up
top = Math.max(0, top - 10);
break;
case 40: // Down
top += 10;
break;
}
element.style.left = left + "px";
element.style.top = top + "px";
// Stop propagation and prevent default
event.stopPropagation();
event.preventDefault();
});
})();
This code must be after the <div id="target"></div>
on the page, because it assumes that the element is already there.
Bringing that all together
Runnable live example *(you have to click the box to make sure that frame has focus before the keys will work):
(function() {
// A cross-browser hook event function, tucked at the end to keep
// it out of the way
var hookEvent = makeHookEventFunction();
// Get the element, and our initial position
var element = document.getElementById("target"),
left = 0,
top = 0;
// Set the initial position on the element
element.style.left = left + "px";
element.style.top = top + "px";
// Handle the 'keydown' event
hookEvent(document, "keydown", function(event) {
// Work with 'which' or 'keyCode', whichever the browser gives us
switch (event.which || event.keyCode) {
case 37: // Left
left = Math.max(0, left - 10);
break;
case 39: // Right
left += 10;
break;
case 38: // Up
top = Math.max(0, top - 10);
break;
case 40: // Down
top += 10;
break;
default:
// Not for us
return;
}
// Update the element's position
element.style.left = left + "px";
element.style.top = top + "px";
// Since we've handled the key event, prevent it from
// propagating and prevent its default action
event.stopPropagation();
event.preventDefault();
});
// ------------------------------------------------------------
// A cross-browser "hook event" function, see my answer at
// https://stackoverflow.com/questions/23799296/js-li-tag-onclick-not-working-on-ie8/23799448#23799448
// for details.
function makeHookEventFunction() {
var div;
// The function we use on standard-compliant browsers
function standardHookEvent(element, eventName, handler) {
element.addEventListener(eventName, handler, false);
return element;
}
// The function we use on browsers with the previous Microsoft-specific mechanism
function oldIEHookEvent(element, eventName, handler) {
element.attachEvent("on" + eventName, function(e) {
e = e || window.event;
e.preventDefault = oldIEPreventDefault;
e.stopPropagation = oldIEStopPropagation;
handler.call(element, e);
});
return element;
}
// Polyfill for preventDefault on old IE
function oldIEPreventDefault() {
this.returnValue = false;
}
// Polyfill for stopPropagation on old IE
function oldIEStopPropagation() {
this.cancelBubble = true;
}
// Return the appropriate function; we don't rely on document.body
// here just in case someone wants to use this within the head
div = document.createElement('div');
if (div.addEventListener) {
div = undefined;
return standardHookEvent;
}
if (div.attachEvent) {
div = undefined;
return oldIEHookEvent;
}
throw "Neither modern event mechanism (addEventListener nor attachEvent) is supported by this browser.";
}
})();
#target {
position: absolute;
width: 10px;
/* Or whatever */
height: 10px;
/* Or whatever */
border: 1px solid black;
background-color: #ddd;
}
<div id="target"></div>
Some references: