# Carousel

Build carousels and sliders with Carousel JavaScript plugin.

[![npm](https://img.shields.io/badge/npm-v4.2.0-blue)](https://www.npmjs.com/package/@preline/carousel) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Demo](https://img.shields.io/badge/demo-online-brightgreen)](https://preline.co/plugins/carousel.html)

## Contents

- [Overview](#overview)
- [Installation](#installation)
- [Basic usage](#basic-usage)
- [Configuration Options](#configuration-options)
- [JavaScript API](#javascript-api)
- [Events](#events)
- [Common Patterns](#common-patterns)
- [License](#license)

## Overview

The Carousel component provides a flexible, feature-rich slider for displaying multiple items in a scrollable container. It supports autoplay, infinite loop, drag gestures, responsive breakpoints, and multiple display modes.

**Key Features:**
- Multiple slides in a scrollable container
- Autoplay and infinite loop support
- Drag/swipe gestures for navigation
- Responsive breakpoints for different screen sizes
- Multiple display modes (default, snap, bounded)
- Programmatic control via JavaScript API
- Keyboard navigation support
- Customizable pagination dots

## Installation

To get started, install Carousel plugin via npm, else you can skip this step if you are already using Preline UI as a package.

```bash
npm i @preline/carousel
```

### CSS

Use [`@source`](https://tailwindcss.com/docs/functions-and-directives#source-directive) to register the plugin's JavaScript path for Tailwind CSS scanning, then [`@import`](https://tailwindcss.com/docs/functions-and-directives#import-directive) the plugin's CSS files into your Tailwind CSS file.

```css
@import "tailwindcss";

/* @preline/carousel */
@source "../node_modules/@preline/carousel/*.js";
@import "./node_modules/@preline/carousel/variants.css";
@import "./node_modules/@preline/carousel/theme.css";
```

### JavaScript

Include the JavaScript that powers the interactive elements near the end of your `</body>` tag:

```html
<script src="./node_modules/@preline/carousel/index.js"></script>
```

### Manual Initialization

Use the `non-auto` entry if you need manual initialization. In this mode, automatic initialization on page load is not included, so the component should be initialized explicitly.

```html
<script type="module">
  import HSCarousel from "@preline/carousel/non-auto.mjs";

  new HSCarousel(document.querySelector("#carousel"));
</script>
```

### Via Bundler

When using a bundler (Vite, webpack, etc.), import the plugin directly as an ES module.

`@preline/carousel` is the auto-init entry: it scans the DOM and initializes matching elements automatically.

```js
import "@preline/carousel";
```

`@preline/carousel/non-auto` is the manual entry: use it when you want explicit control over when initialization happens, either via `autoInit()` or by creating a specific instance yourself.

```js
import HSCarousel from "@preline/carousel/non-auto";

HSCarousel.autoInit();

// Or initialize a specific element manually
const el = document.querySelector("#carousel");
if (el) new HSCarousel(el);
```

### TypeScript

This package ships with TypeScript type definitions. No additional `@types/` package is needed.

## Basic usage

The following example demonstrates the minimal HTML structure required for a carousel component. This is a base template without custom styling - you can apply your own CSS classes and styles as needed. The carousel displays three slides with previous/next navigation buttons.

```html
<div data-hs-carousel='{
    "loadingClasses": "opacity-0"
  }' class="relative">
  <div class="hs-carousel relative overflow-hidden w-full min-h-64">
    <div class="hs-carousel-body absolute top-0 bottom-0 start-0 flex flex-nowrap transition-transform duration-700 opacity-0">
      <div class="hs-carousel-slide">
        1
      </div>
      <div class="hs-carousel-slide">
        2
      </div>
      <div class="hs-carousel-slide">
        3
      </div>
    </div>
  </div>

  <button type="button" class="hs-carousel-prev">
    <span aria-hidden="true">«</span>
    <span class="sr-only">Previous</span>
  </button>
  <button type="button" class="hs-carousel-next">
    <span class="sr-only">Next</span>
    <span aria-hidden="true">»</span>
  </button>
</div>
```

**Structure Requirements:**
- `data-hs-carousel`: Required on the container element, contains configuration options
- `hs-carousel`: Required wrapper for slides collection, must contain `overflow-hidden` class
- `hs-carousel-body`: Required slides container
- `hs-carousel-slide`: Required for each slide element
- `hs-carousel-prev`: Optional previous slide button
- `hs-carousel-next`: Optional next slide button
- `hs-carousel-pagination`: Optional pagination container

**Initial State:**
- Carousel body typically starts with `opacity-0` which is removed after loading
- First slide is active by default (index 0)

## Configuration Options

### Data Options

Data options are specified in the `data-hs-carousel` attribute as a JSON object.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `currentIndex` | number | `0` | Specifies the index of the current slide initially (from 0 to slides quantity). |
| `loadingClasses` | string | `"opacity-0"` | Specifies which classes should be removed after the carousel is loaded. CSS classes should be separated with space. |
| `dotsItemClasses` | string | - | Specifies which classes will be added to the point elements (which are generated automatically). CSS classes should be separated with space. |
| `isAutoHeight` | boolean | `false` | Sets the height of the carousel to the height of the current slide. |
| `isAutoPlay` | boolean | `false` | Enables autoplay. Slides advance automatically at the interval specified by `speed`. |
| `isInfiniteLoop` | boolean | `true` | Enables infinite loop. When reaching the last slide, it wraps to the first slide. |
| `isCentered` | boolean | `false` | Enables centered mode. This mode adds space on the sides to center the slides. |
| `isSnap` | boolean | `false` | Enables a mode in which you can scroll the contents of the slider using the scroll along the x-axis, while the active slider is centered relative to the slider. |
| `isScrollBlocked` | boolean | `false` | Prevents the user from scrolling the slider. Useful when you need to prevent scrolling while the carousel is active. |
| `isDraggable` | boolean | `false` | Adds the ability to change slides using dragging. Requires adding `hs-carousel:dragging:transition-none` class to the `hs-carousel-body`. Not working with `isSnap: true` option. |
| `isRTL` | boolean | `false` | Turns on RTL (right-to-left) mode. |
| `hasSnapSpacers` | boolean | `true` | Adds additional elements to create a constant centering effect. |
| `slidesQty` | object \| number | `1` | Allows to set the number of slides to display at a certain screen resolution if passed as an object (e.g., `{"xs": 1, "md": 2, "lg": 3}`). If passed as a number, the specified number of slides will be displayed for all screen resolutions. |
| `slideBy` | object \| number | `1` | Specifies how many slides to move when navigating through the carousel. Can be a number for all screen sizes or an object with breakpoint-specific values. |
| `speed` | number | `4000` | Autoplay animation speed in milliseconds. Available if `isAutoplay: true`. |
| `updateDelay` | number | `0` | Allows you to delay the update function when resizing a window. Suitable for a slider with images, for more accurate calculation of the size of the images. |
| `mode` | `"default"` \| `"snap"` \| `"bounded"` | `"default"` | Defines the carousel's behavior mode. `default` for standard sliding, `snap` for scroll-based navigation with snap points, and `bounded` for a fixed-width container with responsive slides. |
| `boundedOptions` | object | `null` | Configuration options for the bounded mode carousel. Includes `maxWidth` to set the maximum width of the carousel container, `slidesGap` to define spacing between slides, and `spacersWidth` to control the width of side spacers. When `spacersWidth` is set to `"auto"`, it automatically calculates equal spacing on both sides. |

**Example:**
```html
<div data-hs-carousel='{
  "isAutoPlay": true,
  "speed": 3000,
  "slidesQty": {"xs": 1, "md": 2, "lg": 3},
  "isInfiniteLoop": true
}'>
  <!-- Carousel content -->
</div>
```

### Required CSS Classes

These classes define the structure and must be present for the carousel to function.

| Class | Required On | Purpose |
| --- | --- | --- |
| `hs-carousel` | Wrapper container | A wrapper for a collection of slides. Must contain the `overflow-hidden` class for correct functionality. |
| `hs-carousel-body` | Slides container | Container for all slide elements. |
| `hs-carousel-slide` | Each slide element | Identifies an individual slide. |

### Optional CSS Classes

| Class | Required On | Purpose |
| --- | --- | --- |
| `hs-carousel-prev` | Previous button | Previous slide navigation button. |
| `hs-carousel-next` | Next button | Next slide navigation button. |
| `hs-carousel-pagination` | Pagination container | Pagination container. If it doesn't contain nested elements, dots are generated automatically. If it contains elements with `hs-carousel-pagination-item` class, they will act as dots. |

### Tailwind Modifiers

| Name | Description |
| --- | --- |
| `hs-carousel-active:*` | A modifier that allows you to set Tailwind classes when the active slide was shown. |
| `hs-carousel-disabled:*` | A modifier that allows you to set Tailwind classes for arrow buttons when they are `disabled`. |
| `hs-carousel:dragging:*` | A modifier that allows you to set Tailwind classes for `hs-carousel-body` when dragging. |

## JavaScript API

The `HSCarousel` object is available in the global `window` object after the plugin is loaded.

### Instance Methods

These methods are called on a carousel instance.

| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `goToPrev()` | None | `void` | Navigates to the previous slide. |
| `goToNext()` | None | `void` | Navigates to the next slide. |
| `goTo(index)` | `index`: `number` | `void` | Navigates to the slide at the specified index. Index starts from 0. |
| `recalculateWidth()` | None | `void` | Recalculates the width of the carousel. Useful after dynamically adding or removing slides, or when window is resized. |
| `destroy()` | None | `void` | Destroys the carousel instance, removes all generated markup, classes, and event listeners. Use when removing carousel from DOM. |

### Static Methods

These methods are called directly on the `HSCarousel` class.

| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `HSCarousel.getInstance(target, isInstance)` | `target`: `HTMLElement \| string` (CSS selector)<br>`isInstance`: `boolean` (optional) | `HSCarousel \| { id: string \| number, element: HSCarousel } \| null` | Returns the carousel instance associated with the target. If `isInstance` is `true`, returns collection item object `{ id, element }` where `element` is the `HSCarousel` instance. If `isInstance` is `false` or omitted, returns the `HSCarousel` instance directly. Returns `null` if carousel instance is not found. |

### Usage Examples

**Example 1: Using instance methods (public API)**
```javascript
// Create a new carousel instance
const carousel = new HSCarousel(document.querySelector('#hs-carousel'));
const goToSecondBtn = document.querySelector('#hs-go-to-second-btn');

// Navigate to slide by index
goToSecondBtn.addEventListener('click', () => {
  carousel.goTo(1); // Go to second slide (index 1)
});
```

**Example 2: Getting instance and using methods (recommended pattern)**
```javascript
// Get the carousel instance
const instance = HSCarousel.getInstance('#hs-carousel', true);

if (instance) {
  const { element } = instance; // element is the HSCarousel instance
  const goToSecondBtn = document.querySelector('#hs-go-to-second-btn');
  const prevBtn = document.querySelector('#hs-prev-btn');
  const nextBtn = document.querySelector('#hs-next-btn');

  // Navigate to specific slide
  goToSecondBtn.addEventListener('click', () => {
    element.goTo(1);
  });

  // Navigate to previous slide
  prevBtn.addEventListener('click', () => {
    element.goToPrev();
  });

  // Navigate to next slide
  nextBtn.addEventListener('click', () => {
    element.goToNext();
  });

  // Recalculate width after dynamic changes
  function updateCarousel() {
    element.recalculateWidth();
  }

  // Clean up when removing from DOM
  function removeCarousel() {
    element.destroy();
  }
}
```

**Example 3: Destroying carousel instance**
```javascript
const instance = HSCarousel.getInstance('#hs-carousel', true);

if (instance) {
  const { element } = instance;
  const removeBtn = document.querySelector('#hs-remove-btn');

  removeBtn.addEventListener('click', () => {
    // Clean up before removing from DOM
    element.destroy();
    // Now safe to remove the element
    document.querySelector('#hs-carousel').remove();
  });
}
```

## Events

Carousel instances emit events that can be listened to for slide lifecycle hooks.

| Event Name | When Fired | Callback Parameter | Description |
| --- | --- | --- | --- |
| `on:update` | After the active slide changes | `number` (zero-based index of the current slide) | Fires after navigating to the next or previous slide, or after `goTo()` is called. |

### Event Usage Example

```javascript
// Get carousel instance
const instance = HSCarousel.getInstance('#hs-carousel', true);

if (instance) {
  const { element } = instance;

  // Listen for slide changes
  element.on('update', (currentIndex) => {
    console.log('Active slide:', currentIndex);
    // Update external indicators, counters, etc.
  });
}
```

## Common Patterns

### Pattern 1: Responsive Carousel

Display different number of slides at different breakpoints.

```html
<div data-hs-carousel='{
  "slidesQty": {"xs": 1, "sm": 2, "md": 3, "lg": 4}
}'>
  <!-- Carousel content -->
</div>
```

### Pattern 2: Autoplay Carousel

Automatically advance slides at regular intervals.

```html
<div data-hs-carousel='{
  "isAutoPlay": true,
  "speed": 5000,
  "isInfiniteLoop": true
}'>
  <!-- Carousel content -->
</div>
```

### Pattern 3: Programmatic Control

Control carousel from external buttons.

```html
<div id="hs-carousel-first" data-hs-carousel>
  <!-- Carousel content -->
</div>

<button id="hs-go-to-first">Go to First</button>
<button id="hs-go-to-last">Go to Last</button>

<script>
  const instance = HSCarousel.getInstance('#hs-carousel-first', true);
  
  if (instance) {
    const { element } = instance;

    document.querySelector('#hs-go-to-first').addEventListener('click', () => {
      element.goTo(0);
    });

    document.querySelector('#hs-go-to-last').addEventListener('click', () => {
      // Assuming 5 slides (indices 0-4)
      element.goTo(4);
    });
  }
</script>
```

## License

Copyright (c) 2024 Htmlstream.

Licensed under the [MIT License](https://opensource.org/licenses/MIT).
