可观察到的 Scrubber notebook 转换为 vanilla JS

observable Scrubber notebook convertion into vanilla JS

我正在尝试将 observablehq 'Scrubber' notebook 转换为 vanilla JS。我当前的代码如下所示:

<!DOCTYPE html>
<meta charset="utf-8">
<title>Scrubber</title>
<link rel="stylesheet" type="text/css" href="./inspector.css">
<body>
    <div class="test"></div>
    <script>
dates = ["2018-12-31T23:00:00.000Z","2019-01-01T23:00:00.000Z"]
function Scrubber(container, values, 
  format = value => value,
  initial = 0,
  delay = null,
  autoplay = true,
  loop = true,
  loopDelay = null,
  alternate = false) {
  values = Array.from(values);
  const form = '<form style="font: 12px var(--sans-serif); font-variant-numeric: tabular-nums; display: flex; height: 33px; align-items: center;">\
  <button name=b type=button style="margin-right: 0.4em; width: 5em;"></button>\
  <label style="display: flex; align-items: center;">\
    <input name=i type=range min=0 max=${values.length - 1} value=${initial} step=1 style="width: 180px;">\
    <output name=o style="margin-left: 0.4em;"></output>\
  </label>\
</form>';
  let frame = null;
  let timer = null;
  let interval = null;
  let direction = 1;
  function start() {
    form.b.textContent = "Pause";
    if (delay === null) frame = requestAnimationFrame(tick);
    else interval = setInterval(tick, delay);
  }
  function stop() {
    form.b.textContent = "Play";
    if (frame !== null) cancelAnimationFrame(frame), frame = null;
    if (timer !== null) clearTimeout(timer), timer = null;
    if (interval !== null) clearInterval(interval), interval = null;
  }
  function running() {
    return frame !== null || timer !== null || interval !== null;
  }
  function tick() {
    if (form.i.valueAsNumber === (direction > 0 ? values.length - 1 : direction < 0 ? 0 : NaN)) {
      if (!loop) return stop();
      if (alternate) direction = -direction;
      if (loopDelay !== null) {
        if (frame !== null) cancelAnimationFrame(frame), frame = null;
        if (interval !== null) clearInterval(interval), interval = null;
        timer = setTimeout(() => (step(), start()), loopDelay);
        return;
      }
    }
    if (delay === null) frame = requestAnimationFrame(tick);
    step();
  }
  function step() {
    form.i.valueAsNumber = (form.i.valueAsNumber + direction + values.length) % values.length;
    form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
  }
  

  form.i.oninput = event => {
    if (event && event.isTrusted && running()) stop();
    form.value = values[form.i.valueAsNumber];
    form.o.value = format(form.value, form.i.valueAsNumber, values);
  };
  form.b.onclick = () => {
    if (running()) return stop();
    direction = alternate && form.i.valueAsNumber === values.length - 1 ? -1 : 1;
    form.i.valueAsNumber = (form.i.valueAsNumber + direction) % values.length;
    form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
    start();
  };
  form.i.oninput();
  if (autoplay) start();
  else stop();
  Inputs.disposal(form).then(stop);
  return form;
}  
Scrubber(".test", dates)
    </script>
</body>

但我收到以下错误:未捕获类型错误:无法在 Scrubber 设置未定义的属性(设置 'oninput')。

看来这应该是一个新功能,但是我该怎么做呢?

预先感谢您的帮助。

form 只是一个未定义的字符串。我在 DOM 中附加了表单字符串并访问了表单节点,也许它会对你有所帮助。

<!DOCTYPE html>
<meta charset="utf-8">
<title>Scrubber</title>
<link rel="stylesheet" type="text/css" href="./inspector.css">
<body>
    <div class="test"></div>
    <script>
dates = ["2018-12-31T23:00:00.000Z","2019-01-01T23:00:00.000Z"]
function Scrubber(container, values, 
  format = value => value,
  initial = 0,
  delay = null,
  autoplay = true,
  loop = true,
  loopDelay = null,
  alternate = false) {
  values = Array.from(values);
  const formString = '<form style="font: 12px var(--sans-serif); font-variant-numeric: tabular-nums; display: flex; height: 33px; align-items: center;">\
  <button name=b type=button style="margin-right: 0.4em; width: 5em;"></button>\
  <label style="display: flex; align-items: center;">\
    <input name=i type=range min=0 max=${values.length - 1} value=${initial} step=1 style="width: 180px;">\
    <output name=o style="margin-left: 0.4em;"></output>\
  </label>\
</form>';

    document.querySelector(container).innerHTML = formString;

    const form = document.querySelector(container + ' form');

  let frame = null;
  let timer = null;
  let interval = null;
  let direction = 1;
  function start() {
    form.b.textContent = "Pause";
    if (delay === null) frame = requestAnimationFrame(tick);
    else interval = setInterval(tick, delay);
  }
  function stop() {
    form.b.textContent = "Play";
    if (frame !== null) cancelAnimationFrame(frame), frame = null;
    if (timer !== null) clearTimeout(timer), timer = null;
    if (interval !== null) clearInterval(interval), interval = null;
  }
  function running() {
    return frame !== null || timer !== null || interval !== null;
  }
  function tick() {
    if (form.i.valueAsNumber === (direction > 0 ? values.length - 1 : direction < 0 ? 0 : NaN)) {
      if (!loop) return stop();
      if (alternate) direction = -direction;
      if (loopDelay !== null) {
        if (frame !== null) cancelAnimationFrame(frame), frame = null;
        if (interval !== null) clearInterval(interval), interval = null;
        timer = setTimeout(() => (step(), start()), loopDelay);
        return;
      }
    }
    if (delay === null) frame = requestAnimationFrame(tick);
    step();
  }
  function step() {
    form.i.valueAsNumber = (form.i.valueAsNumber + direction + values.length) % values.length;
    form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
  }
  

  form.i.oninput = (event) => {
    if (event && event.isTrusted && running()) stop();
    form.value = values[form.i.valueAsNumber];
    form.o.value = format(form.value, form.i.valueAsNumber, values);
  };

  form.b.onclick = () => {
    if (running()) return stop();
    direction = alternate && form.i.valueAsNumber === values.length - 1 ? -1 : 1;
    form.i.valueAsNumber = (form.i.valueAsNumber + direction) % values.length;
    form.i.dispatchEvent(new CustomEvent("input", {bubbles: true}));
    start();
  };
  form.i.oninput();
  if (autoplay) start();
  else stop();
  Inputs.disposal(form).then(stop);
  return form;
}  
Scrubber(".test", dates)
    </script>
</body>

代码还有其他问题,但原始问题已解决,滑块现在可以正常工作。