timer.js 179 regels JavaScript
/**
 * @param {string|number} defaultTime - number of seconds, or formatted string `H:MM:SS` (hours and minutes optional)
 * @param {*} persist - disable saving of timer by passing `false`. true otherwise.
 * @example
 *     <script src="/js/timer.js"></script>
 *     <script>new BonarooTimer("01:00").write();</script>
 */
function BonarooTimer(defaultTime, persist) {
  this.rootElement = document.createElement("div");
  this.rootElement.innerHTML = BonarooTimer.TEMPLATE;

  this.inputElement = this.rootElement.querySelector("[data-input]");

  this.inputElement.addEventListener("change", (function (event) {
    event.preventDefault();
    this.setTime(this.inputElement.value);
  }).bind(this))

  this.startStopButton = this.rootElement.querySelector("[data-toggle]");
  this.startStopButton.addEventListener("click", this.toggle.bind(this));

  this.resetButton = this.rootElement.querySelector("[data-reset]");
  this.resetButton.addEventListener("click", this.reset.bind(this));
  this.resetButton.textContent = BonarooTimer.RESET_LABEL;

  this.defaultTime = typeof defaultTime === "undefined" ? 60 : BonarooTimer.parseTime(defaultTime);
  this.time = this.defaultTime;
  this.running = false;
  this.timeoutId = null;

  /** @var boolean whether saving is enabled or not */
  this.persist = persist !== false;

  if (this.persist) {
    this.restore();
  }
  this.updateGuiLater();
}

BonarooTimer.prototype.setTime = function setTime(val) {
  this.time = BonarooTimer.parseTime(val);
  if (this.running) {
    this.start();
  }
}

BonarooTimer.prototype.updateGui = function updateGui() {
  if (this.updateTimeoutId) {
    clearTimeout(this.updateTimeoutId);
    this.updateTimeoutId = null;
  }
  this.startStopButton.textContent = this.running ? BonarooTimer.STOP_LABEL : BonarooTimer.START_LABEL;
  if (this.inputElement !== document.activeElement) {
    this.inputElement.value = BonarooTimer.formatTime(this.time);
  }
}

/**
 * Write the root element before the current executing script element.
 */
BonarooTimer.prototype.write = function write() {
  if (document.currentScript) {
    document.currentScript.insertAdjacentElement("beforebegin", this.rootElement);
  } else {
    throw new Error("BonarooTimer.write can only be called inside SCRIPT element");
  }
};

BonarooTimer.prototype.toggle = function toggle() {
  if (this.running) {
    this.stop();
  } else {
    this.start();
  }
}

BonarooTimer.prototype.stop = function stop() {
  if (this.timeoutId) {
    clearInterval(this.timeoutId);
    this.timeoutId = null;
  }
  this.running = false;
  this.updateGuiLater();

  if (this.persist) {
    window.localStorage.removeItem("timer-time-end");
  }
}

BonarooTimer.prototype.start = function start() {
  this.stop();
  this.running = true;
  this.timeoutId = setInterval(this.next.bind(this), 1000);
  this.updateGuiLater();

  if (this.persist) {
    window.localStorage.setItem("timer-time-end", `${Math.round(new Date().getTime() / 1000 + this.time)}`);
  }
}

BonarooTimer.prototype.restore = function restore() {
  if (this.persist) {
    var endTimeStr = window.localStorage.getItem("timer-time-end");
    if (endTimeStr) {
      var endTime = parseInt(endTimeStr, 10);
      var currentTime = Math.round(new Date().getTime() / 1000);
      var timeRemaining = endTime - currentTime;
      if (timeRemaining > 0) {
        this.time = timeRemaining;
        this.start();
      } else {
        this.time = 0;
      }
    }
    this.updateGuiLater();
  }
}

BonarooTimer.prototype.reset = function reset() {
  this.stop();
  this.time = this.defaultTime;
  this.updateGuiLater();
}

BonarooTimer.prototype.updateGuiLater = function updateGuiLater() {
  if (!this.updateTimeoutId) {
    this.updateTimeoutId = setTimeout(this.updateGui.bind(this), 0);
  }
}

BonarooTimer.prototype.next = function next() {
  this.time--;
  this.updateGui();
  if (this.time <= 0) {
    this.stop();
  }
}

BonarooTimer.TEMPLATE = "<input data-input><button data-toggle type=button /><button data-reset type=button />";
BonarooTimer.STOP_LABEL = "Stop";
BonarooTimer.START_LABEL = "Start";
BonarooTimer.RESET_LABEL = "Reset";

BonarooTimer.parseTime = function parseTime(val) {
  if (typeof val === "number") {
    return val;
  }
  if (!val) {
    return 0;
  }
  var match = val.match(/(?:(?:(\d+)?:)?(\d+)?:)?(\d+)/);
  if (!match) {
    return 0;
  }
  var hours = parseInt(match[1] || "0", 10);
  var minutes = parseInt(match[2] || "0", 10);
  var seconds = parseInt(match[3] || "0", 10);
  return hours * 3600 + minutes * 60 + seconds;
}

BonarooTimer.formatTime = function formatTime(time) {
  if (time < 0) {
    time = 0;
  }
  const hours = Math.floor(time / 3600);
  const minutes = Math.floor((time / 60) % 60);
  const seconds = Math.floor(time % 60);
  if (hours > 0) {
    return BonarooTimer.lpad(hours) + ":" + BonarooTimer.lpad(minutes) + ":" + BonarooTimer.lpad(seconds)
  } else {
    return BonarooTimer.lpad(minutes) + ":" + BonarooTimer.lpad(seconds);
  }
}

BonarooTimer.lpad = function lpad(num) {
  num = Math.round(num);
  return num < 10 ? `0${num}` : `${num}`;
}