2020 Update
In case someone wants to do this in Vue.js , I have added the code below with comments wherever necessary and rest is self explanatory
HTML
<script type="text/x-template" id="list">
<div id="list-container" ref="root">
<div v-for="item in items" :key="item.id" class="list-item" :class="item.id === selectedId ? 'selected': ''" @click="select(item.id)">
{{item.value}}
</div>
</div>
</script>
<div id="app">
<list></list>
</div>
CSS
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
height: 100%;
}
body {
height: 100%;
min-height: 100%;
padding: 1rem;
font-family: 'Tahoma', sans-serif;
}
.list-item {
padding: 1rem 0.25rem;
}
.selected {
background: lightyellow;
}
JS
const items = new Array(100).fill(null).map((item, index) => {
return { id: index, value: "Item " + index };
});
// https://mcmap.net/q/378401/-scroll-to-element-only-if-not-in-view-jquery
function scrollIntoViewIfNeeded(target) {
var rect = target.getBoundingClientRect();
if (rect.bottom > window.innerHeight) {
target.scrollIntoView(false);
}
if (rect.top < 0) {
target.scrollIntoView();
}
}
Vue.component("list", {
template: "#list",
data() {
return {
items,
selectedId: 0
};
},
methods: {
select(itemId) {
this.selectedId = itemId;
scrollIntoViewIfNeeded(this.$refs.root.children[itemId])
// this.$refs.root.children[item.id].scrollIntoView({ behavior: "smooth" });
},
handleKeyDown(event) {
switch (event.keyCode) {
// In case of left arrow key move to the last item
case 37:
if (this.selectedId > 0) {
this.select(this.selectedId - 1);
}
// Prevent the default scroll event from firing
event.preventDefault();
break;
// In case of up arrow key, move to the last item
case 38:
if (this.selectedId > 0) {
this.select(this.selectedId - 1);
}
event.preventDefault();
break;
// In case of right arrow key, move to the next item
case 39:
if (this.selectedId < this.items.length - 1) {
this.select(this.selectedId + 1);
}
event.preventDefault();
break;
// In case of down arrow key, move to the next item
case 40:
if (this.selectedId < this.items.length - 1) {
this.select(this.selectedId + 1);
}
event.preventDefault();
break;
}
}
},
mounted() {
window.addEventListener("keydown", this.handleKeyDown);
},
destroyed() {
window.removeEventListener("keydown", this.handleKeyDown);
}
});
new Vue({
el: "#app"
});