npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

videoshorts

v1.7.1

Published

A lightweight YouTube video library with lazy loading support

Readme

VideoShorts

A lightweight YouTube video library with lazy loading support. Transform YouTube URLs into embedded iframes with full API control.

Features

  • 🚀 Lazy Loading - Videos load only when visible in viewport
  • 📱 Responsive - Configurable sizes and aspect ratios
  • 🎮 Full API Control - Play, pause, mute, seek, and more
  • 💀 Skeleton Loading - Smooth loading experience
  • Lightweight - No dependencies
  • 🎨 Customizable - CSS classes fully customizable
  • 🔌 Library Friendly - Compatible with SwiperJS, Slick, and other carousel/slider libraries

Installation

NPM

npm install videoshorts

CDN

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/eulucastiagolt/[email protected]/dist/videoshorts.min.css">
<script src="https://cdn.jsdelivr.net/gh/eulucastiagolt/[email protected]/dist/videoshorts.min.js"></script>

Manual

Download the files from dist/ folder:

  • videoshorts.min.js
  • videoshorts.min.css

Quick Start

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="videoshorts.min.css">
</head>
<body>
  <div id="videos" data-videoshort></div>

  <script src="videoshorts.min.js"></script>
  <script>
    const videos = [
      'https://www.youtube.com/shorts/ZDqo7XROuwM',
      'https://www.youtube.com/watch?v=SGfb6y1j5x0'
    ];

    const player = new VideoShorts('#videos', videos);
    player.init();
  </script>
</body>
</html>

Options

const player = new VideoShorts(container, videos, {
  // CSS Classes
  containerClass: 'videoshort-container',
  wrapperClass: 'videoshort-wrapper',
  itemClass: 'videoshort-item',
  skeletonClass: 'videoshort-skeleton',
  overlayClass: 'videoshort-overlay',
  playButtonClass: 'videoshort-play-button',
  muteButtonClass: 'videoshort-mute-button',

  // Button Icons
  playButtonIcon: '▶',
  pauseButtonIcon: '⏸',
  muteButtonIcon: '🔇',
  unmuteButtonIcon: '🔊',

  // Lazy Loading
  lazy: true,
  lazyThreshold: 0.1,
  lazyRootMargin: '200px',

  // Size
  width: '360px',
  height: null,
  aspectRatio: '9/16',

  // YouTube Player
  autoplay: false,
  muted: false,
  loop: false,
  controls: 1,
  rel: 0,
  modestbranding: 1,

  // Insert Position (for compatibility with sliders/carousels)
  insertPositionWrapper: 'beforeend',
  insertPositionItem: 'beforeend',

  // Events
  onReady: (event, index, instance) => {},
  onStateChange: (event, index, instance) => {},
  onEnd: (event, index, instance) => {},
  onPlay: (event, index, instance) => {},
  onError: (event, index, instance) => {}
});

Options Details

| Option | Type | Default | Description | |--------|------|---------|-------------| | containerClass | string | 'videoshort-container' | CSS class added to the container element | | wrapperClass | string | 'videoshort-wrapper' | CSS class for the wrapper element | | itemClass | string | 'videoshort-item' | CSS class for each video item | | skeletonClass | string | 'videoshort-skeleton' | CSS class for skeleton loader | | overlayClass | string | 'videoshort-overlay' | CSS class for the overlay element | | playButtonClass | string | 'videoshort-play-button' | CSS class for the play/pause button | | muteButtonClass | string | 'videoshort-mute-button' | CSS class for the mute/unmute button | | playButtonIcon | string | '<svg>...</svg>' | SVG icon for play button | | pauseButtonIcon | string | '<svg>...</svg>' | SVG icon for pause button | | muteButtonIcon | string | '<svg>...</svg>' | SVG icon for muted state | | unmuteButtonIcon | string | '<svg>...</svg>' | SVG icon for unmuted state | | showThumbnail | boolean | true | Show video thumbnail over player | | thumbnailClass | string | 'videoshort-thumbnail' | CSS class for thumbnail element | | thumbnailTransitionDuration | string | '0.3s' | Duration of thumbnail fade transition | | showThumbnailOnPause | boolean | false | Show thumbnail when video is paused | | thumbnailFallbackIcon | string | '<svg>...</svg>' | SVG icon when thumbnail fails to load | | lazy | boolean | true | Enable lazy loading | | lazyThreshold | number | 0.1 | Intersection threshold (0-1) | | lazyRootMargin | string | '200px' | Load videos before they enter viewport | | width | string | '360px' | Width of video items | | height | string | null | Fixed height (overrides aspectRatio) | | aspectRatio | string | '9/16' | Aspect ratio of videos | | autoplay | boolean | false | Autoplay videos when loaded | | muted | boolean | false | Mute videos by default | | loop | boolean | false | Loop videos | | controls | number | 1 | Show player controls | | rel | number | 0 | Show related videos | | modestbranding | number | 1 | Hide YouTube logo | | insertPositionWrapper | string | 'beforeend' | Position to insert wrapper element in container | | insertPositionItem | string | 'beforeend' | Position to insert video items in wrapper |

Methods

Playback Control

// Play all videos
player.play();

// Play specific video
player.play(0);

// Pause all videos
player.pause();

// Pause specific video
player.pause(0);

// Stop all videos
player.stop();

// Stop specific video
player.stop(0);

Volume Control

// Mute all videos
player.mute();

// Mute specific video
player.mute(0);

// Unmute all videos
player.unMute();

// Unmute specific video
player.unMute(0);

// Toggle mute
player.toggleMute(0);

// Set volume (0-100)
player.setVolume(50);
player.setVolume(50, 0);

// Get volume
const vol = player.getVolume(0);

Seek Control

// Seek to seconds
player.seekTo(30);
player.seekTo(30, 0);

// Get current time
const time = player.getCurrentTime(0);

// Get duration
const duration = player.getDuration(0);

State Management

// Check if video is playing
const playing = player.isPlaying(0);

// Check if video is muted
const muted = player.isMuted(0);

// Get player state
const state = player.getPlayerState(0);

// Check if video is loaded
const loaded = player.isLoaded(0);

Size Control

// Change size dynamically
player.setSize({
  width: '480px',
  aspectRatio: '16/9'
});

Player Access

// Get specific YT.Player instance
const ytPlayer = player.getPlayer(0);

// Get all YT.Player instances
const allPlayers = player.getPlayers();

// Force load a video
player.loadVideo(0);

Event Management

// Add event listener
player.on('play', (event, index, instance) => {
  console.log(`Video ${index} playing`);
});

// Remove specific listener
player.off('play', callback);

// Remove all listeners for an event
player.off('play');

// Remove all listeners
player.off();

DOM Synchronization

// Refresh players to sync with DOM changes (useful for sliders with loop)
player.refresh();

Cleanup

// Destroy all players and clean up
player.destroy();

Events

const player = new VideoShorts('#container', videos, {
  onReady: (event, index, instance) => {
    console.log(`Video ${index} is ready`);
  },

  onStateChange: (event, index, instance) => {
    // event.data values:
    // -1: unstarted
    //  0: ended
    //  1: playing
    //  2: paused
    //  3: buffering
    //  5: video cued
    console.log(`Video ${index} state:`, event.data);
  },

  onPlay: (event, index, instance) => {
    console.log(`Video ${index} started playing`);
  },

  onEnd: (event, index, instance) => {
    console.log(`Video ${index} ended`);
  },

  onError: (event, index, instance) => {
    console.error(`Video ${index} error:`, event.data);
  }
});

Event System

VideoShorts provides a flexible event system that allows you to add, remove, and emit events dynamically. This is useful when you need multiple listeners for the same event or want to manage events programmatically.

Adding Events

const player = new VideoShorts('#container', videos, options);

// Add event listeners
player.on('ready', (event, index, instance) => {
  console.log(`Video ${index} is ready`);
});

player.on('play', (event, index, instance) => {
  console.log(`Video ${index} started playing`);
});

player.on('pause', (event, index, instance) => {
  console.log(`Video ${index} paused`);
});

player.on('end', (event, index, instance) => {
  console.log(`Video ${index} ended`);
});

player.on('stateChange', (event, index, instance) => {
  console.log(`Video ${index} state:`, event.data);
});

player.on('error', (event, index, instance) => {
  console.error(`Video ${index} error:`, event.data);
});

player.on('load', (index, instance) => {
  console.log(`Video ${index} started loading`);
});

player.init();

Removing Events

// Define a callback
const onPlay = (event, index, instance) => {
  console.log(`Video ${index} playing`);
};

// Add listener
player.on('play', onPlay);

// Remove specific listener
player.off('play', onPlay);

// Remove all listeners for 'play' event
player.off('play');

// Remove all listeners for all events
player.off();

Available Events

| Event | Arguments | Description | |-------|-----------|-------------| | ready | (event, index, instance) | Player is ready | | play | (event, index, instance) | Video started playing | | pause | (event, index, instance) | Video paused | | end | (event, index, instance) | Video ended | | stateChange | (event, index, instance) | Player state changed | | error | (event, index, instance) | Error occurred | | load | (index, instance) | Video started loading (lazy) |

Using with Slider Libraries (Loop Mode)

When using slider libraries with loop mode (like SwiperJS), the DOM elements get cloned. The refresh() method helps synchronize players with the updated DOM:

const player = new VideoShorts('#video-container', videos, options);

player.init().then(() => {
  const swiper = new Swiper('.swiper', {
    loop: true,
    // ...other options
  });

  // Refresh players when slides change
  swiper.on('slideChange', () => player.refresh());
  
  // Optional: refresh on loop fix
  swiper.on('loopFix', () => player.refresh());
});

Integration Examples

// SwiperJS
swiper.on('slideChange', () => player.refresh());

// Splide
splide.on('moved', () => player.refresh());

// Keen Slider
slider.on('slideChanged', () => player.refresh());

// Any custom slider
mySlider.onChange(() => player.refresh());

Supported URL Formats

const videos = [
  'https://www.youtube.com/watch?v=VIDEO_ID',
  'https://youtu.be/VIDEO_ID',
  'https://www.youtube.com/shorts/VIDEO_ID',
  'https://www.youtube.com/embed/VIDEO_ID'
];

Styling

Default Theme

The included CSS provides a minimal base theme. Override with your own classes:

const player = new VideoShorts('#container', videos, {
  containerClass: 'my-container',
  wrapperClass: 'my-wrapper',
  itemClass: 'my-item',
  skeletonClass: 'my-skeleton'
});

Custom Skeleton

.my-skeleton {
  background: linear-gradient(90deg, #e0e0e0 0%, #f0f0f0 50%, #e0e0e0 100%);
  background-size: 200% 100%;
  animation: my-skeleton-animation 1.5s ease-in-out infinite;
}

@keyframes my-skeleton-animation {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

Different Aspect Ratios

// YouTube Shorts (9:16)
new VideoShorts('#shorts', shortsVideos, {
  width: '280px',
  aspectRatio: '9/16'
});

// Regular Videos (16:9)
new VideoShorts('#videos', videos, {
  width: '480px',
  aspectRatio: '16/9'
});

// Square (1:1)
new VideoShorts('#square', squareVideos, {
  width: '300px',
  aspectRatio: '1/1'
});

// Fixed Height
new VideoShorts('#fixed', fixedVideos, {
  width: '100%',
  height: '400px'
});

Integration with Other Libraries

VideoShorts is designed to work seamlessly with slider/carousel libraries like SwiperJS, Slick, and others.

SwiperJS Example

<!DOCTYPE html>
<html>
<head>
  <!-- Swiper CSS -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
  <!-- VideoShorts CSS -->
  <link rel="stylesheet" href="videoshorts.min.css">
</head>
<body>
  <div class="swiper">
    <div class="swiper-wrapper" id="video-container"></div>
    <div class="swiper-pagination"></div>
    <div class="swiper-button-next"></div>
    <div class="swiper-button-prev"></div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
  <script src="videoshorts.min.js"></script>
  <script>
    const videos = [
      'https://www.youtube.com/shorts/ZDqo7XROuwM',
      'https://www.youtube.com/watch?v=SGfb6y1j5x0',
      'https://www.youtube.com/shorts/abc123'
    ];

    // Initialize VideoShorts with insert positions
    const player = new VideoShorts('#video-container', videos, {
      insertPositionWrapper: 'beforeend',
      insertPositionItem: 'beforeend'
    });

    player.init().then(() => {
      // Initialize Swiper after videos are rendered
      const swiper = new Swiper('.swiper', {
        slidesPerView: 1,
        spaceBetween: 20,
        loop: true, // Works with loop mode!
        pagination: {
          el: '.swiper-pagination',
        },
        navigation: {
          nextEl: '.swiper-button-next',
          prevEl: '.swiper-button-prev',
        }
      });
    });
  </script>
</body>
</html>

Loop Mode Support

VideoShorts is fully compatible with slider/carousel libraries that use loop mode (like SwiperJS's loop: true). When slides are cloned for infinite looping, the library automatically syncs all UI elements (play/pause buttons, mute buttons, thumbnails) across both original and cloned slides.

This is achieved through:

  • data-instance-id attribute to isolate elements per VideoShorts instance
  • data-video-index attribute to identify videos across clones
  • Dynamic element queries that find all matching elements (original + clones)

Insert Position Options

The insertPosition options control where elements are inserted in the DOM. Possible values:

| Value | Description | |-------|-------------| | 'beforebegin' | Before the element itself | | 'afterbegin' | Just inside the element, before its first child | | 'beforeend' | Just inside the element, after its last child | | 'afterend' | After the element itself |

Use these options to control the exact placement of video elements within your slider/carousel structure.

Build

npm run build

Output files in dist/:

  • videoshorts.min.js
  • videoshorts.min.css

Browser Support

  • Chrome 51+
  • Firefox 55+
  • Safari 12.1+
  • Edge 15+

Requires IntersectionObserver API (lazy loading).

License

MIT License - see LICENSE file.

Author

Lucas Tiago

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.