├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── examples
├── index.css
├── index.html
└── index.js
├── package.json
├── server.js
├── src
├── Arrows.js
├── Dots.js
├── Slider.js
├── Slides.js
├── index.js
└── util.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0,
3 | "plugins": [
4 | "react-transform"
5 | ],
6 | "extra": {
7 | "react-transform": [{
8 | "target": "react-transform-hmr",
9 | "imports": ["react"],
10 | "locals": ["module"]
11 | }, {
12 | "target": "react-transform-catch-errors",
13 | "imports": ["react", "redbox-react"]
14 | }]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ecmaFeatures": {
3 | "jsx": true,
4 | "modules": true
5 | },
6 | "env": {
7 | "browser": true,
8 | "node": true
9 | },
10 | "extends": "airbnb",
11 | "parser": "babel-eslint",
12 | "rules": {
13 | "quotes": [2, "single"],
14 | "strict": [2, "never"],
15 | "comma-dangle": [2, "never"],
16 | "no-undef": 2,
17 | "func-names": 0,
18 |
19 | "react/jsx-uses-react": 2,
20 | "react/jsx-uses-vars": 2,
21 | "react/react-in-jsx-scope": 2,
22 | "react/no-multi-comp": 0
23 | },
24 | "plugins": [
25 | "react"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .DS_Store
4 | lib
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | examples
3 | node_modules
4 | .babelrc
5 | .dev.babelrc
6 | .eslintrc
7 | .eslintignore
8 | index.html
9 | .gitignore
10 | server.js
11 | webpack.config.js
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Ramana Venkata
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # THIS PROJECT IS NOT MAINTAINED!
2 |
3 | React Flex Slick
4 | ================
5 |
6 | This is library aims to replace [react-slick][react-slick].
7 |
8 | Uses **flexbox** exclusively, so no support for older browsers :cry: :cry:
9 |
10 | On the bright side, it is extremly flexible. Currently it supports `>=react-0.14` including rc's
11 |
12 | Any questions?? Join here [](http://www.reactiflux.com)
13 |
14 | ## Examples
15 |
16 | To run the examples:
17 |
18 | 1. `git clone http://github.com/vramana/react-flex-slick`
19 | 2. `cd react-flex-slick && npm i`
20 | 3. `npm start`
21 |
22 | ## What works
23 |
24 | - Infinite and Non-infinite Mode
25 | - Pages
26 | - Horizontal and Vertical Sliding
27 | - Multiple Slides per page (slidesToScroll = slidesToShow)
28 | - Left and Right Arrows to go back and forth
29 | - Arrows have active and inactive classes.
30 | - Custom Arrows
31 | - **Touch Scrolling/Mouse Dragging**
32 | - Edge Friction
33 |
34 | ## TODO
35 |
36 | - Feature Parity with [Slick Carousel][slick]
37 | - Examples
38 | - Documentation
39 | - Tests
40 |
41 | ## Comparision with Slick
42 |
43 | ### Settings
44 |
45 | - cssEase - renamed to transitionTimingFn
46 | - speed - renamed to transitionSpeed
47 | - easing - Not supported: Minimum browser support ensure CSS Transistions are present.
48 | - arrows - Alternate way: Instead of arrows pass empty `
` to the Slider
49 | - appendArrows, appendDots - Not supported due the architecture of component
50 | - mobileFirst - Alternate way: Control the size of Slider just usign css on the parent class
51 | - prevArrow, nextArrow - Alternate way: Just put a ref on prevArrow and nextArrow
52 | - infinite - works as expected
53 | - initialSlide - works as expected
54 | - rows - Alternate way: Pass pages of slides instead of slides
55 | - slidesPerRow - Alternate Way: Pass pages of slides instead of slides
56 | - vertical - works as expected
57 | - swipe, verticalSwiping - merged into swipe - works as expected
58 | - touchMove - works as expected
59 | - draggable - works as expected
60 | - edgeFriction - works as expected
61 | - touchThreshold - fraction by which you should slide for slide to change - lies between 0 and 1
62 | - autoPlay, autoPlaySpeed - works as expected. *Bonus*: If the mounted component with `autoPlay` recieve
63 | `autoPlay={false}` then it will pause the slider.
64 | - zIndex - Not supported: No support for IE 9 itself requires atleast IE11
65 | - centerPadding - Alternate way: Use css on pages to manipulate this
66 | - customPaging - Alternate way: Just pass your slides in pages whatever way you want
67 | - waitForAnimate - This is the default behaviour of current slider. Otherwise behaviour
68 | is not implemented yet.
69 | - useCSS - Not supported: We don't have to fallback for jQuery animations for ancient browsers
70 | because we dont support them in first place.
71 | - dots, dotClass - works almost as expected. Instead of dotClass on slider give it as
72 | className in the `Dots` component
73 |
74 | **Remaining**: slidesToShow, slidesToScroll, accessibility, rtl, pauseOnHover,
75 | pauseOnDotsHover, responsive, swipeToSlide, slide, variableWidth, centerMode,
76 | fade, lazyLoad, respondTo
77 |
78 | **Progress** - Total: 43 Current: 29
79 |
80 | ### Events
81 |
82 | - beforeChange - `beforeChange(prevSlide, currSlide)` but doesn't have the event handler
83 | - afterChange - `afterChange(prevSlide, currSlide)` but doesn't have the event handler
84 | - swipeEvent - `swipeEvent(direction)` but doesn't have the event handler
85 | - edgeEvent - `edgeEvent(direction)` but doesn't have the event handler
86 | - init, reInit - Alternate way: Can be invoke in parent components lifecycle methods.
87 | - destroy - Alternate way: Can be invoke in parent components lifecycle methods when the slider is taken out of the render tree.
88 | - setPosition - Not positions are calculated from the DOM. So, doesn't make sense.
89 | - breakpoint - Not implemented yet
90 |
91 | **Progress** - Total: 9 Current: 8
92 |
93 | ### Methods
94 |
95 | No slick method will be supported because they encourage anti-patterns in react i.e, changing
96 | the state of child component via a parent component directly or indirectly using setState.
97 | You can add all this by passing props to the Slider component. Detailed examples will be written
98 | showing how.
99 |
100 | ## Inspiration
101 |
102 | - [Slick Carousel][slick]
103 | - [React Slick][react-slick]
104 | - [React SoundPlayer][react-soundplayer]
105 | - [Smart and Dumb Componenst][smart-dumb]
106 |
107 | ## License
108 |
109 | MIT
110 |
111 | [react-slick]: https://github.com/akiran/react-slick
112 | [react-soundplayer]: https://github.com/soundblogs/react-soundplayer
113 | [smart-dumb]: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
114 | [slick]: https://github.com/kenwheeler/slick
115 |
--------------------------------------------------------------------------------
/examples/index.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | .banner {
7 | height: 5rem;
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | font-size: 2rem;
12 | background-color: #1282DE;
13 | color: white;
14 | }
15 |
16 | h2 {
17 | color: #333;
18 | }
19 |
20 |
21 | .non-infinite-left--active {
22 | width: 0;
23 | height: 0;
24 | border-bottom: solid 30px transparent;
25 | border-top: solid 30px transparent;
26 | border-right: solid 40px #795548;
27 | }
28 |
29 | .non-infinite-left--inactive {
30 | width: 0;
31 | height: 0;
32 | border-bottom: solid 30px transparent;
33 | border-top: solid 30px transparent;
34 | border-right: solid 40px rgba(121, 85, 72, 0.51);
35 | }
36 |
37 | .non-infinite-right--active {
38 | width: 0;
39 | height: 0;
40 | border-bottom: solid 30px transparent;
41 | border-top: solid 30px transparent;
42 | border-left: solid 40px #795548;
43 | }
44 |
45 | .non-infinite-right--inactive {
46 | width: 0;
47 | height: 0;
48 | border-bottom: solid 30px transparent;
49 | border-top: solid 30px transparent;
50 | border-left: solid 40px rgba(121, 85, 72, 0.51);
51 | }
52 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
React Flex Slick
4 |
5 |
6 |
7 |
8 | React Flex Slick
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Slider, Slides, PrevArrow, NextArrow, Dots } from '../src';
4 |
5 | import './index.css';
6 |
7 | class App extends Component {
8 |
9 | render() {
10 | return (
11 |
12 |
13 |
Non Infinite
14 |
15 |
16 |
17 |
Infinite
18 |
19 |
20 |
21 |
Custom arrows
22 |
23 |
24 |
25 |
Control Play (will autoPlay after 5s)
26 |
27 |
28 |
29 |
Set slide to show (will show the 6th slide after 5s)
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
37 | const slideStyle = {
38 | width: 540,
39 | height: 125,
40 | backgroundColor: 'slateblue',
41 | color: 'white',
42 | display: 'flex',
43 | justifyContent: 'center',
44 | alignItems: 'center'
45 | };
46 |
47 | class NonInfinite extends Component {
48 |
49 | render() {
50 | return (
51 |
52 |
54 |
55 | 1
56 | 2
57 | 3
58 | 4
59 | 5
60 | 6
61 |
62 |
64 |
65 |
66 | );
67 | }
68 | }
69 |
70 | class Infinite extends Component {
71 |
72 | render() {
73 | return (
74 |
75 |
76 |
77 | 1
78 | 2
79 | 3
80 | 4
81 | 5
82 | 6
83 |
84 |
85 |
86 |
87 | );
88 | }
89 | }
90 |
91 | class CustomArrows extends Component {
92 |
93 | render() {
94 | return (
95 |
96 |
97 |
98 | 1
99 | 2
100 | 3
101 | 4
102 | 5
103 | 6
104 |
105 |
106 |
107 | );
108 | }
109 | }
110 |
111 | class ControlPlay extends Component {
112 |
113 | constructor(props) {
114 | super(props);
115 | this.state = { playing: false };
116 | }
117 |
118 | componentDidMount() {
119 | this.timeout = setTimeout(() => {
120 | this.setState({ playing: true });
121 | }, 5000);
122 | }
123 |
124 | componentWillUnmount() {
125 | clearTimeout(this.timeout);
126 | }
127 |
128 | render() {
129 | return (
130 |
131 |
132 |
133 | 1
134 | 2
135 | 3
136 | 4
137 | 5
138 | 6
139 |
140 |
141 |
142 | );
143 | }
144 | }
145 |
146 | class SetSlide extends Component {
147 |
148 | constructor(props) {
149 | super(props);
150 | this.state = { currentSlide: 0 };
151 | }
152 |
153 | componentDidMount() {
154 | this.timeout = setTimeout(() => {
155 | this.setState({ currentSlide: 5 });
156 | }, 5000);
157 | }
158 |
159 | componentWillUnmount() {
160 | clearTimeout(this.timeout);
161 | }
162 |
163 | render() {
164 | return (
165 |
166 |
167 |
168 | 1
169 | 2
170 | 3
171 | 4
172 | 5
173 | 6
174 |
175 |
176 |
177 | );
178 | }
179 | }
180 |
181 | export default App;
182 |
183 | ReactDOM.render(
, document.getElementById('root'));
184 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-flex-slick2",
3 | "version": "0.5.0",
4 | "description": "Slick carousel using Flexbox",
5 | "main": "./lib/index.js",
6 | "scripts": {
7 | "start": "node server.js",
8 | "lint": "eslint .",
9 | "prepublish": "rm .babelrc && babel src --out-dir lib --stage 0 -L all && git checkout -- ."
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/vramana/react-flex-slick.git"
14 | },
15 | "keywords": [
16 | "react",
17 | "reactjs",
18 | "component",
19 | "slick",
20 | "carousel",
21 | "react-slick",
22 | "react-flex-slick",
23 | "react-component"
24 | ],
25 | "author": "Ramana Venkata
(http://github.com/vramana)",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "http://github.com/vramana/react-flex-slick/issues"
29 | },
30 | "homepage": "http://github.com/vramana/react-flex-slick",
31 | "devDependencies": {
32 | "babel": "^5.8.23",
33 | "babel-core": "^5.8.25",
34 | "babel-eslint": "^4.1.3",
35 | "babel-loader": "^5.3.2",
36 | "babel-plugin-react-transform": "^1.0.5",
37 | "css-loader": "^0.18.0",
38 | "eslint": "^1.5.1",
39 | "eslint-config-airbnb": "0.0.8",
40 | "eslint-plugin-react": "^3.4.2",
41 | "express": "^4.13.3",
42 | "react": "^0.14.0-rc1",
43 | "react-dom": "^0.14.0-rc1",
44 | "react-transform-catch-errors": "^1.0.0",
45 | "react-transform-hmr": "^1.0.0",
46 | "redbox-react": "^1.1.0",
47 | "style-loader": "^0.12.4",
48 | "webpack": "^1.12.2",
49 | "webpack-dev-middleware": "^1.2.0",
50 | "webpack-hot-middleware": "^2.2.0"
51 | },
52 | "peerDependencies": {
53 | "react": ">=0.14 || ^0.14.0-alpha"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const webpack = require('webpack');
4 | const config = require('./webpack.config');
5 |
6 | const app = express();
7 | const compiler = webpack(config);
8 |
9 | app.use(require('webpack-dev-middleware')(compiler, {
10 | stats: {
11 | colors: true
12 | },
13 | publicPath: config.output.publicPath
14 | }));
15 |
16 | app.use(require('webpack-hot-middleware')(compiler));
17 |
18 | app.get('*', function(req, res) {
19 | res.sendFile(path.join(__dirname, 'examples/index.html'));
20 | });
21 |
22 | app.listen(3000, 'localhost');
23 |
--------------------------------------------------------------------------------
/src/Arrows.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | class PrevArrow extends Component {
4 | static propTypes = {
5 | handleClick: PropTypes.func,
6 | currentSlide: PropTypes.number,
7 | activeClassName: PropTypes.string,
8 | inactiveClassName: PropTypes.string,
9 | infinite: PropTypes.bool
10 | }
11 |
12 | static defaultProps = {
13 | activeClassName: '',
14 | inactiveClassName: ''
15 | }
16 |
17 | render() {
18 | const { activeClassName, inactiveClassName, currentSlide, infinite } = this.props;
19 |
20 | const className = currentSlide === 0 && infinite === false ? inactiveClassName : activeClassName;
21 | const style = className !== '' ? null : {
22 | width: 0,
23 | height: 0,
24 | borderBottom: 'solid 30px transparent',
25 | borderTop: 'solid 30px transparent',
26 | borderRight: 'solid 40px #795548'
27 | };
28 |
29 | return (
30 |
31 |
32 | );
33 | }
34 | }
35 |
36 | class NextArrow extends Component {
37 | static propTypes = {
38 | handleClick: PropTypes.func,
39 | currentSlide: PropTypes.number,
40 | activeClassName: PropTypes.string,
41 | inactiveClassName: PropTypes.string,
42 | infinite: PropTypes.bool,
43 | slideCount: PropTypes.number
44 | }
45 |
46 | static defaultProps = {
47 | activeClassName: '',
48 | inactiveClassName: ''
49 | }
50 |
51 | render() {
52 | const { activeClassName, inactiveClassName, currentSlide } = this.props;
53 | const { infinite, slideCount } = this.props;
54 |
55 | const className = (currentSlide + 1) === slideCount && infinite === false ? inactiveClassName : activeClassName;
56 | const style = className !== '' ? null : {
57 | width: 0,
58 | height: 0,
59 | borderBottom: 'solid 30px transparent',
60 | borderTop: 'solid 30px transparent',
61 | borderLeft: 'solid 40px #795548'
62 | };
63 |
64 | return (
65 |
66 | );
67 | }
68 | }
69 |
70 | export { PrevArrow, NextArrow };
71 |
--------------------------------------------------------------------------------
/src/Dots.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | const range = n => [...Array(n)].map((_, i) => i);
4 |
5 | class Dots extends Component {
6 | static propTypes = {
7 | className: PropTypes.string,
8 | currentSlide: PropTypes.number,
9 | slideCount: PropTypes.number,
10 | handleSlideShift: PropTypes.func
11 | }
12 |
13 | render() {
14 | const { className, slideCount, currentSlide } = this.props;
15 | const dotStyle = { display: 'flex', justifyContent: 'center'};
16 |
17 | const dots = range(slideCount).map((x, i) => {
18 | const isActive = i === currentSlide;
19 | const style = {
20 | width: 8,
21 | height: 8,
22 | borderRadius: 8,
23 | backgroundColor: 'black',
24 | opacity: isActive ? 1 : 0.2,
25 | margin: 3
26 | };
27 |
28 | return (
29 |
30 | );
31 | });
32 |
33 | return (
34 |
35 | {dots}
36 |
37 | );
38 | }
39 | }
40 |
41 | export default Dots;
42 |
--------------------------------------------------------------------------------
/src/Slider.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes, cloneElement, Children } from 'react';
2 | import { swipeDirection, swipeDistance } from './util.js';
3 | import { SWIPE_UP, SWIPE_DOWN, SWIPE_RIGHT, SWIPE_LEFT } from './util.js';
4 |
5 | class Slider extends Component {
6 | static propTypes = {
7 | children: PropTypes.any,
8 | initialSlide: PropTypes.number,
9 | currentSlide: PropTypes.number,
10 | infinite: PropTypes.bool,
11 | vertical: PropTypes.bool,
12 | transitionSpeed: PropTypes.number,
13 | transitionTimingFn: PropTypes.string,
14 | beforeChange: PropTypes.func,
15 | afterChange: PropTypes.func,
16 | swipe: PropTypes.bool,
17 | draggable: PropTypes.bool,
18 | edgeFriction: PropTypes.number,
19 | touchThreshold: PropTypes.number,
20 | edgeEvent: PropTypes.func,
21 | swipeEvent: PropTypes.func,
22 | touchMove: PropTypes.bool,
23 | autoPlay: PropTypes.bool,
24 | autoPlaySpeed: PropTypes.number
25 | }
26 | // May be move most of the props from here to Slider. and copy them to state while
27 | // componentWillMount
28 |
29 | static defaultProps = {
30 | initialSlide: 0,
31 | vertical: false,
32 | transitionSpeed: 500,
33 | transitionTimingFn: 'ease',
34 | swipe: false,
35 | draggable: false,
36 | infinite: false,
37 | edgeFriction: 0.35,
38 | touchThreshold: 0.2,
39 | touchMove: true,
40 | autoPlay: false,
41 | autoPlaySpeed: 3000
42 | }
43 |
44 | /**
45 | * @constructor
46 | */
47 | constructor(props, context) {
48 | super(props, context);
49 | const { currentSlide, initialSlide } = props;
50 | this.state = {
51 | currentSlide: currentSlide !== undefined ? currentSlide : initialSlide,
52 | animating: false,
53 | translateXOffset: 0,
54 | translateYOffset: 0
55 | };
56 | }
57 |
58 | componentDidMount() {
59 | if (this.props.autoPlay && this.props.infinite) {
60 | this.autoPlayTransistionCallback =
61 | setInterval(() => { this.handleSlideShift(1); }, this.props.autoPlaySpeed);
62 | }
63 | }
64 |
65 | componentWillReceiveProps(nextProps) {
66 | const { currentSlide, autoPlay } = nextProps;
67 | if (currentSlide !== undefined && currentSlide !== this.state.currentSlide) {
68 | this.setState({ currentSlide });
69 | }
70 |
71 | if (this.props.autoPlay && !autoPlay && this.autoPlayTransistionCallback) {
72 | clearInterval(this.autoPlayTransistionCallback);
73 | }
74 | }
75 |
76 | componentDidUpdate(prevProps) {
77 | const { autoPlay } = prevProps;
78 |
79 | if (this.props.autoPlay && !autoPlay && this.props.infinite) {
80 | this.autoPlayTransistionCallback =
81 | setInterval(() => { this.handleSlideShift(1); }, this.props.autoPlaySpeed);
82 | }
83 | }
84 |
85 | componentWillUnmount() {
86 | if (this.transitionEndCallback) {
87 | clearTimeout(this.transitionEndCallback);
88 | }
89 | if (this.autoPlayTransistionCallback) {
90 | clearInterval(this.autoPlayTransistionCallback);
91 | }
92 | }
93 |
94 | /**
95 | * Change the position of a slider by `delta`
96 | * @method
97 | * @private
98 | * @param {number} delta - Move forward the slide by delta (delta can be negative)
99 | */
100 | handleSlideShift(delta) {
101 | const { currentSlide, animating } = this.state;
102 | const { transitionSpeed, infinite } = this.props;
103 | if (animating === true || delta === 0) {
104 | return;
105 | }
106 |
107 | const slides = this.props.children[1];
108 | const slideCount = Children.count(slides.props.children);
109 |
110 | let newNextSlide;
111 | if (infinite === true) {
112 | if (currentSlide + 1 === slideCount && delta > 0) {
113 | newNextSlide = 0;
114 | } else if (currentSlide === 0 && delta < 0) {
115 | newNextSlide = slideCount - 1;
116 | }
117 | }
118 |
119 | // EndEventListeners are not reliable. So,we use setTimeout
120 | // See react 0.14.0-rc1 blog post.
121 | // FIXME PERFORMANCE BOTTLENECK
122 | this.transitionEndCallback = () => {
123 | this.setState({
124 | currentSlide: newNextSlide === undefined ? currentSlide + delta : newNextSlide,
125 | animating: false
126 | });
127 | delete this.transitionEndCallback;
128 | };
129 |
130 | const nonInfiniteCondition =
131 | infinite === false &&
132 | ((delta < 0 && currentSlide > 0) || (delta > 0 && currentSlide + 1 < slideCount));
133 |
134 | if (nonInfiniteCondition || infinite === true) {
135 | this.setState({
136 | currentSlide: currentSlide + delta,
137 | animating: true,
138 | translateXOffset: 0,
139 | translateYOffset: 0
140 | }, () => {
141 | setTimeout(this.transitionEndCallback, transitionSpeed);
142 | });
143 | }
144 | }
145 |
146 | handleSwipeStart(e) {
147 | const { swipe, draggable, edgeEvent, swipeEvent, touchThreshold, vertical } = this.props;
148 | if (swipe === false && (draggable === false && e.type.indexOf('mouse') === -1)) {
149 | return;
150 | }
151 |
152 | const posX = (e.touches !== undefined) ? e.touches[0].pageX : e.clientX;
153 | const posY = (e.touches !== undefined) ? e.touches[0].pageY : e.clientY;
154 |
155 | // FIXME Breaks compatibility with react-0.13 may be use id to this.
156 | // Doesn't require a style recalc as the actions happens after component is
157 | // mounted or updated
158 | const slider = this.refs.slider;
159 | const sliderRect = slider.getBoundingClientRect();
160 | const trackRect = slider.children[1].children[0].getBoundingClientRect();
161 | const maxSwipeLength = vertical === false ? trackRect.width : trackRect.height;
162 | const minSwipe = (vertical === false ? sliderRect.width : sliderRect.height) * touchThreshold;
163 |
164 | this.setState({
165 | swiping: true,
166 | touchObject: {
167 | startX: posX,
168 | startY: posY,
169 | currX: posX,
170 | currY: posY,
171 | swipeLength: 0,
172 | edgeEventFired: edgeEvent === undefined ? true : false,
173 | swipeEventFired: swipeEvent === undefined ? true : false,
174 | minSwipe,
175 | maxSwipeLength
176 | }
177 | });
178 | }
179 |
180 | handleSwipeMove(e) {
181 | if (this.state.animating === true || this.state.swiping === false) {
182 | return;
183 | }
184 |
185 | const { infinite, edgeEvent, children, swipeEvent, vertical, touchMove } = this.props;
186 | const { touchObject, currentSlide } = this.state;
187 |
188 | touchObject.currY = e.touches !== undefined ? e.touches[0].pageY : e.clientY;
189 | touchObject.currX = e.touches !== undefined ? e.touches[0].pageX : e.clientX;
190 |
191 | touchObject.swipeLength = swipeDistance(touchObject);
192 |
193 | const direction = swipeDirection(touchObject);
194 | const horizontalPrev = vertical === false && direction === SWIPE_RIGHT;
195 | const horizontalNext = vertical === false && direction === SWIPE_LEFT;
196 |
197 | const verticalPrev = vertical === true && direction === SWIPE_DOWN;
198 | const verticalNext = vertical === true && direction === SWIPE_UP;
199 |
200 | const slideCount = Children.count(children[1].props.children);
201 |
202 | const edgeSwipePrev = currentSlide === 0 &&
203 | infinite === false && (horizontalPrev || verticalPrev);
204 | const edgeSwipeNext = (currentSlide + 1) === slideCount &&
205 | infinite === false && (horizontalNext || verticalNext);
206 |
207 | const edgeFriction = (edgeSwipePrev || edgeSwipeNext) ?
208 | this.props.edgeFriction : 1;
209 |
210 | // TODO refactor this into a function.
211 | if (touchObject.edgeEventFired === false) {
212 | if (edgeSwipePrev || edgeSwipeNext) {
213 | edgeEvent(direction);
214 | touchObject.edgeEventFired = true;
215 | }
216 | }
217 |
218 | if (touchObject.swipeEventFired === false) {
219 | swipeEvent(direction);
220 | touchObject.swipeEventFired = true;
221 | }
222 |
223 | const translateXOffset = vertical === false && touchMove === true ?
224 | ((touchObject.currX - touchObject.startX) * 100 * edgeFriction) / touchObject.maxSwipeLength : 0;
225 | const translateYOffset = vertical === true && touchMove === true ?
226 | ((touchObject.currY - touchObject.startY) * 100 * edgeFriction) / touchObject.maxSwipeLength : 0;
227 |
228 | // FIXME PERFORMANCE BOTTLENECK
229 | this.setState({
230 | touchObject: {...touchObject},
231 | translateXOffset,
232 | translateYOffset
233 | });
234 |
235 | // Don't cancel scrolling in the cross-axis to the slider
236 | const verticalScroll = vertical === false && (direction === SWIPE_UP || direction === SWIPE_DOWN);
237 | const horizontalScroll = vertical === true && (direction === SWIPE_LEFT || direction === SWIPE_RIGHT);
238 | if (verticalScroll || horizontalScroll) {
239 | return;
240 | }
241 |
242 | // Don't preventDefault for small movement helps in clicking links etc.,
243 | // Refer to react-slick#26
244 | if (touchObject.swipeLength > 4) {
245 | e.preventDefault();
246 | }
247 | }
248 |
249 | handleSwipeEnd(e) {
250 | if (!this.state.swiping) {
251 | return;
252 | }
253 | const { touchObject } = this.state;
254 | const { vertical } = this.props;
255 | const direction = swipeDirection(touchObject);
256 |
257 | this.setState({
258 | swiping: false,
259 | touchObject: {},
260 | translateXOffset: 0,
261 | translateYOffset: 0
262 | });
263 |
264 | const horizontalPrev = vertical === false && direction === SWIPE_RIGHT;
265 | const horizontalNext = vertical === false && direction === SWIPE_LEFT;
266 |
267 | const verticalPrev = vertical === true && direction === SWIPE_DOWN;
268 | const verticalNext = vertical === true && direction === SWIPE_UP;
269 |
270 | if (touchObject.swipeLength > touchObject.minSwipe) {
271 | e.preventDefault();
272 | if (horizontalPrev || verticalPrev) {
273 | this.handleSlideShift(-1);
274 | } else if (horizontalNext || verticalNext) {
275 | this.handleSlideShift(1);
276 | }
277 | }
278 | }
279 |
280 | render() {
281 | const { children, vertical, infinite, swipe, draggable } = this.props;
282 | const { transitionSpeed, transitionTimingFn } = this.props;
283 | const { beforeChange, afterChange } = this.props;
284 | const [ leftArrow, slides, rightArrow, customComponent ] = children;
285 | const { currentSlide, translateXOffset, translateYOffset } = this.state;
286 | const slideCount = Children.count(slides.props.children);
287 |
288 | // onClick is passed as a props so that dom elements can be custom arrows
289 |
290 | const newLeftArrow = leftArrow !== undefined ? cloneElement(leftArrow, {
291 | key: 0,
292 | handleClick: () => { this.handleSlideShift(-1); },
293 | onClick: () => { this.handleSlideShift(-1); },
294 | currentSlide,
295 | infinite
296 | }) : null;
297 |
298 | // Need to pass slideCount to check if end of slide has been reached.
299 | const newRightArrow = rightArrow !== undefined ? cloneElement(rightArrow, {
300 | key: 2,
301 | handleClick: () => { this.handleSlideShift(1); },
302 | onClick: () => { this.handleSlideShift(1); },
303 | currentSlide,
304 | infinite,
305 | slideCount
306 | }) : null;
307 |
308 | // TODO Show a warning if transitionSpeed prop is declared on Slides.
309 | const newSlides = cloneElement(slides, {
310 | key: 1,
311 | currentSlide,
312 | infinite,
313 | swipe,
314 | draggable,
315 | transitionSpeed,
316 | transitionTimingFn,
317 | vertical,
318 | onMouseDown: ::this.handleSwipeStart,
319 | onMouseMove: this.state.swiping ? ::this.handleSwipeMove : null,
320 | onMouseUp: ::this.handleSwipeEnd,
321 | onMouseLeave: this.state.swiping ? ::this.handleSwipeEnd : null,
322 | onTouchStart: ::this.handleSwipeStart,
323 | onTouchMove: this.state.swiping ? ::this.handleSwipeMove : null,
324 | onTouchEnd: ::this.handleSwipeEnd,
325 | onTouchCancel: this.state.swiping ? ::this.handleSwipeEnd : null,
326 | beforeChange,
327 | afterChange,
328 | translateXOffset,
329 | translateYOffset
330 | });
331 |
332 | const newCustomComponent = customComponent !== undefined ? cloneElement(customComponent, {
333 | currentSlide,
334 | slideCount,
335 | handleSlideShift: ::this.handleSlideShift
336 | }) : null;
337 |
338 | return (
339 |
340 |
341 | {newLeftArrow}
342 | {newSlides}
343 | {newRightArrow}
344 |
345 | {newCustomComponent}
346 |
347 | );
348 | }
349 | }
350 |
351 | export default Slider;
352 |
--------------------------------------------------------------------------------
/src/Slides.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes, Children, cloneElement } from 'react';
2 |
3 | class Page extends Component {
4 | static propTypes = {
5 | children: PropTypes.any,
6 | className: PropTypes.string,
7 | pageStyle: PropTypes.any
8 | }
9 |
10 | render() {
11 | const { pageStyle, className } = this.props;
12 |
13 | return (
14 |
15 | {this.props.children}
16 |
17 | );
18 | }
19 | }
20 |
21 | // TODO Possible PERF OPTIMIZATION remove translateXOffset and translateYOffset from
22 | // here and do imperative DOM operations (translate) inside Slider.
23 |
24 | // This is posibly blocked until the above todo.
25 | // Implement shouldComponentUpdate so that setTimeout state change only effects
26 | // on the edge. May be have an edge key inside state??
27 | class Track extends Component {
28 |
29 | static propTypes = {
30 | children: PropTypes.any,
31 | infinite: PropTypes.bool.isRequired,
32 | vertical: PropTypes.bool.isRequired,
33 | swipe: PropTypes.bool.isRequired,
34 | draggable: PropTypes.bool.isRequired,
35 | currentSlide: PropTypes.number.isRequired,
36 | pageClass: PropTypes.string,
37 | transitionSpeed: PropTypes.number.isRequired,
38 | transitionTimingFn: PropTypes.string.isRequired,
39 | beforeChange: PropTypes.func,
40 | afterChange: PropTypes.func,
41 | translateXOffset: PropTypes.number,
42 | translateYOffset: PropTypes.number
43 | }
44 |
45 | constructor(props, context) {
46 | super(props, context);
47 | this.state = {
48 | previousSlide: undefined
49 | };
50 | }
51 |
52 | componentWillReceiveProps() {
53 | // TODO May be move this to Slider
54 | const { currentSlide } = this.props;
55 | const { previousSlide } = this.state;
56 | this.setState({
57 | previousSlide: previousSlide !== currentSlide ? currentSlide : previousSlide
58 | });
59 | }
60 |
61 | shouldComponentUpdate(nextProps) {
62 | const { swipe, draggable } = this.props;
63 |
64 | if (swipe === false && draggable === false) {
65 | return this.state.previousSlide !== nextProps.currentSlide;
66 | }
67 |
68 | return true;
69 | }
70 |
71 | componentWillUpdate() {
72 | if (this.props.beforeChange !== undefined) {
73 | this.props.beforeChange(this.state.previousSlide, this.props.currentSlide);
74 | }
75 | }
76 |
77 |
78 | componentDidUpdate() {
79 | if (this.props.afterChange !== undefined) {
80 | this.props.afterChange(this.state.previousSlide, this.props.currentSlide);
81 | }
82 | }
83 |
84 | computeTrackStyle() {
85 | const { vertical, currentSlide, infinite,
86 | translateXOffset, translateYOffset,
87 | transitionSpeed, transitionTimingFn } = this.props;
88 | const slideCount = Children.count(this.props.children);
89 | const totalCount = slideCount + (infinite === true ? 2 : 0);
90 | const { previousSlide } = this.state;
91 | const preSlideCount = infinite === true ? 1 : 0;
92 |
93 | const trackWidth = vertical ? '100%' : `${100 * totalCount}%`;
94 | const trackHeight = vertical ? `${100 * totalCount}%` : '100%';
95 | const translate = (100 * (currentSlide + preSlideCount)) / totalCount;
96 | const translateX = vertical === false ? translate - translateXOffset : 0;
97 | const translateY = vertical === true ? translate - translateYOffset : 0;
98 | const trackTransform = `translate3d(${-translateX}%, ${-translateY}%, 0)`;
99 | const trackTransition =
100 | infinite === true && ((previousSlide === -1 && (currentSlide === slideCount - 1)) ||
101 | ((previousSlide === slideCount) && currentSlide === 0)) ||
102 | (translateXOffset !== 0 || translateYOffset !== 0) ? '' :
103 | `all ${transitionSpeed}ms ${transitionTimingFn}`;
104 | const flexDirection = vertical ? 'column' : 'row';
105 |
106 | const trackStyle = {
107 | width: trackWidth,
108 | height: trackHeight,
109 | display: 'flex',
110 | flexDirection,
111 | flexShrink: 0,
112 | transform: trackTransform,
113 | transition: trackTransition
114 | };
115 |
116 | return trackStyle;
117 | }
118 |
119 | computePageStyle() {
120 | const { vertical, infinite } = this.props;
121 |
122 | const slideCount = Children.count(this.props.children);
123 | const totalCount = slideCount + (infinite === true ? 2 : 0);
124 |
125 | const pageWidth = vertical ? '100%' : `${100 / totalCount}%`;
126 | const pageHeight = vertical ? `${100 / totalCount}%` : '100%';
127 | const pageStyle = {
128 | width: pageWidth,
129 | height: pageHeight,
130 | display: 'flex',
131 | justifyContent: 'center',
132 | alignItems: 'center'
133 | };
134 |
135 | return pageStyle;
136 | }
137 |
138 | render() {
139 | const { pageClass, infinite } = this.props;
140 | const slideCount = Children.count(this.props.children);
141 | const totalCount = slideCount + (infinite === true ? 2 : 0);
142 |
143 | const trackStyle = this.computeTrackStyle();
144 | const pageStyle = this.computePageStyle();
145 |
146 | const slides = Children.map(this.props.children, (child, i) =>
147 |
148 | {cloneElement(child, { key: i })}
149 |
150 | );
151 |
152 | const preSlides = slideCount === 1 || infinite === false ? null :
153 |
154 | {cloneElement(this.props.children[slideCount - 1], { key: -1 })}
155 | ;
156 |
157 | const postSlides = slideCount === 1 || infinite === false ? null :
158 |
159 | {cloneElement(this.props.children[0], { key: totalCount })}
160 | ;
161 |
162 | return (
163 |
164 | {preSlides}
165 | {slides}
166 | {postSlides}
167 |
168 | );
169 | }
170 | }
171 |
172 | class Slides extends Component {
173 | static propTypes = {
174 | children: PropTypes.any,
175 | width: PropTypes.number,
176 | height: PropTypes.number,
177 | currentSlide: PropTypes.number,
178 | infinite: PropTypes.bool,
179 | vertical: PropTypes.bool,
180 | swipe: PropTypes.bool,
181 | draggable: PropTypes.bool,
182 | pageClass: PropTypes.string,
183 | transitionSpeed: PropTypes.number,
184 | transitionTimingFn: PropTypes.string,
185 | onMouseDown: PropTypes.func,
186 | onMouseMove: PropTypes.func,
187 | onMouseUp: PropTypes.func,
188 | onMouseLeave: PropTypes.func,
189 | onTouchStart: PropTypes.func,
190 | onTouchMove: PropTypes.func,
191 | onTouchEnd: PropTypes.func,
192 | onTouchCancel: PropTypes.func,
193 | beforeChange: PropTypes.func,
194 | afterChange: PropTypes.func,
195 | translateXOffset: PropTypes.number,
196 | translateYOffset: PropTypes.number
197 | }
198 |
199 | static defaultProps = {
200 | width: 0,
201 | height: 0
202 | }
203 |
204 | render() {
205 | const { width, height, children,
206 | onMouseDown, onMouseMove, onMouseUp, onMouseLeave,
207 | onTouchStart, onTouchMove, onTouchEnd, onTouchCancel, ...props } = this.props;
208 |
209 | const containerWidth = width === 0 ? '100%' : width;
210 | const containerHeight = height === 0 ? '100%' : height;
211 | const containerStyle = {
212 | width: containerWidth,
213 | height: containerHeight,
214 | display: 'flex',
215 | overflow: 'hidden'
216 | };
217 |
218 | return (
219 |
228 |
231 |
232 | );
233 | }
234 | }
235 |
236 | export default Slides;
237 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export Slider from './Slider.js';
2 | export Slides from './Slides.js';
3 | export { PrevArrow, NextArrow } from './Arrows.js';
4 | export Dots from './Dots.js';
5 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | const SWIPE_UP = 'SWIPE_UP';
2 | const SWIPE_DOWN = 'SWIPE_DOWN';
3 | const SWIPE_LEFT = 'SWIPE_LEFT';
4 | const SWIPE_RIGHT = 'SWIPE_RIGHT';
5 |
6 | const swipeDirection = touchObject => {
7 | const { startX, startY, currX, currY } = touchObject;
8 |
9 | const deltaX = currX - startX;
10 | const deltaY = currY - startY;
11 |
12 | const angle = (Math.atan2(deltaY, deltaX) * 180) / Math.PI;
13 | if (angle < 45 && angle > -45) {
14 | return SWIPE_RIGHT;
15 | } else if ((angle <= 180 && angle > 135) || (angle < -135 && angle > -180)) {
16 | return SWIPE_LEFT;
17 | } else if (angle > 45 && angle < 135 ) {
18 | return SWIPE_DOWN;
19 | } else if (angle < -45 && angle > -135 ) {
20 | return SWIPE_UP;
21 | }
22 | };
23 |
24 | const swipeDistance = touchObject => {
25 | const { startX, startY, currX, currY } = touchObject;
26 | return Math.round(Math.sqrt(Math.pow(currX - startX, 2) + Math.pow(currY - startY, 2)));
27 | };
28 |
29 | export {
30 | swipeDirection,
31 | swipeDistance,
32 | SWIPE_UP,
33 | SWIPE_DOWN,
34 | SWIPE_LEFT,
35 | SWIPE_RIGHT
36 | };
37 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = {
5 | devtool: '#source-map',
6 | entry: [
7 | 'webpack-hot-middleware/client',
8 | './examples/index'
9 | ],
10 | output: {
11 | path: path.join(__dirname, 'dist'),
12 | filename: 'bundle.js',
13 | publicPath: '/static/'
14 | },
15 | plugins: [
16 | new webpack.HotModuleReplacementPlugin(),
17 | new webpack.NoErrorsPlugin()
18 | ],
19 | module: {
20 | loaders: [
21 | {
22 | test: /\.js$/,
23 | include: [path.join(__dirname, 'examples'), path.join(__dirname, 'src')],
24 | loaders: ['babel']
25 | },
26 | {
27 | test: /\.css$/,
28 | include: [path.join(__dirname, 'examples')],
29 | loaders: ['style', 'css']
30 | }
31 | ]
32 | }
33 | };
34 |
--------------------------------------------------------------------------------