Skip to content

A React carousel component that aims to be as flexible as possible

License

Notifications You must be signed in to change notification settings

moxystudio/react-carousel

Repository files navigation

React Carousel

A React carousel component that aims to be as flexible as possible.

Installation

npm i -S @moxy/react-carousel

or

yarn add @moxy/react-carousel

This library is written in modern JavaScript and is published in both CommonJS and ES module transpiled variants. If you target older browsers please make sure to transpile accordingly.

Motivation

After analysing and testing a number of different carousel solutions for react, MOXY determined that none of the existing solutions would be a good candidate the numerous projects that we develop as an agency. As such, we set out to build an extensible, easy to use, small carousel component.

This project is still a work in progress. You can see what is currently missing in Future Work.

How does @moxy/react-carousel differ from other packages?

  • Uses the minimum amount of dependencies.
    • The only direct dependency is lodash.debounce.
  • Does not highjack native scrolling functionality unless desired.
  • Respects native browser scrolling behaviour (desktop and mobile), and only aims to augment its capabilities.
    • Once Safari fixes its issues with scroll-snap and programmatic manipulation of scrollLeft / scrollTop, js-based snapping behaviour can be dropped in favor of scroll-snap.
  • Manipulates carousel state by changing scrollLeft property.
  • Does not use inline styles for controlling behaviour (except offset); all behaviour is controlled via className exchange and CSS rules. All classNames can be customized.
  • Developer / designer oriented: @moxy/react-carousel does not assume or enforce a particular style of carousel, and provides a lot of options for customizing its behaviour.
  • Provides callbacks before and after current slide changes

Usage

@moxy/react-carousel does not make any assumptions on what elements your slides represent. They can be a <div>, an <img>, or any other element. They can also be react components, but you must be aware that the key, onMouseUp, onDragStart, and onDrag will be overriden. className prop will be appended to @moxy/react-carousel's classNames.

Basic usage

import Carousel from '@moxy/react-carousel';

<Carousel>
    <div>Slide 1</div>
    <div>Slide 2</div>
    <div className="custom">Slide 3</div>
    <div>Slide 4</div>
</Carousel>

Custom arrows rendering

import Carousel from '@moxy/react-carousel';

<Carousel
    arrows
    renderArrows={ ({ previous, next }) => (
        <>
            <span onClick={ previous } />
            <span onClick={ next }>
        </>
    )}>
    <div>Slide 1</div>
    <div>Slide 2</div>
    <div>Slide 3</div>
    <div>Slide 4</div>
</Carousel>

There is a lot more than you can do, check out the demo, where you can easily explore different prop values and prop combinations.

HTML Structure

<Carousel arrows dots>
    <div>Slide 1</div>
    <img src={ slide2 } alt="Slide 2" />
    <div className="custom">Slide 3</div>
</Carousel>

will render:

<div className="rc-wrapper">
    <div className="rc">
        <div className="rc-slider">
            <div className="rc-slide -current">Slide </div>
            <img className="rc-slide" src={ slide2 } alt="Slide 2" />
            <div class="rc-slide custom">Slide 3<div>
        </div>
    </div>

    <button className="rc-arrow -left">previous</button>
    <button className="rc-arrow -right">next</button>

    <div className="rc-dots">
        <button className="rc-dot -current" />
        <button className="rc-dot" />
        <button className="rc-dot" />
    </div>
</div>

CSS

There isn't one definitive implementation suitable for @moxy/react-carousel.

A default stylesheet is shipped with the package, and can be imported with:

import `@moxy/react-carousel/dist/styles.css`

You can use this stylesheet as is and build on top of it, or use it as inspiration for your own styles. The default styles sets .rc-slide to display: inline-block, and, .rc-slider to white-space: nowrap so that slides are positioned horizontally. You could achieve the same effects with float, flexbox, etc. Because the position for the current slide is calculated based on the distance from the element to its parent, it also allows for different size slides

Props

Prop Type Required Default
children any yes undefined
arrows boolean no false
dots boolean no false
disableNativeScroll boolean no false
draggable boolean no false
infinite boolean no false
keyboardControl boolean no false
resetCurrentOnResize boolean no true
swapOnDragMoveEnd boolean no true
autoplayIntervalMs number no 0
autoplayDirection string no 'ltr'
offset number or function no 0
slideSnapEasing string or function no ease-in-out
slideSnapDuration number no 150
slideTransitionEasing string or function no ease-in-out
slideTransitionDuration number no 300
touchSwipeVelocityThreshold number no 0.3
touchCrossAxisScrollThreshold number no 0.45
current number no undefined
beforeChange function no () => {}
afterChange function no () => {}
renderArrows function no undefined
renderDots function no undefined
wrapperClassName string no undefined
carouselClassName string no undefined
sliderClassName string no undefined
arrowClassName string no undefined
dotContainerClassName string no undefined
dotClassName string no undefined
modifierDraggableClassName string no undefined
modifierDraggingClassName string no undefined
modifierCurrentClassName string no undefined
modifierLeftClassName string no undefined
modifierRightClassName string no undefined

API

children

Type: any

Default: undefined

@moxy/react-carousel assumes that any direct descendants of the <Carousel> component are the slides that you wish to render. It makes no assumptions on what those elements should be, or their dimensions.

arrows

Type: boolean

Default: false

Render previous / next buttons to navigate carousel. Default arrows are rendered as <button> elements. arrowClassName and modifierLeftClassName applied to the 'Previous' button, and arrowClassName and modifierRightClassName are applied to 'Next' button.

dots

Type: boolean

Default: false

Render one button per slide to navigate / indicate carousel state. Default dots are rendered as <button> elements. dotClassName is applied, as well as modifierCurrentClassName to the button that matches the current slide.

disableNativeScroll

Type: boolean

Default: false

It true, modifierDisableScrollClassName is applied to .rc element

draggable

Type: boolean

Default: false

If true, enables grabbing and dragging the slider with the mouse to change its scroll value.

NOTE: When false, it only prevents dragging with mouse, it does not prevent dragging with touch events in devices that support it

infinite

Type: boolean

Default: false

If true, the carousel will navigate to the first / last slide if attempting to navigate out of bounds

keyboardControl

Type: boolean

Default: false

If true, enables focusing the .rc element. While focused, you are able to switch slides using the left and right arrow keys.

resetCurrentOnResize

Type: boolean

Default: true

If true, carousel will re-render with the current state after the window has been resized. This operation is debounced by 200ms.

swapOnDragMoveEnd

Type: boolean

Default: true

If true, the carousel will change the current slide, to an adjacent one, based on the direction of the drag movement. Although, if the drag is enough to change the current slide by itself, it will respect that behaviour.

Notes: Only works if the draggable prop is set as true.

autoplayIntervalMs

Type: number

Default: 0

The number of milliseconds to wait between automatic slide advancement. A value of 0 turns off autoplay.

autoplayDirection

Type: oneOf(['ltr', 'rtl'])

Default: 'ltr'

The direction that the carousel should change the slides when autoplayIntervalMs > 0. Can be one of:

  • 'ltr': causes carousel to change slides from left to right
  • 'rtl': causes carousel to change slides from right to left

offset

Type: number or function

Default: 0

The horizontal padding given to .rc-slider.

If it is a number, is treated as a value in pixels.

If it is a function, its signature is:

({ animating: boolean, dragging: boolean, current: number }) => number

It is expected that the returned number represents a value in pixels.

slideSnapEasing

Type: function or oneOf(['linear', 'ease-in', 'ease-out', 'ease-in-out'])

Default: ease-in-out

Easing function applied to snapping animation.

You may instead provide your own easing function if the supplied ones are not suitable. slideSnapEasing also accepts a function, (value: number) => number. Return value must be in the [0.0, 1.0] range.

slideSnapDuration

Type: number

Default: 150

Number of milliseconds that the snapping animation takes to complete.

slideTransitionEasing

Type: function or oneOf(['linear', 'ease-in', 'ease-out', 'ease-in-out'])

Default: ease-in-out

Easing function applied to transition animation.

You may instead provide your own easing function if the supplied ones are not suitable. slideTransitionEasing also accepts a function, (value: number) => number. Return value must be in the [0.0, 1.0] range.

slideTransitionDuration

Type: number

Default: 300

Number of milliseconds that the transition animation takes to complete.

touchSwipeVelocityThreshold

Type: number

Default: 0.3

Note: Only applicable when disableNativeScroll = true.

The velocity in pixels per frame where a swipe causes the slide to change

touchCrossAxisScrollThreshold

Type: number

Default: 0.45

Note: Only applicable when disableNativeScroll = true.

The velocity in pixels per frame in the cross axis (currently just y axis) below where a diagonal swipe on the carousel will prevent vertical scrolling

current

Type: number

Default: undefined

If set, makes the Carousel as a controlled component, allowing you to control the current slide from outside the scope of the Carousel. This will not disable any internal state management, and beforeChange / afterChange props will still be called, so you will be responsible for syncing your external state with the internal one of the Carousel.

beforeChange

Type: function

Default: () => {}

Callback function that is called before a slide change happens.

Its signature is ({ current: number, next: number, source: 'user' | 'autoplay' }) => void

afterChange

Type: function

Default: () => {}

Callback function that is called after a slide change happens. Function is only called after transitions have ended and internal state is updated.

Its signature is ({ previous: number, current: number, source: 'user' | 'autoplay' }) => void

renderArrows

Type: (props: object) => React.Node

Default: undefined

If a function is provided to renderArrows prop, the default arrows will not be rendered, and you may use this render prop to render your own arrows.

props has the following shape:

{
    next: () => void,      // call this to change to the next (right) slide
    previous: () => void,  // call this to change to the previous (left) slide
    current: number,       // index of the current slide
    animating: boolean,    // boolean indicating that a transition or snap is happening
    dragging: boolean,     // boolean indicating that the carousel is being dragged by a mouse
    slideCount: number,    // number indicating how many slides are rendered by the carousel
}

renderDots

Type: (props: object) => React.Node

Default: undefined

If a function is provided to renderDots prop, the default dots will not be rendered, and you may use this render prop to render your own dots.

props has the following shape:

{
    setCurrent: (i: number) => void,  // call this to change to slide with index i
    current: number,                  // index of the current slide
    animating: boolean,               // boolean indicating that a transition or snap is happening
    dragging: boolean,                // boolean indicating that the carousel is being dragged by a mouse
}

Appending element classNames

You may append the default classNames rendered by <Carousel> with your own classNames. Each type of element has its own className, and some of them also have modifier classNames applied.

wrapperClassName

Type: string

Default: undefined

Applied to top level element rendered by <Carousel>, .rc-wrapper. It will have .rc, .rc-arrow and .rc-dots elements as its direct descendants.

carouselClassName

Type: string

Default: undefined

Applied to scrollable element.

Expected to have at least overflow: x; -webkit-overflow-scrolling: touch applied.

sliderClassName

Type: string

Default: undefined

Applied to slides container element. This element can be larger than the full width of the viewport, and is scrolled by its direct parent, the .rc element

arrowClassName

Type: string

Default: undefined

Applied to "previous" and "next" <button> elements rendered by default when arrows is true and no custom renderArrows prop is provided.

dotContainerClassName

Type: string

Default: undefined

Applied to container <div> element that holds the dots rendered by default when dots is true and no custom renderDots prop is provided.

dotClassName

Type: string

Default: undefined

Applied to each individual <button> dot element rendered by default when dots is true and no custom renderDots prop is provided.

Appending modifier classNames

modifierDraggableClassName

Type: string

Default: undefined

Appended to .rc-slider.-draggable when draggable prop is true.

modifierDraggingClassName

Type: string

Default: undefined

Appended to .rc-slider.-draggable.-dragging when carousel is being dragged by mouse. Useful to change icons.

modifierCurrentClassName

Type: string

Default: undefined

Appended to .rc-slide.-current and .rc-dot.-current elements. Represents the currently selected slide / dot.

modifierLeftClassName

Type: string

Default: undefined

Appended to .rc-arrow.-left that switches to the previous (left) slide.

modifierRightClassName

Type: string

Default: undefined

Appended to .rc-arrow.-right that switches to the next (right) slide

Future work

  • Infinite mode
    • enable / disable
    • bounce
    • wrap around
  • Current slide placement options
    • left
    • center
    • right
  • Vertical carousel and vertical placement options
    • top
    • center
    • bottom