Video Table of Contents

Video Table of Contents

I built an interactive video table of contents that syncs playback with a clickable chapter list. As the video plays, the current section highlights automatically; click any chapter and it jumps straight there.

See it live: https://codepen.io/josdea/pen/jOWEdEK

Why it exists

Long-form videos need better navigation. Scrubbing a timeline works, but it’s slow and you can’t see what’s where. This gives you a clean, side-by-side UI: video on the left, chapters on the right, both always in sync.

What it does

  • Automatic highlight tracking: As the video plays, the current chapter badge lights up in real time based on timeupdate events
  • Instant navigation: Click any chapter button and the video jumps to that timestamp and resumes playback
  • JSON-driven chapters: Define your table of contents as a simple array of { text, tStart } objects—no video re-encoding, no manual cue points
  • Responsive layout: Bootstrap grid keeps it usable on mobile and desktop

How it’s built

Stack:

  • Vue.js (2.x) for reactive state and DOM updates
  • HTML5 <video> element with native controls
  • Bootstrap for layout and button styling
  • Vanilla JavaScript for time parsing (MM:SS → seconds)

Key mechanics:

  • The @timeupdate event on the video element feeds currentTime into Vue’s reactive data
  • A computed class binding checks if currentTime falls between a chapter’s start and the next chapter’s start
  • The goToTime method converts MM:SS strings to seconds, sets video.currentTime, and calls .play()

Example chapter definition:

bookmarks: [
  { text: "Introduction", tStart: "0:00" },
  { text: "Link 1", tStart: "0:03" },
  { text: "Link 2", tStart: "0:06" },
  // ...
]

Core Vue setup:

var app = new Vue({
  el: "#app",
  data: {
    title: "Video Table of Contents Demo",
    currentTime_: 0,
    bookmarks: [
      { text: "Introduction", tStart: "0:00" },
      { text: "Link 1", tStart: "0:03" },
      { text: "Link 2", tStart: "0:06" },
      // ...
    ]
  },
  methods: {
    goToTime: function (event) {
      let vid = document.getElementById("myVideo");
      let bookmarkID = event.target.id;
      vid.currentTime = this.getSecondsFromTime(
        this.bookmarks[bookmarkID].tStart
      );
      vid.play();
    },
    getSecondsFromTime: function (t) {
      let tempMinutes = t.replace(/(\d?\d)?:?(\d?\d):(\d\d)/g, "$2");
      let tempSeconds = t.replace(/(\d?\d)?:?(\d?\d):(\d\d)/g, "$3");
      return (tempMinutes * 60) + tempSeconds;
    }
  }
});

Use cases

  • Educational content: Lecture recordings, tutorials, course modules
  • Product demos: Jump between feature walkthroughs
  • Meeting recordings: Navigate to specific agenda items
  • Documentation videos: Fast-track to the section you need

What makes it work well

  • No server required: Pure client-side, runs anywhere you can host HTML
  • Accessible structure: Semantic buttons, clear labels, keyboard-friendly navigation
  • Portable: Swap the video source and chapter JSON, and it adapts instantly
  • Lightweight: No heavyweight player libraries—just Vue, Bootstrap, and the browser’s native video API

Possible next steps

  • Add keyboard shortcuts (arrow keys to jump chapters)
  • Store playback position in localStorage for “resume where you left off”
  • Generate chapters from WebVTT cue files or YouTube timestamps
  • Show mini-thumbnails in the chapter list for visual scanning

This is a straightforward pattern that makes long videos less painful to navigate. If you’ve got timestamped content, this gives you a clean, reusable UI to make it immediately more usable.