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
timeupdateevents - 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
@timeupdateevent on the video element feedscurrentTimeinto Vue’s reactive data - A computed class binding checks if
currentTimefalls between a chapter’s start and the next chapter’s start - The
goToTimemethod convertsMM:SSstrings to seconds, setsvideo.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
localStoragefor “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.