├── test
├── __mocks__
│ ├── styleMock.js
│ └── fileMock.js
├── touchHelper.js
└── simple.test.js
├── src
├── utils.js
├── SwiperContext.js
├── SwiperSlider.js
├── styles
│ └── Swiper.module.scss
├── Sprite.js
└── Swiper.js
├── demo
├── images
│ ├── test1.jpg
│ ├── test2.jpg
│ ├── test3.jpg
│ ├── test1_bkg.jpg
│ ├── test2_bkg.jpg
│ └── test3_bkg.jpg
├── history.js
├── template
│ ├── SliderDefaultTemplate.js
│ ├── SliderUserTemplate.js
│ └── SliderSpriteTemplate.js
├── tpl
│ └── index.html
├── index.css
├── index.js
├── nanaDesign.scss
├── pages
│ ├── UserDefineSlider.js
│ ├── FreeModeSlider.js
│ ├── LoopSlider.js
│ ├── SpirteSlider.js
│ ├── CoverFlowSlider.js
│ └── NormalSlider.js
├── App.scss
├── App.js
└── serviceWorker.js
├── index.js
├── babel.config.js
├── .npmignore
├── .gitignore
├── webpackDevServer.js
├── .jest.js
├── dist
├── test.html
└── nanaSwiper.umd.js
├── package.json
├── webpack.config.bundle.js
├── webpack.config.demo.js
├── webpack.config.build.demo.js
└── README.md
/test/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
--------------------------------------------------------------------------------
/test/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub';
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | export function checkTouch(){
2 | return "ontouchstart" in window
3 | }
--------------------------------------------------------------------------------
/demo/images/test1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanaSun/nanaSwiper/HEAD/demo/images/test1.jpg
--------------------------------------------------------------------------------
/demo/images/test2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanaSun/nanaSwiper/HEAD/demo/images/test2.jpg
--------------------------------------------------------------------------------
/demo/images/test3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanaSun/nanaSwiper/HEAD/demo/images/test3.jpg
--------------------------------------------------------------------------------
/demo/images/test1_bkg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanaSun/nanaSwiper/HEAD/demo/images/test1_bkg.jpg
--------------------------------------------------------------------------------
/demo/images/test2_bkg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanaSun/nanaSwiper/HEAD/demo/images/test2_bkg.jpg
--------------------------------------------------------------------------------
/demo/images/test3_bkg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanaSun/nanaSwiper/HEAD/demo/images/test3_bkg.jpg
--------------------------------------------------------------------------------
/demo/history.js:
--------------------------------------------------------------------------------
1 | import createHashHistory from "history/createHashHistory";
2 | const history = createHashHistory()
3 | export default history
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import Sprite from "./src/Sprite"
2 | import Swiper from "./src/Swiper"
3 | import SwiperSlider from "./src/SwiperSlider"
4 | export default Swiper
5 | export {Sprite,SwiperSlider}
--------------------------------------------------------------------------------
/src/SwiperContext.js:
--------------------------------------------------------------------------------
1 | import {createContext} from 'react';
2 |
3 | const createNamedContext = name => {
4 | const context = createContext();
5 | context.Provider.displayName = `${name}.Provider`;
6 | context.Consumer.displayName = `${name}.Consumer`;
7 | return context;
8 | }
9 |
10 | const context = createNamedContext('Swiper');
11 | export default context;
--------------------------------------------------------------------------------
/demo/template/SliderDefaultTemplate.js:
--------------------------------------------------------------------------------
1 | import React,{Fragment} from 'react'
2 |
3 | export function SliderDefaultTemplate(props){
4 | //if(process.env.NODE_ENV==="development") console.log(props)
5 | return (
6 |
7 |
8 | slider-{props.text}
9 |
10 |
11 | )
12 | }
--------------------------------------------------------------------------------
/demo/template/SliderUserTemplate.js:
--------------------------------------------------------------------------------
1 | import React,{Fragment} from 'react'
2 |
3 | export function SliderUserTemplate(props){
4 | if(process.env.NODE_ENV==="development") console.log(props)
5 | return (
6 |
7 |
8 |
9 |
10 | )
11 | }
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const presets = [
2 | [
3 | "@babel/preset-env",
4 | {
5 | targets: {
6 | "browsers": ["last 10 versions"]
7 | },
8 | useBuiltIns: "usage",
9 | }
10 | ],
11 | ["@babel/preset-react"]
12 | ];
13 | const plugins=[
14 | ["@babel/plugin-proposal-class-properties"]
15 | ]
16 | module.exports = { presets,plugins };
--------------------------------------------------------------------------------
/demo/template/SliderSpriteTemplate.js:
--------------------------------------------------------------------------------
1 | import React,{Fragment,Component} from 'react'
2 | import {Sprite} from '../../index'
3 |
4 | class SliderSpriteTemplate extends Component{
5 | render(){
6 | return (
7 |
8 | {this.props.isActive?:""}
9 |
10 | )
11 | }
12 | }
13 | export {SliderSpriteTemplate}
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /test/coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | .env
25 | demo
26 | lib
27 |
--------------------------------------------------------------------------------
/demo/tpl/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React App
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 | /test/coverage
11 |
12 | # production
13 | /build
14 | /demo/dist
15 | # misc
16 | .env
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 |
28 |
--------------------------------------------------------------------------------
/demo/index.css:
--------------------------------------------------------------------------------
1 | *{
2 | margin: 0;
3 | padding: 0;
4 | }
5 | body {
6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
7 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
8 | sans-serif;
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 | overflow: hidden;
12 | width: 100%;
13 | }
14 | a,a:visited,a:active{
15 | color: inherit;
16 | text-decoration: none;
17 | }
18 | ul,li{
19 | list-style:none;
20 | }
21 |
--------------------------------------------------------------------------------
/test/touchHelper.js:
--------------------------------------------------------------------------------
1 | class Touch {
2 | emulateTouchEvent(type,point) {
3 | return new window.TouchEvent(type, {
4 | bubbles: true,
5 | cancelable: true,
6 | touches:[{
7 | clientX:point[0],
8 | clientY:point[1]
9 | }]
10 | })
11 | }
12 | emulateMouseEvent(type,point) {
13 | return new window.TouchEvent(type, {
14 | bubbles: true,
15 | cancelable: true,
16 | clientX:point[0],
17 | clientY:point[1]
18 | })
19 | }
20 | }
21 |
22 | module.exports = new Touch()
23 |
--------------------------------------------------------------------------------
/webpackDevServer.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const webpack = require('webpack');
3 | const webpackDevMiddleware = require('webpack-dev-middleware');
4 |
5 | const app = express();
6 | const config = require('./webpack.config.demo.js');
7 | const compiler = webpack(config);
8 |
9 | app.use(webpackDevMiddleware(compiler, {
10 | publicPath: config.output.publicPath
11 | }));
12 |
13 | app.use(require("webpack-hot-middleware")(compiler,{
14 | log: false,
15 | path: "/__what",
16 | heartbeat: 2000
17 | }));
18 |
19 | app.listen(8080, function () {
20 | console.log('Example app listening on port 8080!\n');
21 | });
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 | import { HashRouter} from 'react-router-dom'
7 |
8 | ReactDOM.render(, document.getElementById('root'));
9 |
10 | // If you want your app to work offline and load faster, you can change
11 | // unregister() to register() below. Note this comes with some pitfalls.
12 | // Learn more about service workers: http://bit.ly/CRA-PWA
13 | serviceWorker.unregister();
14 |
15 | if(module.hot){
16 | module.hot.accept()
17 | }
18 |
--------------------------------------------------------------------------------
/demo/nanaDesign.scss:
--------------------------------------------------------------------------------
1 | .nanaDesign{
2 | background: blue;
3 | }
4 | .nanaDesignSlider{
5 | &.nanaDesignActive{
6 | background:yellow;
7 | }
8 | & .defaultSlider{
9 | background-size:auto 100%;
10 | width: 100%;
11 | height: 100%;
12 | }
13 | }
14 | .navigationUser {
15 | width: 100%;
16 | padding: 10px 0;
17 | background: rgba(0, 0, 0, 0.3);
18 | z-index: 10;
19 | .navigationUserItem{
20 | width: 10px;
21 | height: 10px;
22 | border-radius: 10px;
23 | overflow: hidden;
24 | background: pink;
25 | flex-shrink: 0;
26 | margin:0 10px;
27 | &.navigationUserActive{
28 | width: 20px;
29 | background: plum;
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/demo/pages/UserDefineSlider.js:
--------------------------------------------------------------------------------
1 | import React,{Fragment} from 'react';
2 |
3 | import Swiper,{SwiperSlider} from '../../index'
4 |
5 | export function UserDefineSlider(){
6 | return (
7 |
8 |
9 |
14 | (
15 | slider-ahahah
16 |
)}/>
17 | (
18 | slider-gasdffds
19 |
)}/>
20 | (
21 | slider-werqwerq
22 |
)}/>
23 |
24 |
25 |
26 | )
27 | }
--------------------------------------------------------------------------------
/demo/pages/FreeModeSlider.js:
--------------------------------------------------------------------------------
1 | import React,{Fragment} from 'react';
2 |
3 | import Swiper,{SwiperSlider} from '../../index'
4 |
5 | export function FreeModeSlider(){
6 | return (
7 |
8 |
9 |
15 | (
16 | slider-ahahah
17 |
)}/>
18 | (
19 | slider-gasdffds
20 |
)}/>
21 | (
22 | slider-werqwerq
23 |
)}/>
24 |
25 |
26 |
27 | )
28 | }
--------------------------------------------------------------------------------
/src/SwiperSlider.js:
--------------------------------------------------------------------------------
1 | import React,{Component} from 'react'
2 | import PropTypes from "prop-types";
3 | import SwiperContext from "./SwiperContext";
4 | import SwiperCSS from './styles/Swiper.module.scss'
5 | class SwiperSlider extends Component {
6 | render() {
7 | return (
8 |
9 | {context => {
10 | const {currentSliderIndex,isMoving}=context
11 | const {index,width,height}=this.props
12 | return (
13 |
16 | {this.props.render({
17 | ...this.props,
18 | isMoving:isMoving,
19 | isActive:currentSliderIndex===index
20 | })}
21 |
22 | )
23 | }}
24 |
25 | )
26 | ;
27 | }
28 | }
29 | SwiperSlider.propTypes = {
30 | render:PropTypes.func.isRequired,
31 | index: PropTypes.number
32 | };
33 |
34 | export default SwiperSlider
--------------------------------------------------------------------------------
/.jest.js:
--------------------------------------------------------------------------------
1 | const transformIgnorePatterns = [
2 | '/dist/',
3 | 'node_modules/[^/]+?/(?!(es|node_modules)/)', // Ignore modules without es dir
4 | ];
5 | module.exports = {
6 | verbose: true,
7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'md'],
8 | testPathIgnorePatterns: ['/node_modules/', 'dist'],
9 | transform: {
10 | "^.+\\.js$": "babel-jest"
11 | },
12 | moduleNameMapper: {
13 | "\\.(css|less|scss)$": "identity-obj-proxy",
14 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "./test/__mocks__/fileMock.js"
15 | },
16 | transformIgnorePatterns,
17 | testURL: 'http://localhost',
18 | globals: {
19 | ontouchstart: null//模拟windows的touchstart
20 | },
21 | collectCoverage :true,
22 | coverageDirectory: '/test/coverage',
23 | collectCoverageFrom:[
24 | "**/index.{js,jsx}",
25 | "**/src/*.{js,jsx}"
26 | ],
27 | coveragePathIgnorePatterns :["node_modules","demo","dist"],
28 | coverageThreshold: { // 测试覆盖率通过阈值
29 | global: {
30 | branches: 90,
31 | functions: 90,
32 | lines: 90,
33 | statements: 90
34 | }
35 | }
36 | };
--------------------------------------------------------------------------------
/demo/pages/LoopSlider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Swiper,{SwiperSlider} from '../../index'
4 | import {SliderSpriteTemplate} from '../template/SliderSpriteTemplate'
5 | import {SliderDefaultTemplate} from '../template/SliderDefaultTemplate'
6 | import img1 from "../images/test1.jpg"
7 | import img1Bkg from "../images/test1_bkg.jpg"
8 | import img3 from "../images/test3.jpg"
9 | import img3Bkg from "../images/test3_bkg.jpg"
10 |
11 | const data=[
12 | {
13 | width:window.innerWidth,
14 | height:300,
15 | spriteImg:img1,
16 | spriteConf:[3,1,222,350],
17 | speed:6,
18 | img:img1Bkg,
19 | tpl:SliderSpriteTemplate
20 | },
21 | {
22 | text:"text",
23 | tpl:SliderDefaultTemplate
24 | },
25 | {
26 | width:window.innerWidth,
27 | height:300,
28 | spriteImg:img3,
29 | spriteConf:[3,1,222,350],
30 | speed:6,
31 | img:img3Bkg,
32 | tpl:SliderSpriteTemplate
33 | }
34 | ]
35 | export function LoopSlider(){
36 | return (
37 |
42 | {data.map((d,index)=>{
43 | return }/>
44 | })}
45 |
46 |
)
47 | }
--------------------------------------------------------------------------------
/demo/pages/SpirteSlider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Swiper,{SwiperSlider} from '../../index'
4 | import {SliderSpriteTemplate} from '../template/SliderSpriteTemplate'
5 | import {SliderDefaultTemplate} from '../template/SliderDefaultTemplate'
6 | import img1 from "../images/test1.jpg"
7 | import img1Bkg from "../images/test1_bkg.jpg"
8 | import img3 from "../images/test3.jpg"
9 | import img3Bkg from "../images/test3_bkg.jpg"
10 |
11 | const data=[
12 | {
13 | width:window.innerWidth,
14 | height:300,
15 | spriteImg:img1,
16 | spriteConf:[3,1,222,350],
17 | speed:6,
18 | img:img1Bkg,
19 | tpl:SliderSpriteTemplate
20 | },
21 | {
22 | text:"text",
23 | tpl:SliderDefaultTemplate
24 | },
25 | {
26 | width:window.innerWidth,
27 | height:300,
28 | spriteImg:img3,
29 | spriteConf:[3,1,222,350],
30 | speed:6,
31 | img:img3Bkg,
32 | tpl:SliderSpriteTemplate
33 | }
34 | ]
35 | export function SpirteSlider(){
36 | return (
37 |
42 | {data.map((d,index)=>{
43 | return }/>
44 | })}
45 |
46 |
)
47 | }
--------------------------------------------------------------------------------
/src/styles/Swiper.module.scss:
--------------------------------------------------------------------------------
1 | .SwiperWrapper{
2 | position: relative;
3 | height: 100%;
4 | width: 100%;
5 | z-index: 1;
6 | display: flex;
7 | transition-property: transform;
8 | box-sizing: border-box;
9 | will-change: transition;
10 | touch-action: none;
11 | }
12 |
13 | .SwiperSlider{
14 | box-sizing: border-box;
15 | /*background: #eee;*/
16 | background-size:100% 100%;
17 | background-repeat:no-repeat;
18 | color: #333;
19 | width: 100%;
20 | height: 100%;
21 | overflow: hidden;
22 | flex-shrink:0;
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | }
27 | .flatCoverFlow {
28 | margin:10px;
29 | }
30 | .flatCoverFlow .SwiperSlider{
31 | transform: scale3d(0.9,0.9,1);
32 | will-change: transform;
33 | box-shadow: 0px 0px 10px #333;
34 | }
35 | .SwiperSlider.SwiperActive{
36 | transform: scale3d(1,1,1);
37 | }
38 | .SwiperSliderNav {
39 | display:flex;
40 | justify-content: center;
41 | position: absolute;
42 | bottom: 0px;
43 | left:0px;
44 | width: 100%;
45 | padding: 10px 0;
46 | // background: rgba(0, 0, 0, 0.3);
47 | z-index: 10;
48 | .SwiperItemNav{
49 | width: 10px;
50 | height: 10px;
51 | border-radius: 10px;
52 | overflow: hidden;
53 | background: gray;
54 | flex-shrink: 0;
55 | margin:0 10px;
56 | &.SwiperNavActive{
57 | background: red;
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/demo/pages/CoverFlowSlider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Swiper,{SwiperSlider} from '../../index'
4 | import {SliderSpriteTemplate} from '../template/SliderSpriteTemplate'
5 | import {SliderDefaultTemplate} from '../template/SliderDefaultTemplate'
6 | import img1 from "../images/test1.jpg"
7 | import img1Bkg from "../images/test1_bkg.jpg"
8 | import img3 from "../images/test3.jpg"
9 | import img3Bkg from "../images/test3_bkg.jpg"
10 |
11 | const width=window.innerWidth*.8-20
12 | const initMovex=window.innerWidth*.1
13 |
14 | const data=[
15 | {
16 | width:width,
17 | height:300,
18 | spriteImg:img1,
19 | spriteConf:[3,1,222,350],
20 | speed:6,
21 | img:img1Bkg,
22 | tpl:SliderSpriteTemplate
23 | },
24 | {
25 | text:"text",
26 | tpl:SliderDefaultTemplate
27 | },
28 | {
29 | width:width,
30 | height:300,
31 | spriteImg:img3,
32 | spriteConf:[3,1,222,350],
33 | speed:6,
34 | img:img3Bkg,
35 | tpl:SliderSpriteTemplate
36 | }
37 | ]
38 | export function CoverFlowSlider(){
39 | return (
40 |
47 | {data.map((d,index)=>{
48 | return }/>
49 | })}
50 |
51 |
)
52 | }
--------------------------------------------------------------------------------
/demo/App.scss:
--------------------------------------------------------------------------------
1 | .SwiperContainer{
2 | width: 100%;
3 | height: 300px;
4 | overflow: hidden;
5 | position: relative;
6 | display: flex;
7 | }
8 | .menu{
9 | width:10%;
10 | padding-top:10%;
11 | background:pink;
12 | position: fixed;
13 | right: 0px;
14 | z-index: 1000;
15 | &:before,&:after,span{
16 | content:"";
17 | background:white;
18 | position:absolute;
19 | top:50%;
20 | left:50%;
21 | width:10%;
22 | height:60%;
23 | }
24 | span{
25 | transform:translate(-50%,-50%) rotate(90deg);
26 | transition:opacity 1s,background 1s;
27 | }
28 | &:before{
29 | transform:translate(-50%,-20%) rotate(-90deg);
30 | transition:transform 1s,background 1s;
31 | }
32 | &:after{
33 | transform:translate(-50%,-80%) rotate(90deg);
34 | transition:transform 1s,background 1s;
35 | }
36 | &.close{
37 | &:before,&:after,span{
38 | background:red
39 | }
40 | span{
41 | opacity:0;
42 | }
43 | &:before{
44 | transform:translate(-50%,-50%) rotate(-135deg);
45 | }
46 | &:after{
47 | transform:translate(-50%,-50%) rotate(-45deg);
48 | }
49 | }
50 | }
51 | .navigation{
52 | position:fixed;
53 | width: 100%;
54 | right: -100%;
55 | z-index: 999;
56 | background: pink;
57 | transition: transform 1s ;
58 | &.show{
59 | transform: translateX(-100%);
60 | }
61 | li{
62 | line-height: 3;
63 | padding:0 5%;
64 | &.currentPath{
65 | background: red;
66 | color: #fff;
67 | }
68 | }
69 | }
70 | .intro{
71 | padding: 40px 5%;
72 | ol {
73 | padding:0 0 0 20px;
74 | }
75 | li{
76 | list-style-type: decimal;
77 | }
78 | }
79 |
80 | .userDefaultSlider1{
81 | width: 100%;
82 | height: 100%;
83 | background: pink;
84 | }
85 | .userDefaultSlider2{
86 | width: 100%;
87 | height: 100%;
88 | background: gainsboro;
89 | }
90 | .userDefaultSlider3{
91 | width: 100%;
92 | height: 100%;
93 | background: lightsteelblue;
94 | }
--------------------------------------------------------------------------------
/dist/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
71 |
72 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nanaswiper",
3 | "version": "0.1.6",
4 | "private": false,
5 | "scripts": {
6 | "test": "jest --config .jest.js",
7 | "start": "node ./webpackDevServer.js",
8 | "broswer": "webpack --config ./webpack.config.bundle.js"
9 | },
10 | "module": "dist/nanaSwiper.umd.js",
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/nanaSun/nanaSwiper.git"
14 | },
15 | "eslintConfig": {
16 | "extends": "react-app"
17 | },
18 | "browserslist": [
19 | ">0.2%",
20 | "not dead",
21 | "not ie <= 11",
22 | "not op_mini all"
23 | ],
24 | "peerDependencies": {
25 | "prop-types": "^15.7.2",
26 | "react": "^16.8.1"
27 | },
28 | "dependencies": {
29 | "@babel/polyfill": "^7.2.5",
30 | "react-router": "^4.3.1",
31 | "react-router-dom": "^4.3.1",
32 | "prop-types": "^15.7.2",
33 | "react": "^16.8.1",
34 | "react-dom": "^16.8.1"
35 | },
36 | "devDependencies": {
37 | "@babel/cli": "^7.2.3",
38 | "@babel/core": "^7.2.2",
39 | "@babel/plugin-proposal-class-properties": "^7.3.3",
40 | "@babel/preset-env": "^7.3.1",
41 | "@babel/preset-react": "^7.0.0",
42 | "babel-eslint": "^9.0.0",
43 | "babel-jest": "^24.1.0",
44 | "babel-loader": "^8.0.5",
45 | "clean-webpack-plugin": "^1.0.1",
46 | "codecov": "^3.2.0",
47 | "css-loader": "^2.1.0",
48 | "enzyme": "^3.9.0",
49 | "enzyme-adapter-react-16": "^1.9.1",
50 | "eslint": "^5.14.1",
51 | "eslint-config-react-app": "^3.0.7",
52 | "eslint-loader": "^2.1.2",
53 | "eslint-plugin-flowtype": "^2.50.3",
54 | "eslint-plugin-import": "^2.16.0",
55 | "eslint-plugin-jsx-a11y": "^6.2.1",
56 | "eslint-plugin-react": "^7.12.4",
57 | "express": "^4.16.4",
58 | "file-loader": "^3.0.1",
59 | "html-webpack-plugin": "^3.2.0",
60 | "identity-obj-proxy": "^3.0.0",
61 | "jest": "^24.1.0",
62 | "mini-css-extract-plugin": "^0.5.0",
63 | "node-sass": "^4.11.0",
64 | "postcss-flexbugs-fixes": "^4.1.0",
65 | "postcss-loader": "^3.0.0",
66 | "postcss-preset-env": "^6.5.0",
67 | "react-test-renderer": "^16.8.3",
68 | "sass-loader": "^7.1.0",
69 | "style-loader": "^0.23.1",
70 | "url-loader": "^1.1.2",
71 | "webpack": "^4.29.3",
72 | "webpack-cli": "^3.2.3",
73 | "webpack-dev-middleware": "^3.6.0",
74 | "webpack-dev-server": "^3.1.14",
75 | "webpack-hot-middleware": "^2.24.3",
76 | "webpack-umd-external": "^1.0.2"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/demo/pages/NormalSlider.js:
--------------------------------------------------------------------------------
1 | import React,{Fragment} from 'react';
2 |
3 | import Swiper,{SwiperSlider} from '../../index'
4 |
5 | const data=[
6 | {
7 | text:"text1"
8 | },
9 | {
10 | text:"text2"
11 | },
12 | {
13 | text:"text3"
14 | }
15 | ]
16 | export function NormalSlider(){
17 | return (
18 |
19 | 类似于router这样,一个个router子函数写好
20 |
21 |
27 | (
28 | slider-ahahah
29 |
)}/>
30 | (
31 | slider-gasdffds
32 |
)}/>
33 | (
34 | slider-werqwerq
35 |
)}/>
36 |
37 |
38 | 带循环的slider
39 |
40 |
45 | {data.map((d,index)=>{
46 | return (
47 | slider-{d.text}
48 |
)}/>
49 | })}
50 |
51 |
52 | 非循环的slider
53 |
54 |
59 | {data.map((d,index)=>{
60 | return (
61 | slider-{d.text}
62 |
)}/>
63 | })}
64 |
65 |
66 |
67 | )
68 | }
--------------------------------------------------------------------------------
/webpack.config.bundle.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | const cssRegex = /\.css$/;
5 | const cssModuleRegex = /\.module\.css$/;
6 | const sassRegex = /\.(scss|sass)$/;
7 | const sassModuleRegex = /\.module\.(scss|sass)$/;
8 | const getStyleLoaders = (cssOptions, preProcessor) => {
9 | const loaders = [
10 | require.resolve('style-loader'),
11 | {
12 | loader: require.resolve('css-loader'),
13 | options: cssOptions,
14 | },
15 | {
16 | loader: require.resolve('postcss-loader'),
17 | options: {
18 | ident: 'postcss',
19 | plugins: () => [
20 | require('postcss-flexbugs-fixes'),
21 | require('postcss-preset-env')({
22 | browsers: 'last 8 versions'
23 | }),
24 | ],
25 | },
26 | },
27 | ];
28 | if (preProcessor) {
29 | loaders.push(require.resolve(preProcessor));
30 | }
31 | return loaders;
32 | };
33 | const config = {
34 | context:__dirname,
35 | mode: 'production',
36 | entry: path.join(__dirname, 'index.js'),
37 | output: {
38 | path: path.join(__dirname,"dist"),
39 | filename: 'nanaSwiper.umd.js',
40 | //filename: 'nanaSwiper.cjs.js',
41 | libraryTarget: "umd",
42 | library: "Swiper",
43 | umdNamedDefine: true
44 | },
45 | resolve: {
46 | extensions: ['.js', '.jsx']
47 | },
48 | plugins: [
49 | new webpack.DefinePlugin({
50 | 'process.env': {
51 | NODE_ENV: JSON.stringify("development"),
52 | PUBLIC_URL: JSON.stringify("")
53 | }
54 | }),
55 | ],
56 | externals:{
57 | react:{
58 | root: 'React',
59 | amd: 'react',
60 | commonjs: 'react',
61 | commonjs2: 'react'
62 | },
63 | "prop-types":{
64 | root: 'prop-types',
65 | amd: 'prop-types',
66 | commonjs: 'prop-types',
67 | commonjs2: 'prop-types'
68 | }
69 | },
70 | module: {
71 | rules: [
72 | {
73 | test: /\.(js|mjs|jsx)$/,
74 | exclude: /node_modules/,
75 | loader: 'babel-loader'
76 | },
77 | {
78 | test: cssRegex,
79 | exclude: cssModuleRegex,
80 | use: getStyleLoaders({
81 | importLoaders: 1,
82 | }),
83 | },
84 | {
85 | test: cssModuleRegex,
86 | use: getStyleLoaders({
87 | importLoaders: 1,
88 | modules: true
89 | }),
90 | },
91 | {
92 | test: sassRegex,
93 | exclude: sassModuleRegex,
94 | use: getStyleLoaders({ importLoaders: 2 }, 'sass-loader'),
95 | },
96 | {
97 | test: sassModuleRegex,
98 | use: getStyleLoaders(
99 | {
100 | importLoaders: 2,
101 | modules: true
102 | },
103 | 'sass-loader'
104 | ),
105 | },
106 | ]
107 | }
108 | };
109 | module.exports = config;
--------------------------------------------------------------------------------
/webpack.config.demo.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | const cssRegex = /\.css$/;
6 | const cssModuleRegex = /\.module\.css$/;
7 | const sassRegex = /\.(scss|sass)$/;
8 | const sassModuleRegex = /\.module\.(scss|sass)$/;
9 |
10 | const getStyleLoaders = (cssOptions, preProcessor) => {
11 | const loaders = [
12 | require.resolve('style-loader'),
13 | {
14 | loader: require.resolve('css-loader'),
15 | options: cssOptions,
16 | },
17 | {
18 | loader: require.resolve('postcss-loader'),
19 | options: {
20 | ident: 'postcss',
21 | plugins: () => [
22 | require('postcss-flexbugs-fixes'),
23 | require('postcss-preset-env')({
24 | browsers: 'last 8 versions'
25 | }),
26 | ],
27 | },
28 | },
29 | ];
30 | if (preProcessor) {
31 | loaders.push(require.resolve(preProcessor));
32 | }
33 | return loaders;
34 | };
35 |
36 | const config = {
37 | context: __dirname,
38 | mode: 'development',
39 | entry: [
40 | require.resolve('webpack-hot-middleware/client') + '?path=/__what&timeout=2000&overlay=false',
41 | path.join(__dirname, 'demo/index.js')
42 | ],
43 | output: {
44 | path: path.join(__dirname, 'demo/dist'),
45 | filename: '[name].[hash].js'
46 | },
47 | resolve: {
48 | extensions: ['.js', '.jsx']
49 | },
50 | plugins: [
51 | new HtmlWebpackPlugin({
52 | inject: true,
53 | template: path.join(__dirname, '/demo/tpl/index.html'),
54 | }),
55 | new webpack.HotModuleReplacementPlugin(),
56 | ],
57 | module: {
58 | rules: [
59 | {
60 | enforce: "pre",
61 | test: /\.(js|mjs|jsx)$/,
62 | exclude: /node_modules/,
63 | loader: "eslint-loader"
64 | },
65 | {
66 | oneOf: [
67 | {
68 | test: /\.(js|mjs|jsx)$/,
69 | exclude: /node_modules/,
70 | loader: ['babel-loader','eslint-loader']
71 | },
72 | {
73 | test: cssRegex,
74 | exclude: cssModuleRegex,
75 | use: getStyleLoaders({
76 | importLoaders: 1,
77 | }),
78 | },
79 | {
80 | test: cssModuleRegex,
81 | use: getStyleLoaders({
82 | importLoaders: 1,
83 | modules: true
84 | }),
85 | },
86 | {
87 | test: sassRegex,
88 | exclude: sassModuleRegex,
89 | use: getStyleLoaders({ importLoaders: 2 }, 'sass-loader'),
90 | },
91 | {
92 | test: sassModuleRegex,
93 | use: getStyleLoaders(
94 | {
95 | importLoaders: 2,
96 | modules: true
97 | },
98 | 'sass-loader'
99 | ),
100 | },
101 | {
102 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
103 | loader: require.resolve('url-loader'),
104 | options: {
105 | limit: 10000,
106 | name: 'static/media/[name].[hash:8].[ext]',
107 | },
108 | }]
109 | }
110 | ]
111 | }
112 | };
113 |
114 | module.exports = config;
115 |
--------------------------------------------------------------------------------
/demo/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import './App.scss';
4 | import {SpirteSlider} from "./pages/SpirteSlider"
5 | import {NormalSlider} from "./pages/NormalSlider"
6 | import {LoopSlider} from "./pages/LoopSlider"
7 | import {CoverFlowSlider} from "./pages/CoverFlowSlider"
8 | import {FreeModeSlider} from "./pages/FreeModeSlider"
9 | import {UserDefineSlider} from "./pages/UserDefineSlider"
10 | import { Switch,Route, Link } from 'react-router-dom'
11 | import history from './history'
12 | const navs=[
13 | {
14 | name:"序列帧滚动",
15 | path:"/"
16 | },
17 | {
18 | name:"普通滚动",
19 | path:"/simple"
20 | }
21 | ,
22 | {
23 | name:"无限滚动",
24 | path:"/loop"
25 | }
26 | ,
27 | {
28 | name:"coverFlow样式",
29 | path:"/coverflow"
30 | },
31 | {
32 | name:"freeMode",
33 | path:"/freemode"
34 | },
35 | {
36 | name:"用户自定义",
37 | path:"/userdefine"
38 | }
39 | ]
40 | function PathNav(props){
41 | return
42 | {navs.map((nav,index)=>- {nav.name}
)}
43 |
44 | }
45 | class App extends Component {
46 | state={
47 | showMenu:false,
48 | currentPath:history.location.pathname
49 | }
50 | componentDidMount(){
51 | history.listen(()=>{
52 | this.setState({
53 | showMenu:false,
54 | currentPath:history.location.pathname
55 | })
56 | })
57 | }
58 | clickMenuBtn(){
59 | this.setState({
60 | showMenu:!this.state.showMenu
61 | })
62 | }
63 | render() {
64 | return (
65 |
66 |
{this.clickMenuBtn()}}>
67 |
68 | {/* // 用于固定Swiper大小 */}
69 |
70 | {
71 | return ;
72 | }}/>
73 | {
74 | return ;
75 | }}/>
76 | {
77 | return ;
78 | }}/>
79 | {
80 | return ;
81 | }}/>
82 | {
83 | return ;
84 | }}/>
85 | {
86 | return ;
87 | }}/>
88 |
89 |
90 |
91 |
当前演示: {navs.filter((n)=>n.path===this.state.currentPath)[0]["name"]}
92 |
实现了simple的Swiper
93 |
功能有:
94 |
95 | - 可以滑动
96 | - 可以自动滑动到下一页
97 |
98 | - 可以自定义模板
99 | - 设置灵敏度
100 | - 支持序列帧
101 | - 无限轮播图
102 |
103 |
github地址
104 |
105 |
106 |
107 | );
108 | }
109 | }
110 |
111 | export default App;
112 |
--------------------------------------------------------------------------------
/webpack.config.build.demo.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | const cssRegex = /\.css$/;
6 | const cssModuleRegex = /\.module\.css$/;
7 | const sassRegex = /\.(scss|sass)$/;
8 | const sassModuleRegex = /\.module\.(scss|sass)$/;
9 |
10 | const getStyleLoaders = (cssOptions, preProcessor) => {
11 | const loaders = [
12 | require.resolve('style-loader'),
13 | {
14 | loader: require.resolve('css-loader'),
15 | options: cssOptions,
16 | },
17 | {
18 | loader: require.resolve('postcss-loader'),
19 | options: {
20 | ident: 'postcss',
21 | plugins: () => [
22 | require('postcss-flexbugs-fixes'),
23 | require('postcss-preset-env')({
24 | browsers: 'last 8 versions'
25 | }),
26 | ],
27 | },
28 | },
29 | ];
30 | if (preProcessor) {
31 | loaders.push(require.resolve(preProcessor));
32 | }
33 | return loaders;
34 | };
35 |
36 | const config = {
37 | context: __dirname,
38 | mode: 'production',
39 | entry: [
40 | path.join(__dirname, 'demo/index.js')
41 | ],
42 | optimization: {
43 | splitChunks: {
44 | cacheGroups: {
45 | commons: {
46 | chunks: "initial",
47 | minChunks: 2,//最小重复的次数
48 | minSize: 0//最小提取字节数
49 | },
50 | vendor: {
51 | test: /node_modules/,
52 | chunks: "initial",
53 | name: "vendor",
54 | }
55 | }
56 | }
57 | },
58 | output: {
59 | path: path.join(__dirname, 'demo/dist'),
60 | filename: '[name].[hash].js'
61 | },
62 | resolve: {
63 | extensions: ['.js', '.jsx']
64 | },
65 | plugins: [
66 | new HtmlWebpackPlugin({
67 | inject: true,
68 | template: path.join(__dirname, '/demo/tpl/index.html'),
69 | }),
70 | new webpack.DefinePlugin({
71 | 'process.env': {
72 | NODE_ENV: JSON.stringify("production")
73 | }
74 | }),
75 | ],
76 | module: {
77 | rules: [
78 | {
79 | oneOf: [
80 | {
81 | test: /\.(js|mjs|jsx)$/,
82 | exclude: /node_modules/,
83 | loader: ['babel-loader']
84 | },
85 | {
86 | test: cssRegex,
87 | exclude: cssModuleRegex,
88 | use: getStyleLoaders({
89 | importLoaders: 1,
90 | }),
91 | },
92 | {
93 | test: cssModuleRegex,
94 | use: getStyleLoaders({
95 | importLoaders: 1,
96 | modules: true
97 | }),
98 | },
99 | {
100 | test: sassRegex,
101 | exclude: sassModuleRegex,
102 | use: getStyleLoaders({ importLoaders: 2 }, 'sass-loader'),
103 | },
104 | {
105 | test: sassModuleRegex,
106 | use: getStyleLoaders(
107 | {
108 | importLoaders: 2,
109 | modules: true
110 | },
111 | 'sass-loader'
112 | ),
113 | },
114 | {
115 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
116 | loader: require.resolve('url-loader'),
117 | options: {
118 | limit: 10000,
119 | name: 'static/media/[name].[hash:8].[ext]',
120 | },
121 | }]
122 | }
123 | ]
124 | }
125 | };
126 |
127 | module.exports = config;
128 |
--------------------------------------------------------------------------------
/test/simple.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Swiper,{SwiperSlider} from '../index';
3 | import {SliderDefaultTemplate} from '../demo/template/SliderDefaultTemplate'
4 | //import TestRenderer from 'react-test-renderer';
5 | import touch from './touchHelper';
6 | import Adapter from 'enzyme-adapter-react-16';
7 | import { configure, shallow, mount, render } from 'enzyme';
8 | configure({ adapter: new Adapter() });
9 | const winwidth=window.innerWidth;
10 | const data=[
11 | {
12 | text:"text1",
13 | tpl:SliderDefaultTemplate
14 | },
15 | {
16 | text:"text2",
17 | tpl:SliderDefaultTemplate
18 | },
19 | {
20 | text:"text3",
21 | tpl:SliderDefaultTemplate
22 | }
23 | ]
24 | //没有做y上的处理,所以这里全部为0,并不影响结果
25 | /**
26 | * 宽度是整屏宽
27 | * 灵敏度是移动偏差为宽度*0.2的时候,触发滚动
28 | * 非循环,小于第一屏和大于最后一屏幕会回弹
29 | */
30 | const baseComponent = {
31 | width:winwidth,
32 | sensitive:.2,
33 | componment:null
34 | };
35 | baseComponent.componment=(
40 | {data.map((d,index)=>{
41 | return }/>
42 | })}
43 | )
44 |
45 | describe('Swiper simple test', () => {
46 | const touchPoints=[
47 | [[winwidth*.5,0],[winwidth*.6,0],[winwidth*.6],0,"sensitive less then .2 and move right slider come back to current"],//偏差小于.2
48 | [[winwidth*.5,0],[winwidth*.29,0],[winwidth*.29],1,"sensitive bigger then .2 and move left slider come to next "],//偏差大于.2,向左
49 | [[winwidth*.5,0],[winwidth*.6,0],[winwidth*.6],1,"sensitive less then .2 and move right slider come back to current "],//偏差大于.2,向右
50 | [[winwidth*.5,0],[winwidth*.2,0],[winwidth*.2],2,"sensitive bigger then .2 and move left slider come to next"],//偏差小于.2,向右
51 | [[winwidth*.5,0],[winwidth*.9,0],[winwidth*.9],1,"sensitive bigger then .2 and move right slider come to prev"],//偏差小于.2,向右
52 | [[winwidth*.5,0],[winwidth*.29,0],[winwidth*.29],2,"sensitive bigger then .2 and move left slider come to next"],//偏差大于.2,向左
53 | [[winwidth*.5,0],[winwidth*.1,0],[winwidth*.1],2,"sensitive bigger then .2 and move left slider come back to current"],//偏差大于.2,向左
54 | [[winwidth*.5,0],[winwidth*.4,0],[winwidth*.4],2,"sensitive less then .2 and move left slider come back to current "],//偏差大于.2,向左
55 | ]
56 | const tree=mount(baseComponent.componment)
57 | const c = tree.instance()
58 | for(let point of touchPoints){
59 | it(point[4], () => {
60 | c.touchstart(touch.emulateTouchEvent('touchstart',point[0]))
61 | c.touchmove(touch.emulateTouchEvent('touchmove',point[1]))
62 | c.touchend(touch.emulateTouchEvent('touchend',point[2]))
63 | c.transitionend()
64 | expect(c.currentSliderIndex).toBe(point[3]);
65 | });
66 | }
67 | });
68 |
69 | /**
70 | * 宽度是整屏宽
71 | * 灵敏度是移动偏差为宽度*0.2的时候,触发滚动
72 | * 循环,小于第一屏会跳转至下一屏
73 | */
74 | const loopComponent={
75 | width:winwidth,
76 | sensitive:.2,
77 | componment:null
78 | };
79 | loopComponent.componment=(
84 | {data.map((d,index)=>{
85 | return }/>
86 | })}
87 | )
88 | describe('Swiper loop test', () => {
89 | const loopPoints=[
90 | [[winwidth*.5,0],[winwidth*.29,0],[winwidth*.29,0],2,"sensitive bigger then .2 and move left slider come to next"],//偏差大于.2,向左
91 | [[winwidth*.5,0],[winwidth*.2,0],[winwidth*.2,0],3,"sensitive bigger then .2 and move left slider come to last"],//偏差小于.2,向左
92 | [[winwidth*.5,0],[winwidth*.1,0],[winwidth*.1,0],1,"sensitive bigger then .2 and move right slider come to first"],//偏差小于.2,向左,loop至第一个
93 | [[winwidth*.5,0],[winwidth*.9,0],[winwidth*.9,0],3,"sensitive bigger then .2 and move right slider come to last"],//偏差小于.2,向右,返回最后一个
94 | ]
95 | const tree=mount(loopComponent.componment)
96 | const c = tree.instance()
97 | it("has plus two slider and init slider's index is 1", () => {
98 | expect(c.currentSliderIndex).toBe(1);
99 | expect(c.sliders.length).toBe(data.length+2);
100 | });
101 | for(let point of loopPoints){
102 | it(point[4], () => {
103 | c.touchstart(touch.emulateTouchEvent('touchstart',point[0]))
104 | c.touchmove(touch.emulateTouchEvent('touchmove',point[1]))
105 | c.touchend(touch.emulateTouchEvent('touchend',point[2]))
106 | c.transitionend()
107 | expect(c.currentSliderIndex).toBe(point[3]);
108 | });
109 | }
110 | });
--------------------------------------------------------------------------------
/src/Sprite.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from "prop-types";
3 | export class Sprite extends Component {
4 | //canvas的宽高
5 | canvasWidth=0
6 | canvasHeight=0
7 | //图片的加载
8 | image=new Image()
9 | refs=null
10 | loadImagePromise=null
11 | //序列帧的参数 行数列数以及序列帧的范围
12 | row=0
13 | col=0
14 | fpsWidth=0
15 | fpsHeight=0
16 | //动画中序列的位置
17 | currentRow=0
18 | currentCol=0
19 | //控制序列帧的速度
20 | fps=0
21 | //是否停止
22 | stop=true
23 | loaded=false
24 | animationTimeout=null
25 | animating=false
26 | time=0
27 | endtime=0
28 | animationTimer=null
29 | constructor(props){
30 | super(props)
31 | if(process.env.NODE_ENV==="development") console.log(props)
32 | //初始化
33 |
34 | this.canvasWidth=props.width*2
35 | this.canvasHeight=props.height*2
36 | this.speed=props.speed||1
37 | this.image.src=props.spriteImg
38 | this.loadImagePromise=this.loadImg()
39 |
40 | if(this.props.spriteConf&&this.props.spriteConf.length===4)
41 | [this.row,this.col,this.fpsWidth,this.fpsHeight]=this.props.spriteConf
42 |
43 | //this.canvas=React.createRef()
44 | this.canvas=(refs)=>{
45 | if(refs) {
46 | //获取绘图环境
47 | this.refs=refs
48 | this.context=refs.getContext("2d")
49 | if(process.env.NODE_ENV==="development") console.log(refs)
50 | }
51 | }
52 | }
53 | shouldComponentUpdate(props){
54 | console.log("shouldComponentUpdate",props.isMoving)
55 | if(props.isMoving!==0){
56 | this.stop=true
57 | return false
58 | }else{
59 | return true
60 | }
61 | }
62 | componentDidUpdate(){//当幻灯片停止滑动的时候,更新组件,重新获取context
63 | window.cancelAnimationFrame(this.animationTimer)//当幻灯片疯狂滑动的时候,停止滑动
64 | this.context=this.refs.getContext("2d")
65 | this.stop=false
66 | this.animating=false
67 | console.log("did update",this.animating)
68 | this.animate()
69 | if(process.env.NODE_ENV==="development") console.log("componentDidUpdate",this.context)
70 | }
71 | componentDidMount(){
72 | this.currentRow=0
73 | this.currentCol=0
74 | //初始化之后,设定动画不停止
75 | this.stop=false
76 | if(!this.loaded){
77 | this.loadImagePromise.then(()=>{
78 | this.animate()
79 | //图片加载完之后才算真正加载完
80 | this.loaded=true
81 | },(e)=>console.log(e))
82 | }else{
83 | this.animate()
84 | }
85 | }
86 | componentWillUnmount(){
87 | if(process.env.NODE_ENV==="development") console.log("canvas unload")
88 | //卸载后,清除canvas
89 | this.stop=true
90 | if(this.context.clearRect) this.context.clearRect(0,0,this.canvasWidth,this.canvasHeight)
91 | this.image=null
92 | }
93 | animate(){
94 | let _this=this
95 | if(_this.animating) return
96 | _this.animating=true;
97 | animation();
98 | function animation(){
99 | // _this.endtime=new Date().getTime()
100 | // console.log("time",_this.endtime-_this.time)
101 | // _this.time=_this.endtime
102 | if(_this.fps>=_this.speed){//此处控制速度
103 | _this.fps=0
104 | let cr=_this.currentRow, cc=_this.currentCol;
105 | if(cr===_this.row){
106 | _this.currentRow=0
107 | _this.currentCol++
108 | }else{
109 | _this.currentRow++
110 | }
111 | if(cc>_this.col){
112 | _this.currentCol=0
113 | }
114 | _this.drawSpirt()
115 | }
116 | _this.fps++
117 | if(!_this.stop&&_this.context){
118 | _this.animationTimer=window.requestAnimationFrame(animation)//如果未停止以及存在绘图环境
119 | }
120 | else{ if(process.env.NODE_ENV==="development") console.log(_this)}
121 | }
122 |
123 |
124 | }
125 | drawSpirt(){
126 | if(this.stop) return
127 | let cr=this.currentRow, cc=this.currentCol;
128 | if("drawImage" in this.context){this.context.drawImage(this.image,cr*this.fpsWidth,cc*this.fpsHeight,this.fpsWidth,this.fpsHeight,0,0,this.canvasWidth,this.canvasHeight)}
129 | }
130 | loadImg(){
131 | return new Promise((rs,rj)=>{
132 | this.image.onload=rs
133 | this.image.onerror=rj
134 | })
135 | }
136 | render(){
137 | let { height,width}=this.props
138 | return
139 | }
140 | }
141 | Sprite.propTypes = {
142 | width:PropTypes.number.isRequired,
143 | height:PropTypes.number.isRequired,
144 | speed:PropTypes.number,
145 | spriteImg:PropTypes.string.isRequired,
146 | spriteConf:PropTypes.node.isRequired
147 | };
148 | export default Sprite
--------------------------------------------------------------------------------
/demo/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | console.log(process.env.NODE_ENV)
25 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
26 | // The URL constructor is available in all browsers that support SW.
27 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
28 | if (publicUrl.origin !== window.location.origin) {
29 | // Our service worker won't work if PUBLIC_URL is on a different origin
30 | // from what our page is served on. This might happen if a CDN is used to
31 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
32 | return;
33 | }
34 |
35 | window.addEventListener('load', () => {
36 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
37 |
38 | if (isLocalhost) {
39 | // This is running on localhost. Let's check if a service worker still exists or not.
40 | checkValidServiceWorker(swUrl, config);
41 |
42 | // Add some additional logging to localhost, pointing developers to the
43 | // service worker/PWA documentation.
44 | navigator.serviceWorker.ready.then(() => {
45 | console.log(
46 | 'This web app is being served cache-first by a service ' +
47 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
48 | );
49 | });
50 | } else {
51 | // Is not localhost. Just register service worker
52 | registerValidSW(swUrl, config);
53 | }
54 | });
55 | }
56 | }
57 |
58 | function registerValidSW(swUrl, config) {
59 | navigator.serviceWorker
60 | .register(swUrl)
61 | .then(registration => {
62 | registration.onupdatefound = () => {
63 | const installingWorker = registration.installing;
64 | if (installingWorker == null) {
65 | return;
66 | }
67 | installingWorker.onstatechange = () => {
68 | if (installingWorker.state === 'installed') {
69 | if (navigator.serviceWorker.controller) {
70 | // At this point, the updated precached content has been fetched,
71 | // but the previous service worker will still serve the older
72 | // content until all client tabs are closed.
73 | console.log(
74 | 'New content is available and will be used when all ' +
75 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
76 | );
77 |
78 | // Execute callback
79 | if (config && config.onUpdate) {
80 | config.onUpdate(registration);
81 | }
82 | } else {
83 | // At this point, everything has been precached.
84 | // It's the perfect time to display a
85 | // "Content is cached for offline use." message.
86 | console.log('Content is cached for offline use.');
87 |
88 | // Execute callback
89 | if (config && config.onSuccess) {
90 | config.onSuccess(registration);
91 | }
92 | }
93 | }
94 | };
95 | };
96 | })
97 | .catch(error => {
98 | console.error('Error during service worker registration:', error);
99 | });
100 | }
101 |
102 | function checkValidServiceWorker(swUrl, config) {
103 | // Check if the service worker can be found. If it can't reload the page.
104 | fetch(swUrl)
105 | .then(response => {
106 | // Ensure service worker exists, and that we really are getting a JS file.
107 | const contentType = response.headers.get('content-type');
108 | if (
109 | response.status === 404 ||
110 | (contentType != null && contentType.indexOf('javascript') === -1)
111 | ) {
112 | // No service worker found. Probably a different app. Reload the page.
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister().then(() => {
115 | window.location.reload();
116 | });
117 | });
118 | } else {
119 | // Service worker found. Proceed as normal.
120 | registerValidSW(swUrl, config);
121 | }
122 | })
123 | .catch(() => {
124 | console.log(
125 | 'No internet connection found. App is running in offline mode.'
126 | );
127 | });
128 | }
129 |
130 | export function unregister() {
131 | if ('serviceWorker' in navigator) {
132 | navigator.serviceWorker.ready.then(registration => {
133 | registration.unregister();
134 | });
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nana's Swiper
2 |
3 | You can install it by npm:
4 |
5 | ```
6 | npm install --save nanaswiper
7 | ```
8 |
9 | you can import it use ES6 `import Swiper,{SwiperSlider} from 'nanaswiper'`。
10 |
11 | Or CommonJS
12 |
13 | ```
14 | const Swiper=require('nanaswiper').default
15 | const SwiperSlider=require('nanaswiper').SwiperSlider`
16 | ```
17 |
18 |
19 | Major change:
20 |
21 | Change how to set slider, less configuration and makes it like react-router's style
22 |
23 | It's a slider that only supports horizonal slide. Just for now,I'll develop vertical slide in the next version.
24 |
25 | The reason why develop such a swiper,just because a project need sliders with sprit and react. And it's hard to find a 3rd plugin which support for slider,react and canvas. It's easy to find a lot of plugins that support canvas and react or react and slider. That's why I develop such a plugin.
26 |
27 | You can check my demo on [https://www.cherryvenus.com/slider/](https://www.cherryvenus.com/slider/)
28 |
29 | If you want to play with this project, you can clone the repositor and then use following directions to start the project.
30 |
31 | build demo
32 |
33 | `npx webpack --config webpack.config.demo`
34 |
35 | start a webpack server
36 | `npm start`
37 |
38 | This Swiper's config:
39 |
40 | |params|function|default|required|
41 | | ------------- |:-------------:| -----:| -----:|
42 | |children|sliders inside swiper is a must|null|yes||sensitive|how sensitive it is when move the swiper to next or prev slider|0.5|no|
43 | |initMovex|calculator initial slider position|0|no|
44 | |isLoop|whether loop. One slider or freemode does not support loop|false|no|
45 | |width|width|window.innerWidth|no|
46 | |height|height|300|no|
47 | |isFreeMode|is Free Mode and not support loop mode|false|no|
48 | |initSliderIndex|inital slider,range from 0 to slider's number minus 1|0|no|
49 | |slideType|`flatCoverFlow` effect,maybe remove after|""|no|
50 |
51 | ```simple usage
52 | import Swiper,{SwiperSlider} from 'nanaswiper'
53 |
54 |
59 | (
60 | slider-ahahah
61 |
)}/>
62 | (
63 | slider-gasdffds
64 |
)}/>
65 | (
66 | slider-werqwerq
67 |
)}/>
68 |
69 |
70 | ```
71 |
72 | Here follow react-router's style, so you can design your slider by `render`
73 |
74 | ```
75 | (
76 | slider-ahahah
77 |
)}/>
78 | ```
79 |
80 | here `props` params passed to `render` has following important function:
81 |
82 | |params|usage|
83 | |:--:|:--:|
84 | |isMoving|whether the swiper is moving,touchmove -1 for left and 1 for right|
85 | |isActive|whether it's current slider|
86 |
87 | And if you want an array loop, the following example is for you:
88 |
89 | ```
90 | const data=[
91 | {
92 | text:"text1"
93 | },
94 | {
95 | text:"text2"
96 | },
97 | {
98 | text:"text3"
99 | }
100 | ]
101 | //...
102 |
103 | {data.map((d,index)=>{
104 | return (
105 | slider-{d.text}
106 |
)}/>
107 | })}
108 |
109 | //...
110 |
111 | ```
112 |
113 | Next part is how to use Sprite with Swiper
114 |
115 | First, define a sprite template.
116 |
117 | ```SliderSpriteTemplate.js
118 | import React,{Fragment,Component} from 'react'
119 | import Swiper,{SwiperSlider} from 'nanaswiper'
120 |
121 | class SliderSpriteTemplate extends Component{
122 | render(){
123 | return (
124 |
125 | {this.props.isActive?:""}
126 |
127 | )
128 | }
129 | }
130 | export {SliderSpriteTemplate}
131 | ```
132 | Next set the sprite's params:
133 |
134 | ```
135 | const data=[
136 | {
137 | width:window.innerWidth,//sprite width must
138 | height:300,//sprite height must
139 | spriteImg:img1,//must
140 | spriteConf:[3,1,222,350],//sprite'position must
141 | speed:6,//sprite'speed optional
142 | tpl:SliderSpriteTemplate//you can set any template you like, it's a must for sprite
143 | },
144 | //...
145 | ]
146 | export function SpirteSlider(){
147 | return (
148 |
153 | {data.map((d,index)=>{
154 | return }
157 | />
158 | })}
159 |
160 |
)
161 | ```
162 |
163 | Sprite support params:
164 |
165 | |params|function|default|required|
166 | | ------------- |:-------------:| -----:| -----:|
167 | |width|canvas'width|""|yes|
168 | |height|canvas'height|""|yes|
169 | |data.spriteImg|image to animate|""|yes|
170 | |data.spriteConf|sprite's config(row num,col num,fpsWidth:width,fpsHeight:height)|{}|yes|
171 | |data.speed|speed,default 60fps, if set 2 it's 30fps|1|no|
172 |
173 | Browser version, you can get it from ` npm run-script broswer`, and then `dist/nanaSwiper.js` is there for you.
174 |
175 | How to use? You can directly use it in browser.
176 |
177 | ```
178 |
179 |
180 |
181 |
182 |
216 | ```
217 |
218 |
219 |
220 | About test, here simulate the touch action, and only test the whether slider is right.
221 |
222 | ```
223 | npm test
224 | ```
225 | .
226 |
227 | 感觉自己做的好复杂,溜了溜了
228 |
229 | 下一版计划:
230 | * 修复freemode下,首尾滑动不流畅
231 | * 序列帧支持导入json配置
232 | * 来个垂直滑动
233 |
--------------------------------------------------------------------------------
/src/Swiper.js:
--------------------------------------------------------------------------------
1 | import React,{Component,Fragment} from 'react'
2 | import SwiperCSS from './styles/Swiper.module.scss'
3 | import SwiperContext from "./SwiperContext";
4 | import {checkTouch} from './utils'
5 | import PropTypes from "prop-types";
6 |
7 | const supportTouch=checkTouch()
8 |
9 | class Swiper extends Component{
10 | //控制手势
11 | startPoint=0//开始滑动的点touchstart
12 | movePoint=0//移动的点touchmove
13 | endPoint=0//结束的点touchend
14 | moveingStart=false//判断是否开始touchstart
15 | isMoving=0//起始滑动为0
16 | swiperWidth=0//幻灯片的宽度
17 | swiperHeight=0//幻灯片的高度
18 | initMovex=0//幻灯片开始的位置
19 | currentSliderIndex=0//初始幻灯片
20 | sensitive=0.5//灵明度
21 | isFreeMode=false
22 | isLoop=false
23 | slideType=""
24 | sliders=[]
25 | bounds={}
26 | boundsMove={}
27 | constructor(props){
28 | super(props)
29 | this.slideType=props.slideType||this.slideType;
30 | this.isFreeMode=props.isFreeMode||this.isFreeMode;
31 | this.sensitive=props.sensitive||this.sensitive;
32 | this.sliders=props.children
33 | this.isLoop=(this.sliders.length>1?props.isLoop:false)||this.isLoop
34 | this.currentSliderIndex=props.initSliderIndex||0
35 | this.initMovex=props.initMovex||this.initMovex;
36 | this.initMovex=(this.initMovex<1?this.initMovex*this.props.width:this.initMovex)
37 | this.swiperWidth=this.props.width||window.innerWidth
38 | this.swiperHeight=this.props.height||300
39 | this.init()
40 | this.state={
41 | moveX:this.initMovex-this.currentSliderIndex*this.swiperWidth,
42 | isMoving:0,//判断是否正在移动touchmove -1 左 1 右
43 | isTransition:false,
44 | isLoaded:true
45 | }
46 |
47 | }
48 |
49 | init(){
50 | if(this.isLoop){
51 | this.sliders=[
52 | React.cloneElement(this.sliders.slice(-1)[0],{key:"SwiperFirst",index:0}),
53 | ...this.sliders.map((s,i)=>{
54 | return React.cloneElement(s,
55 | {key:"Swiper"+(i+1),index:i+1})
56 | }),
57 | React.cloneElement(this.sliders.slice(0,1)[0],{key:"SwiperLast",index:this.sliders.length+1})]
58 | this.bounds={
59 | min:1,
60 | max:this.sliders.length-1
61 | }
62 | this.currentSliderIndex++
63 | }else{
64 | this.sliders=this.sliders.map((s,i)=>{
65 | return React.cloneElement(s,
66 | {key:"Swiper"+i,index:i})
67 | })
68 | this.bounds={
69 | min:0,
70 | max:this.sliders.length-1
71 | }
72 | }
73 | this.boundsMove={
74 | min:-this.bounds.max*this.swiperWidth-this.initMovex,
75 | max:this.initMovex
76 | }
77 |
78 | }
79 | componentDidMount(){
80 | }
81 | getPointX(e){
82 | return supportTouch?e.touches[0].clientX:e.clientX
83 | }
84 | touchstart(e){
85 | if((e.type==="mousedown"&&!supportTouch)||(e.type==="touchstart"&&supportTouch)){
86 | //e.preventDefault();
87 | this.startPoint=this.getPointX(e)
88 | this.movePoint=this.startPoint
89 | this.endPoint=this.startPoint
90 | this.moveingStart=true;
91 | }
92 | }
93 | touchmove(e){
94 | if(this.moveingStart&&((e.type==="mousemove"&&!supportTouch)||(e.type==="touchmove"&&supportTouch))){
95 | e.preventDefault();
96 | this.endPoint=this.getPointX(e)
97 | let changePos=this.endPoint-this.movePoint+this.state.moveX;
98 | //console.log(this.endPoint,this.movePoint,this.state.moveX)
99 | this.movePoint=this.endPoint
100 | this.isMoving=this.movePoint-this.startPoint>0?-1:1
101 | //mark这边触发渲染
102 | //console.log("touchmove",changePos)
103 | this.setState({
104 | isTransition:false,
105 | moveX:changePos
106 | })
107 | }
108 | }
109 | touchend(e){
110 | //console.log("touchend",e.type)
111 | if(((e.type==="mouseleave"||e.type==="mouseup")&&!supportTouch)||(e.type==="touchend"&&supportTouch)){
112 | this.moveingStart=false;
113 | if(this.startPoint!==this.endPoint){
114 | e.preventDefault()
115 | this.slideTo(this.calculateSlider())
116 | }else{
117 | this.isMoving=0
118 | }
119 | }
120 | }
121 | calculateSlider(){
122 | let {min,max}=this.bounds
123 | let MoveSlider=Math.abs(this.state.moveX/this.swiperWidth)
124 | let slideIndex=this.currentSliderIndex
125 | //计算当前slider和滑动的差值
126 | let difference=MoveSlider-slideIndex
127 | if(this.isMoving===1&&difference>this.sensitive){//向右边,向左滑动
128 | slideIndex=Math.ceil(MoveSlider)
129 | slideIndex=slideIndex-this.currentSliderIndex>1?slideIndex-1:slideIndex
130 | }else if(this.isMoving===-1&&difference<-this.sensitive){//向左边
131 | slideIndex=Math.floor(MoveSlider)
132 | //fix灵敏度不高的情况下 滑动超过1slider
133 | slideIndex=slideIndex-this.currentSliderIndex<-1?slideIndex+1:slideIndex
134 | }
135 | slideIndex=slideIndexmax?max:slideIndex
136 | return slideIndex
137 | }
138 | fixLoop(){
139 | if(!this.isLoop) return
140 | let {min,max}=this.bounds
141 | if(this.currentSliderIndex=max){
149 | this.currentSliderIndex=1
150 | this.setState({
151 | isTransition:false,
152 | moveX:-this.swiperWidth+this.initMovex
153 | })
154 | }
155 | }
156 | slideTo(slideIndex,force){
157 | //若果没有滑动,活着正在滑动,就🔙
158 | if(this.moveingStart||this.isMoving===0) return
159 | let {min,max}=this.boundsMove
160 | this.currentSliderIndex=slideIndex
161 | if(force||!this.isFreeMode||(this.state.moveXmax)){
162 | this.setState({
163 | isTransition:true,
164 | moveX:-slideIndex*this.swiperWidth+this.initMovex
165 | })
166 | }else{
167 | this.currentSliderIndex=slideIndex
168 | }
169 | }
170 | clickSlideTo(index){
171 | this.isMoving=1
172 | this.slideTo(index,true)
173 | }
174 | transitionend(){
175 | this.isMoving=0
176 | this.setState({
177 | isTransition:false
178 | })
179 | this.fixLoop()
180 | }
181 | render(){
182 | if(this.sliders.length<1||!this.state.isLoaded){
183 | return
184 | }
185 | let {moveX,isTransition}=this.state,
186 | transitionDuration=isTransition?"300ms":"0ms",
187 | transformX=`translate3d(${moveX}px, 0px, 0px)`
188 | const props = {
189 | ...this.state,
190 | isMoving:this.isMoving,
191 | currentSliderIndex:this.currentSliderIndex
192 | };
193 | return (
194 | {/* 滑动主体 */}
195 | {this.touchstart(e)}}
198 | onTouchMove={(e)=>{this.touchmove(e)}}
199 | onTouchEnd={(e)=>{this.touchend(e)}}
200 | onMouseDown={(e)=>{this.touchstart(e)}}
201 | onMouseMove={(e)=>{this.touchmove(e)}}
202 | onMouseUp={(e)=>{this.touchend(e)}}
203 | onMouseLeave={(e)=>{this.touchend(e)}}
204 | onTransitionEnd={(e)=>{this.transitionend()}}
205 | style={{
206 | WebkitTransitionDuration:transitionDuration,
207 | transitionDuration:transitionDuration,
208 | WebkitTransform:transformX,
209 | transform:transformX,
210 | height:this.swiperHeight
211 | }}
212 | >
213 |
217 |
218 | {/* 导航点 */}
219 |
220 | {this.sliders.map((color,index)=>
221 |
{this.isLoop&&(index===0||index===this.bounds.max)?"":
222 | {this.clickSlideTo(index)}}
224 | >
}
225 |
226 | )}
227 |
228 |
229 | )
230 | }
231 | }
232 | Swiper.propTypes = {
233 | children: PropTypes.node.isRequired,
234 | };
235 |
236 | export default Swiper
--------------------------------------------------------------------------------
/dist/nanaSwiper.umd.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react"),require("prop-types")):"function"==typeof define&&define.amd?define("Swiper",["react","prop-types"],e):"object"==typeof exports?exports.Swiper=e(require("react"),require("prop-types")):t.Swiper=e(t.React,t["prop-types"])}(window,function(t,e){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=105)}([function(e,n){e.exports=t},function(t,e,n){var r=n(28)("wks"),o=n(18),i=n(2).Symbol,s="function"==typeof i;(t.exports=function(t){return r[t]||(r[t]=s&&i[t]||(s?i:o)("Symbol."+t))}).store=r},function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(t,e,n){var r=n(2),o=n(10),i=n(13),s=n(14),c=n(15),u=function(t,e,n){var a,f,l,p,h=t&u.F,v=t&u.G,d=t&u.S,y=t&u.P,m=t&u.B,b=v?r:d?r[e]||(r[e]={}):(r[e]||{}).prototype,g=v?o:o[e]||(o[e]={}),x=g.prototype||(g.prototype={});for(a in v&&(n=e),n)l=((f=!h&&b&&void 0!==b[a])?b:n)[a],p=m&&f?c(l,r):y&&"function"==typeof l?c(Function.call,l):l,b&&s(b,a,l,t&u.U),g[a]!=l&&i(g,a,p),y&&x[a]!=l&&(x[a]=l)};r.core=o,u.F=1,u.G=2,u.S=4,u.P=8,u.B=16,u.W=32,u.U=64,u.R=128,t.exports=u},function(t,e,n){var r=n(9);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,n){t.exports=e},function(t,e,n){var r=n(4),o=n(48),i=n(30),s=Object.defineProperty;e.f=n(7)?Object.defineProperty:function(t,e,n){if(r(t),e=i(e,!0),r(n),o)try{return s(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},function(t,e,n){t.exports=!n(11)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e,n){var r=n(101);"string"==typeof r&&(r=[[t.i,r,""]]);var o={hmr:!0,transform:void 0,insertInto:void 0};n(103)(r,o);r.locals&&(t.exports=r.locals)},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e){var n=t.exports={version:"2.6.1"};"number"==typeof __e&&(__e=n)},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},function(t,e,n){var r=n(6),o=n(19);t.exports=n(7)?function(t,e,n){return r.f(t,e,o(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e,n){var r=n(2),o=n(13),i=n(12),s=n(18)("src"),c=Function.toString,u=(""+c).split("toString");n(10).inspectSource=function(t){return c.call(t)},(t.exports=function(t,e,n,c){var a="function"==typeof n;a&&(i(n,"name")||o(n,"name",e)),t[e]!==n&&(a&&(i(n,s)||o(n,s,t[e]?""+t[e]:u.join(String(e)))),t===r?t[e]=n:c?t[e]?t[e]=n:o(t,e,n):(delete t[e],o(t,e,n)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[s]||c.call(this)})},function(t,e,n){var r=n(23);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}}},function(t,e,n){var r=n(50),o=n(32);t.exports=function(t){return r(o(t))}},function(t,e){t.exports=!1},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e,n){var r=n(49),o=n(35);t.exports=Object.keys||function(t){return r(t,o)}},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},function(t,e){t.exports={}},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,e,n){var r=n(6).f,o=n(12),i=n(1)("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,i)&&r(t,i,{configurable:!0,value:e})}},function(t,e,n){var r=n(33),o=Math.min;t.exports=function(t){return t>0?o(r(t),9007199254740991):0}},function(t,e,n){var r=n(32);t.exports=function(t){return Object(r(t))}},function(t,e,n){n(46)("asyncIterator")},function(t,e,n){var r=n(10),o=n(2),i=o["__core-js_shared__"]||(o["__core-js_shared__"]={});(t.exports=function(t,e){return i[t]||(i[t]=void 0!==e?e:{})})("versions",[]).push({version:r.version,mode:n(17)?"pure":"global",copyright:"© 2018 Denis Pushkarev (zloirock.ru)"})},function(t,e,n){var r=n(9),o=n(2).document,i=r(o)&&r(o.createElement);t.exports=function(t){return i?o.createElement(t):{}}},function(t,e,n){var r=n(9);t.exports=function(t,e){if(!r(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!r(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},function(t,e,n){"use strict";var r=n(2),o=n(12),i=n(7),s=n(3),c=n(14),u=n(68).KEY,a=n(11),f=n(28),l=n(24),p=n(18),h=n(1),v=n(47),d=n(46),y=n(69),m=n(37),b=n(4),g=n(9),x=n(16),w=n(30),S=n(19),O=n(38),_=n(73),j=n(54),P=n(6),k=n(20),E=j.f,M=P.f,T=_.f,A=r.Symbol,I=r.JSON,L=I&&I.stringify,R=h("_hidden"),C=h("toPrimitive"),F={}.propertyIsEnumerable,N=f("symbol-registry"),D=f("symbols"),U=f("op-symbols"),H=Object.prototype,W="function"==typeof A,q=r.QObject,X=!q||!q.prototype||!q.prototype.findChild,B=i&&a(function(){return 7!=O(M({},"a",{get:function(){return M(this,"a",{value:7}).a}})).a})?function(t,e,n){var r=E(H,e);r&&delete H[e],M(t,e,n),r&&t!==H&&M(H,e,r)}:M,z=function(t){var e=D[t]=O(A.prototype);return e._k=t,e},V=W&&"symbol"==typeof A.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof A},G=function(t,e,n){return t===H&&G(U,e,n),b(t),e=w(e,!0),b(n),o(D,e)?(n.enumerable?(o(t,R)&&t[R][e]&&(t[R][e]=!1),n=O(n,{enumerable:S(0,!1)})):(o(t,R)||M(t,R,S(1,{})),t[R][e]=!0),B(t,e,n)):M(t,e,n)},Q=function(t,e){b(t);for(var n,r=y(e=x(e)),o=0,i=r.length;i>o;)G(t,n=r[o++],e[n]);return t},J=function(t){var e=F.call(this,t=w(t,!0));return!(this===H&&o(D,t)&&!o(U,t))&&(!(e||!o(this,t)||!o(D,t)||o(this,R)&&this[R][t])||e)},Y=function(t,e){if(t=x(t),e=w(e,!0),t!==H||!o(D,e)||o(U,e)){var n=E(t,e);return!n||!o(D,e)||o(t,R)&&t[R][e]||(n.enumerable=!0),n}},Z=function(t){for(var e,n=T(x(t)),r=[],i=0;n.length>i;)o(D,e=n[i++])||e==R||e==u||r.push(e);return r},K=function(t){for(var e,n=t===H,r=T(n?U:x(t)),i=[],s=0;r.length>s;)!o(D,e=r[s++])||n&&!o(H,e)||i.push(D[e]);return i};W||(c((A=function(){if(this instanceof A)throw TypeError("Symbol is not a constructor!");var t=p(arguments.length>0?arguments[0]:void 0),e=function(n){this===H&&e.call(U,n),o(this,R)&&o(this[R],t)&&(this[R][t]=!1),B(this,t,S(1,n))};return i&&X&&B(H,t,{configurable:!0,set:e}),z(t)}).prototype,"toString",function(){return this._k}),j.f=Y,P.f=G,n(53).f=_.f=Z,n(36).f=J,n(51).f=K,i&&!n(17)&&c(H,"propertyIsEnumerable",J,!0),v.f=function(t){return z(h(t))}),s(s.G+s.W+s.F*!W,{Symbol:A});for(var $="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),tt=0;$.length>tt;)h($[tt++]);for(var et=k(h.store),nt=0;et.length>nt;)d(et[nt++]);s(s.S+s.F*!W,"Symbol",{for:function(t){return o(N,t+="")?N[t]:N[t]=A(t)},keyFor:function(t){if(!V(t))throw TypeError(t+" is not a symbol!");for(var e in N)if(N[e]===t)return e},useSetter:function(){X=!0},useSimple:function(){X=!1}}),s(s.S+s.F*!W,"Object",{create:function(t,e){return void 0===e?O(t):Q(O(t),e)},defineProperty:G,defineProperties:Q,getOwnPropertyDescriptor:Y,getOwnPropertyNames:Z,getOwnPropertySymbols:K}),I&&s(s.S+s.F*(!W||a(function(){var t=A();return"[null]"!=L([t])||"{}"!=L({a:t})||"{}"!=L(Object(t))})),"JSON",{stringify:function(t){for(var e,n,r=[t],o=1;arguments.length>o;)r.push(arguments[o++]);if(n=e=r[1],(g(e)||void 0!==t)&&!V(t))return m(e)||(e=function(t,e){if("function"==typeof n&&(e=n.call(this,t,e)),!V(e))return e}),r[1]=e,L.apply(I,r)}}),A.prototype[C]||n(13)(A.prototype,C,A.prototype.valueOf),l(A,"Symbol"),l(Math,"Math",!0),l(r.JSON,"JSON",!0)},function(t,e){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},function(t,e,n){var r=n(28)("keys"),o=n(18);t.exports=function(t){return r[t]||(r[t]=o(t))}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e){e.f={}.propertyIsEnumerable},function(t,e,n){var r=n(21);t.exports=Array.isArray||function(t){return"Array"==r(t)}},function(t,e,n){var r=n(4),o=n(72),i=n(35),s=n(34)("IE_PROTO"),c=function(){},u=function(){var t,e=n(29)("iframe"),r=i.length;for(e.style.display="none",n(52).appendChild(e),e.src="javascript:",(t=e.contentWindow.document).open(),t.write("