Last active
February 10, 2019 19:20
-
-
Save mrnkr/ff1119483a9a32481f4eb7588a3b24a4 to your computer and use it in GitHub Desktop.
React carousel implemented using css transformations and hammerjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; | |
import Hammer from 'hammerjs'; | |
import './Gallery.scss'; | |
interface Props<T> { | |
sensitivity?: number; | |
slides: T[]; | |
template: (slide: T) => JSX.Element; | |
} | |
interface State { | |
activeSlide: number; | |
translateValue: number; | |
} | |
export default class Gallery<T> extends Component<Props<T>, State> { | |
public state: State = { | |
activeSlide: 0, | |
translateValue: 0 | |
} | |
private sliderEl: React.RefObject<HTMLDivElement> = React.createRef(); | |
private sliderManager?: HammerManager; | |
public componentDidMount() { | |
const { sliderEl } = this; | |
if (!sliderEl.current) | |
return; | |
this.sliderManager = new Hammer.Manager(sliderEl.current); | |
this.sliderManager.add(new Hammer.Pan({ threshold: 0, pointers: 0 })); | |
this.sliderManager.on('pan', this.handlePanGesture); | |
// This makes sure the translateX value gets updated | |
// everytime the screen is resized. | |
// I THINK in this particular case it would be possible | |
// to use percentage instead of pixels for translating | |
// and avoiding the need for this code - I suck at math | |
// tho, cause I tried 😅 | |
window.addEventListener('resize', () => { | |
const { activeSlide } = this.state; | |
this.goTo(activeSlide); | |
}); | |
} | |
public render(): JSX.Element { | |
const { goTo } = this; | |
const { slides, template } = this.props; | |
const { activeSlide, translateValue } = this.state; | |
return ( | |
<div className="gallery"> | |
<div ref={this.sliderEl} className="my-carousel"> | |
<ul className="slider" style={{ | |
width: `${slides.length * 100}%`, | |
transform: `translateX(${translateValue}px)`, | |
transition: 'transform ease-out 0.45s' | |
}}> | |
{slides.map((slide, index) => ( | |
<li key={index} style={{ width: `${100 / slides.length}%` }}>{template(slide)}</li> | |
))} | |
</ul> | |
<div className="controls"> | |
<div className="left-arrow" onClick={() => goTo(activeSlide - 1)}> | |
<span className="icon is-large"> | |
<i className="ion-ios-arrow-back"></i> | |
</span> | |
</div> | |
<div className="right-arrow"> | |
<span className="icon is-large" onClick={() => goTo(activeSlide + 1)}> | |
<i className="ion-ios-arrow-forward"></i> | |
</span> | |
</div> | |
<div className="indicators"> | |
{slides.map((_, index) => ( | |
<div key={index} className={`indicator ${index === activeSlide ? 'active' : ''}`}></div> | |
))} | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
private goTo = (slide: number) => { | |
const { sliderEl } = this; | |
const { slides } = this.props; | |
const slideCount = slides.length; | |
if (!sliderEl.current) | |
return; | |
const slideWidth = sliderEl.current.clientWidth; | |
if (slide < 0) { | |
this.setState({ activeSlide: 0, translateValue: 0 }); | |
} else if (slide > slideCount - 1) { | |
this.setState({ activeSlide: slideCount - 1, translateValue: - slideWidth * (slideCount - 1) }); | |
} else { | |
this.setState({ activeSlide: slide, translateValue: - slideWidth * slide }); | |
} | |
} | |
private handlePanGesture = (e: HammerInput) => { | |
const { sliderEl } = this; | |
const { sensitivity = 100 } = this.props; | |
const { activeSlide } = this.state; | |
if (!sliderEl.current) | |
return; | |
const slideWidth = sliderEl.current.clientWidth; | |
this.setState({ translateValue: - slideWidth * activeSlide + e.deltaX }); | |
if (!e.isFinal) { | |
return; | |
} | |
if (Math.abs(e.deltaX) > sensitivity) { | |
this.goTo(e.deltaX > 0 ? activeSlide - 1 : activeSlide + 1); | |
} else { | |
this.goTo(activeSlide); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Found throwing errors when the ref is undefined to be a bad approach - changed it to just returning.