JSFiddle - React, Tailwind, and code Playground
HTML
<div id="item-container">
<div class="item-row focused">1</div>
<div class="item-row">2</div>
<div class="item-row">3</div>
<div class="item-row">4</div>
<div class="item-row">5</div>
<div class="item-row">6</div>
<div class="item-row">7</div>
<div class="item-row">8</div>
</div>
CSS
#item-container {
width: 410px;
padding: 5px;
height: 440px;
background-color: rgba(0,0,255,.4);
overflow: hidden;
margin: 0 auto;
}
.item-row {
background-color: rgba(0,255,0,.9);
width: 400px;
height: 100px;
margin-bottom: 10px;
}
.focused {
-webkit-box-shadow: inset 0px 0px 0px 3px rgba(255,255,255,1);
-moz-box-shadow: inset 0px 0px 0px 3px rgba(255,255,255,1);
box-shadow: inset 0px 0px 0px 3px rgba(255,255,255,1);
}
.focused.animate {
-webkit-animation: pulse 1.8s infinite ease-in-out;
-moz-animation: pulse 1.8s infinite ease-in-out;
animation: pulse 1.8s infinite ease-in-out;
}
@-webkit-keyframes pulse {
0% { -webkit-box-shadow: inset 0px 0px 0px 2px rgba(255,255,255,1); }
50% { -webkit-box-shadow: inset 0px 0px 0px 2px rgba(255,255,255,.4); }
100% { -webkit-box-shadow: inset 0px 0px 0px 2px rgba(255,255,255,1); }
}
@-moz-keyframes pulse {
0% { -moz-box-shadow-color: inset 0px 0px 0px 2px rgba(255,255,255,1); }
50% { -moz-box-shadow-color: inset 0px 0px 0px 2px rgba(255,255,255,.4); }
100% { -moz-box-shadow-color: inset 0px 0px 0px 2px rgba(255,255,255,1); }
}
@keyframes pulse {
0% { box-shadow-color: inset 0px 0px 0px 2px rgba(255,255,255,1); }
50% { box-shadow-color: inset 0px 0px 0px 2px rgba(255,255,255,.4); }
100% { box-shadow-color: inset 0px 0px 0px 2px rgba(255,255,255,1); }
}
JavaScript
var pool = document.querySelectorAll('.item-row')
, container = document.getElementById('item-container')
, containerBox = container.getBoundingClientRect()
, focused = pool[0]
, focusIndex = 0
, KEYS = {up: 38, down: 40}
, keypressTimer = null;
window.addEventListener('keyup', function(e) {
if (e.keyCode === KEYS.up && focusIndex !== 0) {
focusIndex--;
setFocus()
} else if (e.keyCode === KEYS.down && focusIndex !== pool.length - 1) {
focusIndex++;
setFocus()
}
});
function setFocus() {
clearTimeout(keypressTimer);
focused.classList.remove('animate');
focused.classList.remove('focused');
focused = pool[focusIndex];
focused.classList.add('focused');
scroll();
keypressTimer = setTimeout(function() {
focused.classList.add('animate');
}, 1000);
}
function scroll() {
var focusedBox = focused.getBoundingClientRect();
if (focusedBox.bottom > containerBox.bottom || focusedBox.top < containerBox.top) {
requestAnimationFrame(function() {
var distance = focusedBox.height + 12;
animate(distance, focusedBox.top < containerBox.top, 20);
});
}
}
function animate(distance, up, speed) {
if (distance >= speed) {
container.scrollTop += (up ? -speed : speed);
requestAnimationFrame(function() {
animate(distance - speed, up, speed);
});
} else {
container.scrollTop += (up ? -distance : distance);
}
}