<div id="map">
  <div id="tiles"></div>
  <div id="loader" hidden>
    <div class="lds-ring"><div></div><div></div><div></div><div></div></div>
  </div>
</div>
#map {
  width: 100%;
  height: 100vh;
  background: wihte;
  overflow: hidden;
  cursor: grab;
}
#map.moving { cursor: grabbing; }
#tiles {
  position: relative;
  background-image:
    linear-gradient(45deg, gray 25%, transparent 25%, transparent 74%, gray 75%, gray),
    linear-gradient(45deg, gray 25%, transparent 25%, transparent 74%, gray 75%, gray);
  background-size: 20px 20px;
  background-position: 0 0, 10px 10px;
}
.tile {
  position: absolute;
  pointer-events: none;
}
#loader {
  position: absolute;
  left: 50%;
  top: 0;
  z-index:200;
  transform: translateX(-50%);
}
.lds-ring {
  display: inline-block;
  position: relative;
  width: 64px;
  height: 64px;
}
.lds-ring div {
  box-sizing: border-box;
  display: block;
  position: absolute;
  width: 51px;
  height: 51px;
  margin: 6px;
  border: 6px solid #fff;
  border-radius: 50%;
  animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
  border-color: #fff transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
  animation-delay: -0.45s;
}
.lds-ring div:nth-child(2) {
  animation-delay: -0.3s;
}
.lds-ring div:nth-child(3) {
  animation-delay: -0.15s;
}
@keyframes lds-ring {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
const imageWidth = 200;
const imagesPath = 'http://lss.format23.ru/screens/';
const imagePrefix = 'Image_';

let map = document.getElementById('map');
let tiles = document.getElementById('tiles');

const data = [];
let maxX = 0;
let maxY = 0;

const delta = 1500;

const imgs = {};

function Loader() {
  this.loader = document.getElementById('loader');
  this.counter = 0;
  this.show = () => {
    this.counter++;
    this.loader.hidden = false;
  }
  this.hide = () => {
    this.counter--;
    if (this.counter <= 0) this.loader.hidden = true;
  }
}

const loader = new Loader();

const leadingZero = n => ('0000' + n).substr(-4);

const makeKey = imageDef => imageDef.src + '_' + imageDef.x + '_' + imageDef.y;

const isVisible = (item,  mapCenter) => item.x > mapCenter.x - map.clientWidth / 2 - delta
    && item.x < mapCenter.x + map.clientWidth / 2 + delta
    && item.y > mapCenter.y - map.clientHeight / 2 - delta
    && item.y < mapCenter.y + map.clientHeight / 2 + delta;

const initData = () => {
  let coordX = 0;
  let coordY = 30300;

  let gamecorX = 0;
  let gamecorY = 1212;

  for (let imageIndex = 0; imageIndex < 30400; imageIndex++) {

    if (imageIndex % 301 == 0) {
      coordX = 0 + 600 * (imageIndex / 301);
      coordY = 30300 - 300 * (imageIndex / 301);  
      gamecorY = gamecorY - 12; 
      gamecorX = 0;
    }

    data.push({
      src: leadingZero(imageIndex) + '.jpg',
      x: coordX,
      y: coordY,
      gx: gamecorX,
      gy: gamecorY,
    });

    coordX += 200;
    coordY += 100;
    gamecorX += 4;
    maxX = Math.max(maxX, coordX);
    maxY = Math.max(maxY, coordY);
  }
}

const initView = () => {
  tiles.style.width = maxX + 'px';
  tiles.style.height = maxY + 'px';

  map.scrollLeft = maxX / 2 - map.clientWidth / 2;
  map.scrollTop = maxY / 2 - map.clientHeight / 2;
}

const updateViewport = () => {
  let statCounter = 0;
  const mapCenter = {
    x: map.scrollLeft + map.clientWidth / 2,
    y: map.scrollTop + map.clientHeight / 2,
  }

  let images = data.filter(item => isVisible(item,  mapCenter));

  images.forEach(image => {
    let key = makeKey(image);
    if (!imgs[key]) {
      const img = document.createElement('img');
      loader.show();
      img.classList.add('tile');
      img.style.left = image.x + 'px';
      img.style.top = image.y + 'px';
      img.onload = () => loader.hide();
      img.src = imagesPath + imagePrefix + image.src;

      imgs[key] = img;
      tiles.appendChild(img);
      statCounter++;
    }
  });
  
  console.log('Добавлено ' + statCounter + ' элементов');
};

initData();
initView();
updateViewport();


const moveCoords = {x:0, y:0, l:0, t:0};
const moving = event => {
    map.scrollLeft = moveCoords.l - (event.screenX - moveCoords.x);
    map.scrollTop = moveCoords.t - (event.screenY - moveCoords.y);
};

map.addEventListener('mousedown', event => {
  map.classList.add('moving');
  moveCoords.x = event.screenX;
  moveCoords.y = event.screenY;
  moveCoords.l = map.scrollLeft;
  moveCoords.t = map.scrollTop;

  document.addEventListener('mousemove', moving);
});

document.addEventListener('mouseup', event => {
  document.removeEventListener('mousemove', moving);
  map.classList.remove('moving');

  updateViewport();

  let statCounter = 0;
  const mapCenter = {
    x: map.scrollLeft + map.clientWidth / 2,
    y: map.scrollTop + map.clientHeight / 2,
  }

  data
    .filter(item => !isVisible(item,  mapCenter))
    .forEach(image => {
      let key = makeKey(image);
      if (imgs[key]) {
        tiles.removeChild(imgs[key]);
        imgs[key] = null;
        statCounter++;
      }
    });

  console.log('Удалено ' + statCounter + ' элементов');
});

let tmp;
window.addEventListener('resize', event => {
  clearTimeout(tmp);
  tmp = setTimeout(() => updateViewport(), 300);
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.