Video Preview on Hover with Stimulus

Published: (March 12, 2026 at 01:15 PM EDT)
2 min read
Source: Dev.to

Source: Dev.to

The presentations index

After recording presentations, you need a way to browse them. A simple index page lists all presentations with video thumbnails. When you hover over a thumbnail, the video plays a preview. Move your cursor away and it returns to the poster image.

preview#play mouseleave->preview#pause"
  }
%>

Active Storage’s representable? method checks if a preview can be generated and representation() creates the thumbnail automatically.

The preview controller

All the logic happens in one Stimulus controller:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static values = {
    segments: { type: Number, default: 3 },
    interval: { type: Number, default: 1000 },
    minDuration: { type: Number, default: 5 }
  }

  connect() {
    this.originalTime = 0
    this.wasPlaying = false
    this.previewTimer = null
    this.currentIndex = 0
    this.isReady = false
    this.timestamps = []

    this.element.addEventListener("loadedmetadata", () => {
      this.#calculateTimestamps()
      this.isReady = true
    })
  }
}

Understanding loadedmetadata

The loadedmetadata event is the key here. It fires when the browser has loaded enough of the video to know its duration, dimensions and other metadata. Without this information, you can’t calculate meaningful preview timestamps.

this.element.addEventListener("loadedmetadata", () => {
  this.#calculateTimestamps()
  this.isReady = true
})

Only after loadedmetadata fires can you access this.element.duration reliably. Trying to use it before this event will return NaN or 0.

Smart timestamping

Instead of just playing from the beginning, the controller shows different parts of the video:

#calculateTimestamps() {
  const duration = this.element.duration

  if (duration  1) {
    this.previewTimer = setInterval(() => {
      this.#showNextTimestamp()
    }, this.intervalValue)
  }
}

When you hover, it saves the current state, mutes the audio and starts cycling through preview segments. When you leave, it restores everything exactly as it was.

Auto‑looping previews

The preview automatically loops through the calculated timestamps:

#showNextTimestamp() {
  this.element.currentTime = this.timestamps[this.currentIndex]
  this.element.play()

  this.currentIndex = (this.currentIndex + 1) % this.timestamps.length
}

The modulo operator (%) creates the loop: when it reaches the last segment, it goes back to the first. Combined with setInterval, this creates a cycling preview that gives users a real sense of the video content.

The key is waiting for the right moment (when metadata loads), calculating smart preview points, and cleaning up properly when the interaction ends. This small Stimulus controller demonstrates how a few lines of JavaScript can deliver a polished UX. 🥳

0 views
Back to Blog

Related posts

Read more »

Intro About Java Script

Introduction In today’s class I learned a short introduction to JavaScript, so I’ll share some facts about JavaScript in this blog. What Is JavaScript? JavaScr...

2026 WeCoded Challenge (Glass Ceiling)

!Cover image for 2026 WeCoded Challenge Glass Ceilinghttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2F...

Vite 8.0 Is Out

Article Announcing Vite 8.0https://vite.dev/blog/announcing-vite8 Discussion Hacker News threadhttps://news.ycombinator.com/item?id=47360730 – 24 points, 1 com...