Docs

Chapter navigation

Build a clickable chapter list that drives playback through the Player API.

Long-form videos benefit from a chapter list that lets viewers jump straight to the section they care about. Keep your chapter metadata alongside the page and seek through the Player API.

Define your chapters

const chapters = [
  { id: "intro", title: "Introduction", start: 0 },
  { id: "setup", title: "Setting up", start: 42 },
  { id: "demo", title: "Live demo", start: 187 },
  { id: "qa", title: "Q&A", start: 354 },
]

Render the navigation

<aside class="chapters">
  <h2>Chapters</h2>
  <ol id="chapter-list"></ol>
</aside>
const list = document.getElementById("chapter-list")

list.innerHTML = chapters
  .map(
    (chapter) =>
      `<li><button data-start="${chapter.start}">${chapter.title}</button></li>`,
  )
  .join("")

list.addEventListener("click", (event) => {
  const target = event.target.closest("button")
  if (!target) return
  const start = Number(target.dataset.start)
  window.Moviie.seek(start)
  window.Moviie.play()
})

Highlight the current chapter

Bind to timeupdate (throttled — it fires four times per second) and update the active chapter when crossing the next start time:

let activeId = null

window.Moviie.on("timeupdate", ({ currentTime }) => {
  const current = [...chapters]
    .reverse()
    .find((chapter) => currentTime >= chapter.start)

  if (current && current.id !== activeId) {
    activeId = current.id
    document
      .querySelectorAll("#chapter-list [data-start]")
      .forEach((node) => node.classList.toggle("active", node.dataset.start == current.start))
  }
})

Tips

  • Pair the chapter list with deep links: read ?chapter=demo from the URL on first load and seek accordingly.
  • Persist the last-watched chapter alongside the progress recipe so the next session resumes inside the right section.

On this page