Skip to content

Instantly share code, notes, and snippets.

@gribnoysup
Last active January 11, 2017 19:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gribnoysup/f7216bc7f6ddde243de575a6d408af9c to your computer and use it in GitHub Desktop.
Save gribnoysup/f7216bc7f6ddde243de575a6d408af9c to your computer and use it in GitHub Desktop.
import React from 'react';
import { Motion, spring, presets } from 'react-motion';
import Swipeable from './Swipeable';
import styled from 'styled-components'
const ReseteUl = styled.ul`
margin: 0;
padding: 0;
list-style: none;
`
const CarouselFrameContainer = styled(ReseteUl)`
position: relative;
padding-bottom: 100%;
transform: translate3d(${(props) => 101 * props.offset}%, 0, 0);
`
const CarouselFrame = styled.li`
width: 100%;
display: block;
position: absolute;
z-index: -1;
transform: translate3d(${(props) => 101 * props.offset}%, 0, 0);
&:first-child {
z-index: 1;
}
`
const CarouselContainer = styled.div`
position: relative;
overflow: hidden;
width: 100%;
`
const FlexDiv = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
flex-direction: column;
margin-top: 10px;
`
const SlideChangeButton = styled.div`
position: absolute;
top: 0;
bottom: 0;
width: 20%;
cursor: pointer;
${(props) => props.buttonPosition}: 0;
`
const ListItem = styled.span`
display: block;
width: 10px;
height: 10px;
border-radius: 50%;
background: #ccc;
cursor: pointer;
${(props) => props.indicator ? `
background: #000;
cursor: default;
` : ''}
`
const Ul = styled(ReseteUl)`
display: flex;
flex-shrink: 0;
flex-grow: 0;
flex-direction: row;
justify-content: center;
`
const Li = styled.li`
margin: 0 5px;
width: 20px;
height: 20px;
display: block;
padding: 5px;
flex-shrink: 0;
flex-grow: 0;
cursor: pointer;
`
const getCurrentIndex = (total, offset) => (total - Math.round(offset % total)) % total;
const getFrameOffset = (index, offset, total) => index + (-total * Math.ceil(((offset - 1) + index) / total));
export default class StyledCarousel extends React.PureComponent {
props: {
children: Object,
};
constructor(props) {
super(props);
this.state = {
offset: 0,
peekOffset: 0,
swipeOffset: 0,
};
}
peekNext = () => {
this.setState({peekOffset: -0.1});
}
peekPrevious = () => {
this.setState({peekOffset: 0.1});
}
unpeek = () => {
this.setState({peekOffset: 0});
}
handleNext = () => {
const { offset } = this.state;
const nextOffset = Math.ceil(offset) - 1;
this.setState({offset: nextOffset});
}
handlePrevious = () => {
const { offset } = this.state;
const nextOffset = Math.floor(offset) + 1;
this.setState({offset: nextOffset});
}
getOffset = () => {
const { offset: baseOffset, swipeOffset, peekOffset } = this.state;
return baseOffset + swipeOffset + peekOffset;
}
handleDotClick(index, event) {
const offset = this.getOffset();
const total = this.props.children.length;
const currentIndex = getCurrentIndex(total, offset);
if (index === currentIndex) {
return;
}
event.preventDefault();
event.stopPropagation();
if (index > currentIndex) {
this.setState({swipeOffset: 0, offset: offset + (currentIndex - index)});
} else {
this.setState({swipeOffset: 0, offset: offset - (index - index)});
}
const diff = currentIndex - index;
this.setState({swipeOffset: 0, offset: offset + diff});
}
handleSwipingLeft = (event, x) => {
this.setState({
swipeOffset: -1 * Math.min(x / this.containerNode.offsetWidth, 1),
});
}
handleSwipedLeft = (event, x, isFlick) => {
if (!isFlick) {
this.setState({
offset: Math.round(this.state.swipeOffset + this.state.offset),
});
} else {
this.handleNext();
}
}
handleSwipingRight = (event, x) => {
this.setState({
swipeOffset: Math.min(x / this.containerNode.offsetWidth, 1),
});
}
handleSwipedRight = (event, x, isFlick) => {
if (!isFlick) {
this.setState({
offset: Math.round(this.state.swipeOffset + this.state.offset),
});
} else {
this.handlePrevious();
}
}
handleSwiped = () => {
this.setState({swipeOffset: 0});
}
render() {
const { children } = this.props;
const total = children.length;
const offset = this.getOffset();
const currentIndex = getCurrentIndex(total, offset);
if (React.Children.count(children) === 1) {
return children;
}
return (
<CarouselContainer innerRef={(ref) => this.containerNode = ref}>
<Swipeable
onSwiped={this.handleSwiped}
onSwipingLeft={this.handleSwipingLeft}
onSwipingRight={this.handleSwipingRight}
onSwipedLeft={this.handleSwipedLeft}
onSwipedRight={this.handleSwipedRight}
>
<Motion style={{x: spring(offset, presets.noWobble)}}>
{({x: interpolatedOffset}) => (
<CarouselFrameContainer offset={interpolatedOffset}>
{children.map((child, index) => (
<CarouselFrame
key={index}
offset={getFrameOffset(index, interpolatedOffset, total)}
>
{child}
</CarouselFrame>
))}
</CarouselFrameContainer>
)}
</Motion>
<FlexDiv>
<SlideChangeButton
buttonPosition="left"
onClick={this.handlePrevious}
onMouseEnter={this.peekPrevious}
onMouseLeave={this.unpeek}
/>
<SlideChangeButton
buttonPosition="right"
onClick={this.handleNext}
onMouseEnter={this.peekNext}
onMouseLeave={this.unpeek}
/>
<Ul>
{children.map((child, index) => (
<Li
key={index}
onClick={this.handleDotClick.bind(this, index)}
>
<ListItem indicator={index === currentIndex} />
</Li>
))}
</Ul>
</FlexDiv>
</Swipeable>
</CarouselContainer>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment