how to move a div with arrow keys

var pane = $('#pane'),
    box = $('#box'),
    w = pane.width() - box.width(),
    d = {},
    x = 3;

function newv(v,a,b) {
    var n = parseInt(v, 10) - (d[a] ? x : 0) + (d[b] ? x : 0);
    return n < 0 ? 0 : n > w ? w : n;
}

$(window).keydown(function(e) { d[e.which] = true; });
$(window).keyup(function(e) { d[e.which] = false; });

setInterval(function() {
    box.css({
        left: function(i,v) { return newv(v, 37, 39); },
        top: function(i,v) { return newv(v, 38, 40); }
    });
}, 20);
#pane {
  position: relative;
  width: 300px;
  height: 300px;
  border: 2px solid red;
}

#box {
  position: absolute;
  top: 140px;
  left: 140px;
  width: 20px;
  height: 20px;
  background-color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="pane">
  <div id="box"></div>
</div>

Variable explanations:
w - the maximal left/top value that the box can have (to stay within bounds)
x - the distance (in px) that the box moves in each interval
d - this object stores the information on what key is being pressed. For instance, while the user holds down the LEFT ARROW key, d['37'] is true. Otherwise it's false. BTW, 37 is the key-code for the LEFT ARROW key and this value is stored in the e.which property of the event object. The d object is being updated on each keydown and keyup event.

An setInterval which is executed every 20ms, updates the left and top CSS properties of the box element. The new values are calculated via the newv function.

The newv function will calculate the new left/top value based on a) the old value v and b) the d object.

The expression n < 0 ? 0 : n > w ? w : n ensures that the new value is in the permitted bounds (which are 0 to w). If n is < 0, zero will be returned. If n is > w, w will be returned.


Live demo: http://jsfiddle.net/simevidas/bDMnX/1299/


Update: This code has the same functionality as the original code above. The only difference is that I used more meaningful names for my variables and arguments. As you can see, it looks awful - the original version is clearly better. :P

var pane = $('#pane'),
    box = $('#box'),
    maxValue = pane.width() - box.width(),
    keysPressed = {},
    distancePerIteration = 3;

function calculateNewValue(oldValue, keyCode1, keyCode2) {
    var newValue = parseInt(oldValue, 10)
                   - (keysPressed[keyCode1] ? distancePerIteration : 0)
                   + (keysPressed[keyCode2] ? distancePerIteration : 0);
    return newValue < 0 ? 0 : newValue > maxValue ? maxValue : newValue;
}

$(window).keydown(function(event) { keysPressed[event.which] = true; });
$(window).keyup(function(event) { keysPressed[event.which] = false; });

setInterval(function() {
    box.css({
        left: function(index ,oldValue) {
            return calculateNewValue(oldValue, 37, 39);
        },
        top: function(index, oldValue) {
            return calculateNewValue(oldValue, 38, 40);
        }
    });
}, 20);

@Šime Vidas: Your first solution is simply marvelous. (i think the second one is redundant =)

May i suggest to make two different functions for the vertical and the horizontal width? Because it’s highly unlikely that you have to move around a div inside a perfect square and i believe it would be nicer to have something like this:

$(function () {
var pane = $('#pane'),
box = $('#box'),
wh = pane.width() - box.width(),
wv = pane.height() - box.height(),
d = {},
x = 5;

function newh(v,a,b) {
    var n = parseInt(v, 10) - (d[a] ? x : 0) + (d[b] ? x : 0);
    return n < 0 ? 0 : n > wh ? wh : n;
}

function newv(v,a,b) {
    var n = parseInt(v, 10) - (d[a] ? x : 0) + (d[b] ? x : 0);
    return n < 0 ? 0 : n > wv ? wv : n;
}

$(window).keydown(function(e) { d[e.which] = true; });
$(window).keyup(function(e) { d[e.which] = false; });

setInterval(function() {
    box.css({
        left: function(i,v) { return newh(v, 37, 39); },
        top: function(i,v) { return newv(v, 38, 40); }
    });
}, 20);
});

This would have been exactly what i was looking for.

If you had a responsive design based on % values it would be recommendable to adjust your setInterval like this:

setInterval(function() {
    box.css({
        left: function(i,v) { return newh(v, 37, 39); },
        top: function(i,v) { return newv(v, 38, 40); }
    });
    wh = pane.width() - box.width();
    wv = pane.height() - box.height();
}, 20);

if you do that it adjusts your panes height and width and the box still stops at its border.

i made a fiddle of that here http://jsfiddle.net/infidel/JkQrR/1/

Thanks a lot.