Prototype: generate only visible columns of a scrollable table

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="wrapper">

  <div id="container"></div>
  <div class="buttons">
    <button id="btn-left" @click='stepLeft'>&larr;</button>
    <button id="btn-right" @click="stepRight">&rarr;</button>
  </div>

</div>

CSS

#wrapper{
  width:175px;
  margin: 0 auto;
}
#container {
  position:relative;
  width:95px;
  height:45px;
  border:1px solid grey;
  padding-top:5px;
}

.box {
  position:absolute;
  width:40px;
  height:40px;
  background:#CCC;
  transition: left 1s;
}

.buttons{
  width:95px; height:10px;
  position:relative;
}

button {position:absolute; top:5px}
#btn-right{right:0}

JavaScript

/**
prototype for a UI
TODO: throttle commands to respect animation time.

*/
const d = document;
const N = 2; // visible
const boxes = []; // N+2
var c;

d.addEventListener('keydown', function(e){
  if(e.key == 'ArrowRight') stepRight();
  if(e.key == 'ArrowLeft') stepLeft();  
});

const Box = Vue.component('box', {
  template: `<div class="box">{{ title }}</div>`,
  props: ['title']
});

const vm = new Vue({
  el: '#wrapper',
  data: {
    items: ['a','b','c','d','e','f','g','h','i','j','k'],
    pos: 0
  },
  methods: {
    stepLeft: function(){this.move(-1)},
    stepRight: function(){this.move(1)},
    move: function(inc) {
      if(this.pos <= 0 && inc < 0 ) return;
      if(this.pos >= this.items.length && inc > 0 ) return;
      this.pos += inc;
    }
  },
  mounted: function() {
    c = d.getElementById('container');

    for(let i=0; i<N+2;i++) {
      let el = d.createElement('div');
      c.appendChild(el);
      let b = new Box({
        propsData:{title: this.items[i]},
        el:el
      });
      boxes.push(b.$el);
    }
    update(); // place
  }
});
var curPosition = '';

function init() {
  for(let i=0; i<N+2; i++) {
    let el = d.createElement('div');
    el.className = "box";
    el.style.left = '' + (-45+5+i*45) + 'px';
    el.innerText = i;
    c.appendChild(el);
    boxes.push(el);
  }
}
var speed = 1;
function update(){
  for(let i=0; i<boxes.length;i++){
    boxes[i].style.left = '' + (-45+5+i*45) + 'px';
    boxes[i].style.transition = 'left ' + (1 / speed) + 's';
  }
}

function stepLeft() {
  boxes.shift().remove(); // delete leftmost
  boxes.push(createBox());
  if (curPosition !== 'left') {
  	curPosition = 'left';
    speed = 1;
  }
  speed++;
    
  update();
}

function stepRight() {
  boxes.pop().remove(); // delete leftmost
  boxes.unshift(createBox());
  if (curPosition !== 'right') {
  	curPosition = 'right';
    speed = 1;
  }
  speed++;
  update();
}

function createBox(title) {
  if(!title) title = Math.floor(0xFF *...