Matt Pasta
Matt Pasta

Reputation: 183

setInterval() isn't running up to speed

I'm making a gallery on a site I'm working on. It scrolls through photos automatically, but the user can pause and manually scroll as well. The issue I'm having is in the automatic scroll timing. I made a setInterval() to run an autoScroll() every millisecond. In the autoScroll(), it increases a variable, then scrolls and sets the variable back to 0 once the variable reaches 3000. I'm doing it like that instead of using 3000 milliseconds on a setInterval that directly calls the scroll so that I can reset the timer when the user pauses or scrolls manually. In theory, it should scroll every 3 seconds. But it's much slower than that. In Chrome, it takes about 12 seconds, and it takes about 45 seconds in Firefox. I haven't tested it on any other browsers yet.

Currently, I'm running it directly on my computer, so there's no server involved yet. So it should be a code issue and not a server issue.

Here's the relevant HTML the script references:

<table>
  <tr>
    <th>
      <button id="left">&#10094;</button>
    </th>
    <th id="picture" colspan="3"></th>
    <th>
      <button id="right">&#10095;</button>
    </th>
  </tr>
  <tr>
    <td></td>
    <td id="pause">&#10074;&#10074;</td>
    <td id="below"><span id="caption" style="text-align: center;"></span><br><span id="dots"
        style="text-align: center;"></span></td>
    <td style="width: 5%"></td>
    <td></td>
  </tr>
</table>

And here's the javascript:

var pics = ["pic1.jpg", "pic2.jpg", "pic3.jpg", "pic4.jpg", "pic5.jpg"];
var captions = ["caption 1", "caption 2", "caption 3", "caption 4", "caption 5",];
var play = true;
var position = 0;
var direction = 0;
var auto = 0;

for (var x = 1; x < pics.length + 1; x++) {
  dots.innerHTML = dots.innerHTML + '<input id="dot' + x.toString() + '" type="radio" name="dot" value="' + x.toString() + '"><label for="dot' + x.toString() + '">&#9679;</label>';
}

dot1.checked = true;
picture.innerHTML = '<img src="' + pics[0] + '" />';
caption.innerHTML = captions[0];

function move() {
  var positionIndex = position + 1;
  position = position + direction;
  if (position == -1) { position = pics.length - 1; }
  if (position == pics.length) { position = 0; }
  positionIndex = position + 1;
  picture.innerHTML = '<img src="' + pics[position] + '" />';
  caption.innerHTML = captions[position];
  document.getElementById("dot" + positionIndex.toString()).checked = true;
  auto = 0;
}

left.onclick = function () {
  direction = -1;
  move();
}

right.onclick = function () {
  direction = 1;
  move();
}

dots.onclick = function () {
  position = parseInt(document.querySelector('input[name = "dot"]:checked').value) - 1;
  positionIndex = position + 1;
  picture.innerHTML = '<img src="' + pics[position] + '" />';
  caption.innerHTML = captions[position];
  document.getElementById("dot" + positionIndex.toString()).checked = true;
  auto = 0;
}

pause.onclick = function () {
  if (play) {
    play = false;
    pause.innerHTML = "&#9654;";
  } else {
    play = true;
    pause.innerHTML = "&#10074;&#10074;";
  }
  auto = 0;
}

function autoScroll() {
  auto++
  if (auto == 3000 && play) {
    direction = 1;
    auto = 0;
    move();
  }
}

window.setInterval(autoScroll, 1);

Upvotes: 0

Views: 315

Answers (1)

Honza
Honza

Reputation: 569

Due to meltdown/spectre bugs mitigations, all browsers deliberately make timers less precise. I'm not sure if setInterval is considered high-resolution timer, but it may be affected as well.

https://developers.google.com/web/updates/2018/02/meltdown-spectre

Even before that, setInterval never guaranteed it will fire precisely after provided time. If the browser would be busy, it can get worse.

In short, instead of increasing the variable by 1 every setInterval, you should check current time (with +(new Date()) for example) and see how much time really passed.

  ...
  auto = +(new Date());
}
function autoScroll() {
  var date = +(new Date());
  if(auto > date + 3000 && play) {
    direction = 1;
    auto = date;
    move();
  }
}

Or, as already suggested, abandon the idea of using shorter setInterval, save a reference to timer and then cancelling that timer and re-starting it if the user clicks.

  ...
  resetTimer();
}
function autoScroll() {
  if(play) {
    direction = 1;
    resetTimer();
    move();
  }
}
function resetTimer() {
   if(timer) {
       window.clearInterval(timer);
   }
   timer = window.setInterval(autoScroll,3000);
}
timer = window.setInterval(autoScroll,3000);

Upvotes: 2

Related Questions