├── .eslintrc.js ├── .github ├── contributing.md ├── issue_template.md └── workflows │ └── nodejs.yml ├── .gitignore ├── .nvmrc ├── README.md ├── bin ├── .eslintrc.js ├── bundle-css.js ├── bundle-js.js ├── lint-json.js └── version.js ├── bower.json ├── css └── flickity.css ├── dist ├── flickity.css ├── flickity.min.css ├── flickity.pkgd.js └── flickity.pkgd.min.js ├── js ├── add-remove-cell.js ├── animate.js ├── cell.js ├── core.js ├── drag.js ├── imagesloaded.js ├── index.js ├── lazyload.js ├── page-dots.js ├── player.js ├── prev-next-button.js └── slide.js ├── package-lock.json ├── package.json ├── sandbox ├── adaptive-height.html ├── add-remove.html ├── ajax.html ├── basic.html ├── freescroll.html ├── group-cells.html ├── jquery.html ├── js │ ├── add-remove.js │ ├── basic.js │ ├── jquery.js │ ├── scroll-event.js │ ├── tricky-drag.js │ ├── v2-sizzle.js │ └── wrap-around.js ├── lazyload.html ├── media.html ├── right-to-left.html ├── sandbox.css ├── scroll-event.html ├── single.html ├── styles.html ├── tricky-drag.html ├── v2-sizzle.html └── wrap-around.html ├── stylelint.config.js └── test ├── drag.html ├── index.html ├── test.css └── unit ├── adaptive-height.js ├── add-remove-cells.js ├── auto-play.js ├── cell-selector.js ├── change.js ├── contain.js ├── destroy.js ├── drag.js ├── empty.js ├── get-parent-cell.js ├── get-wrap-cells.js ├── group-cells.js ├── imagesloaded.js ├── init.js ├── initial-index.js ├── lazyload-srcset.js ├── lazyload.js ├── page-dots.js ├── position-cells.js ├── prev-next-buttons.js ├── resize.js ├── select-cell.js ├── watch.js └── wrap-around-fill.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | plugins: [ 'metafizzy' ], 5 | extends: 'plugin:metafizzy/browser', 6 | env: { 7 | browser: true, 8 | commonjs: true, 9 | }, 10 | parserOptions: { 11 | ecmaVersion: 2018, 12 | }, 13 | globals: { 14 | Flickity: 'readonly', 15 | QUnit: 'readonly', 16 | }, 17 | rules: { 18 | 'prefer-object-spread': 'error', 19 | }, 20 | ignorePatterns: [ 'bower_components' ], 21 | }; 22 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | ## Submitting issues 2 | 3 | ### Reduced test case required 4 | 5 | All bug reports and problem issues require a [**reduced test case**](https://css-tricks.com/reduced-test-cases/). Create one by forking any one of the [CodePen demos](https://codepen.io/desandro/pens/tags/?grid_type=list&selected_tag=flickity-docs&sort_order=asc) from [the docs](https://flickity.metafizzy.co). 6 | 7 | **CodePens** 8 | 9 | + [Basic](https://codepen.io/desandro/pen/azqbop) 10 | + [imagesLoaded](https://codepen.io/desandro/pen/MYQWEe) 11 | + [lazyLoad](https://codepen.io/desandro/pen/vOeGzL) 12 | + [autoPlay](https://codepen.io/desandro/pen/RNQwaB) 13 | 14 | **Test cases** 15 | 16 | + A reduced test case clearly demonstrates the bug or issue. 17 | + It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug. 18 | + A link to your production site is **not** a reduced test case. 19 | 20 | Providing a reduced test case is the best way to get your issue addressed. They help you point out the problem. They help me verify and debug the problem. They help others understand the problem. Without a reduced test case, your issue may be closed. 21 | 22 | ## Pull requests 23 | 24 | Contributions are welcome! 25 | 26 | + **For typos and one-line fixes,** send those right in. 27 | + **For larger features,** open an issue before starting any significant work. Let's discuss to see how your feature fits within Flickity's vision. 28 | + **Follow the code style.** Spaces in brackets, semicolons, trailing commas. 29 | + **Do not edit `dist/` files.** Make your edits to source files in `js/` and `css/`. 30 | + **Do not run `gulp` to update `dist/` files.** I'll take care of this when I create a new release. 31 | 32 | Your code will be used as part of a commercial product if merged. By submitting a Pull Request, you are giving your consent for your code to be integrated into Flickity as part of a commercial product. 33 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Test case:** https://codepen.io/desandro/pen/azqbop 4 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node-version: [16.x] 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - run: npm run lint 26 | env: 27 | CI: true 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | /**/browserify/bundle.js 4 | notes.md 5 | sandbox/yoshi-parallax 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flickity 2 | 3 | _Touch, responsive, flickable carousels_ 4 | 5 | See [flickity.metafizzy.co](https://flickity.metafizzy.co) for complete docs and demos. 6 | 7 | ## Install 8 | 9 | ### Download 10 | 11 | + CSS: 12 | - [flickity.min.css](https://unpkg.com/flickity@2/dist/flickity.min.css) minified, or 13 | - [flickity.css](https://unpkg.com/flickity@2/dist/flickity.css) un-minified 14 | + JavaScript: 15 | - [flickity.pkgd.min.js](https://unpkg.com/flickity@2/dist/flickity.pkgd.min.js) minified, or 16 | - [flickity.pkgd.js](https://unpkg.com/flickity@2/dist/flickity.pkgd.js) un-minified 17 | 18 | ### CDN 19 | 20 | Link directly to Flickity files on [unpkg](https://unpkg.com). 21 | 22 | ``` html 23 | 24 | ``` 25 | 26 | ``` html 27 | 28 | ``` 29 | 30 | ### Package managers 31 | 32 | Bower: `bower install flickity --save` 33 | 34 | npm: `npm install flickity --save` 35 | 36 | ## License 37 | 38 | ### Commercial license 39 | 40 | If you want to use Flickity to develop commercial sites, themes, projects, and applications, the Commercial license is the appropriate license. With this option, your source code is kept proprietary. Purchase a Flickity Commercial License at [flickity.metafizzy.co](https://flickity.metafizzy.co/#commercial-license) 41 | 42 | ### Open source license 43 | 44 | If you are creating an open source application under a license compatible with the [GNU GPL license v3](https://www.gnu.org/licenses/gpl-3.0.html), you may use Flickity under the terms of the GPLv3. 45 | 46 | [Read more about Flickity's license](https://flickity.metafizzy.co/license.html). 47 | 48 | ## Usage 49 | 50 | Flickity works with a container element and a set of child cell elements 51 | 52 | ``` html 53 | 59 | ``` 60 | 61 | ### Options 62 | 63 | ``` js 64 | var flky = new Flickity( '.gallery', { 65 | // options, defaults listed 66 | 67 | accessibility: true, 68 | // enable keyboard navigation, pressing left & right keys 69 | 70 | adaptiveHeight: false, 71 | // set carousel height to the selected slide 72 | 73 | autoPlay: false, 74 | // advances to the next cell 75 | // if true, default is 3 seconds 76 | // or set time between advances in milliseconds 77 | // i.e. `autoPlay: 1000` will advance every 1 second 78 | 79 | cellAlign: 'center', 80 | // alignment of cells, 'center', 'left', or 'right' 81 | // or a decimal 0-1, 0 is beginning (left) of container, 1 is end (right) 82 | 83 | cellSelector: undefined, 84 | // specify selector for cell elements 85 | 86 | contain: false, 87 | // will contain cells to container 88 | // so no excess scroll at beginning or end 89 | // has no effect if wrapAround is enabled 90 | 91 | draggable: '>1', 92 | // enables dragging & flicking 93 | // if at least 2 cells 94 | 95 | dragThreshold: 3, 96 | // number of pixels a user must scroll horizontally to start dragging 97 | // increase to allow more room for vertical scroll for touch devices 98 | 99 | freeScroll: false, 100 | // enables content to be freely scrolled and flicked 101 | // without aligning cells 102 | 103 | friction: 0.2, 104 | // smaller number = easier to flick farther 105 | 106 | groupCells: false, 107 | // group cells together in slides 108 | 109 | initialIndex: 0, 110 | // zero-based index of the initial selected cell 111 | 112 | lazyLoad: true, 113 | // enable lazy-loading images 114 | // set img data-flickity-lazyload="src.jpg" 115 | // set to number to load images adjacent cells 116 | 117 | percentPosition: true, 118 | // sets positioning in percent values, rather than pixels 119 | // Enable if items have percent widths 120 | // Disable if items have pixel widths, like images 121 | 122 | prevNextButtons: true, 123 | // creates and enables buttons to click to previous & next cells 124 | 125 | pageDots: true, 126 | // create and enable page dots 127 | 128 | resize: true, 129 | // listens to window resize events to adjust size & positions 130 | 131 | rightToLeft: false, 132 | // enables right-to-left layout 133 | 134 | setGallerySize: true, 135 | // sets the height of gallery 136 | // disable if gallery already has height set with CSS 137 | 138 | watchCSS: false, 139 | // watches the content of :after of the element 140 | // activates if #element:after { content: 'flickity' } 141 | 142 | wrapAround: false 143 | // at end of cells, wraps-around to first for infinite scrolling 144 | 145 | }); 146 | ``` 147 | 148 | --- 149 | 150 | By [Metafizzy 🌈🐻](https://metafizzy.co) 151 | -------------------------------------------------------------------------------- /bin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 'metafizzy' ], 3 | extends: 'plugin:metafizzy/node', 4 | }; 5 | -------------------------------------------------------------------------------- /bin/bundle-css.js: -------------------------------------------------------------------------------- 1 | const CleanCss = require('clean-css'); 2 | const fs = require('fs'); 3 | 4 | let srcCss = fs.readFileSync( 'css/flickity.css', 'utf8' ); 5 | let minifiedCss = new CleanCss().minify( srcCss ).styles.replace( '*/', '*/\n' ); 6 | fs.writeFileSync( 'dist/flickity.min.css', minifiedCss ); 7 | -------------------------------------------------------------------------------- /bin/bundle-js.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { execSync } = require('child_process'); 3 | const { minify } = require('terser'); 4 | 5 | const indexPath = 'js/index.js'; 6 | const distPath = 'dist/flickity.pkgd.js'; 7 | const distMinPath = 'dist/flickity.pkgd.min.js'; 8 | 9 | let indexContent = fs.readFileSync( `./${indexPath}`, 'utf8' ); 10 | // get file paths from index.js 11 | let jsPaths = indexContent.match( /require\('([.\-/\w]+)'\)/gi ) 12 | .map( ( path ) => path.replace( "require('.", 'js' ).replace( "')", '.js' ) ); 13 | 14 | let paths = [ 15 | 'node_modules/jquery-bridget/jquery-bridget.js', 16 | 'node_modules/ev-emitter/ev-emitter.js', 17 | 'node_modules/get-size/get-size.js', 18 | 'node_modules/fizzy-ui-utils/utils.js', 19 | 'node_modules/unidragger/unidragger.js', 20 | 'node_modules/imagesloaded/imagesloaded.js', 21 | 'js/cell.js', 22 | 'js/slide.js', 23 | 'js/animate.js', 24 | ...jsPaths, 25 | ]; 26 | 27 | // concatenate files 28 | execSync(`cat ${paths.join(' ')} > ${distPath}`); 29 | 30 | // add banner 31 | let banner = indexContent.split(' */')[0] + ' */\n\n'; 32 | banner = banner.replace( 'Flickity', 'Flickity PACKAGED' ); 33 | let distJsContent = fs.readFileSync( distPath, 'utf8' ); 34 | distJsContent = banner + distJsContent; 35 | fs.writeFileSync( distPath, distJsContent ); 36 | 37 | // minify 38 | ( async function() { 39 | let { code } = await minify( distJsContent, { mangle: true } ); 40 | fs.writeFileSync( distMinPath, code ); 41 | } )(); 42 | -------------------------------------------------------------------------------- /bin/lint-json.js: -------------------------------------------------------------------------------- 1 | require('../package.json'); 2 | require('../bower.json'); 3 | -------------------------------------------------------------------------------- /bin/version.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const version = require('../package.json').version; 3 | 4 | [ 'css/flickity.css', 'js/index.js' ].forEach( ( file ) => { 5 | let src = fs.readFileSync( file, 'utf8' ); 6 | src = src.replace( /Flickity v\d+\.\d+\.\d+/, `Flickity v${version}` ); 7 | fs.writeFileSync( file, src, 'utf8' ); 8 | } ); 9 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flickity", 3 | "description": "Touch, responsive, flickable carousels", 4 | "main": [ 5 | "js/index.js", 6 | "css/flickity.css" 7 | ], 8 | "dependencies": { 9 | "ev-emitter": "^2.1.2", 10 | "fizzy-ui-utils": "^3.0.0", 11 | "get-size": "^3.0.0", 12 | "unidragger": "^3.0.1" 13 | }, 14 | "devDependencies": { 15 | }, 16 | "keywords": [ 17 | "gallery", 18 | "carousel", 19 | "touch" 20 | ], 21 | "homepage": "https://flickity.metafizzy.co", 22 | "authors": [ 23 | "Metafizzy" 24 | ], 25 | "license": "GPL-3.0", 26 | "ignore": [ 27 | "**/.*", 28 | "node_modules", 29 | "bower_components", 30 | "test", 31 | "tests", 32 | "sandbox", 33 | "package.json", 34 | "gulpfile.js", 35 | "notes.md" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /css/flickity.css: -------------------------------------------------------------------------------- 1 | /*! Flickity v3.0.0 2 | https://flickity.metafizzy.co 3 | ---------------------------------------------- */ 4 | 5 | .flickity-enabled { 6 | position: relative; 7 | } 8 | 9 | .flickity-enabled:focus { outline: none; } 10 | 11 | .flickity-viewport { 12 | overflow: hidden; 13 | position: relative; 14 | height: 100%; 15 | touch-action: pan-y; 16 | } 17 | 18 | .flickity-slider { 19 | position: absolute; 20 | width: 100%; 21 | height: 100%; 22 | left: 0; 23 | } 24 | 25 | .flickity-rtl .flickity-slider { 26 | left: unset; 27 | right: 0; 28 | } 29 | 30 | /* draggable */ 31 | 32 | .flickity-enabled.is-draggable { 33 | -webkit-tap-highlight-color: transparent; 34 | user-select: none; 35 | } 36 | 37 | .flickity-enabled.is-draggable .flickity-viewport { 38 | cursor: move; 39 | cursor: grab; 40 | } 41 | 42 | .flickity-enabled.is-draggable .flickity-viewport.is-pointer-down { 43 | cursor: grabbing; 44 | } 45 | 46 | /* ---- flickity-cell ---- */ 47 | 48 | .flickity-cell { 49 | position: absolute; 50 | left: 0; 51 | } 52 | 53 | .flickity-rtl .flickity-cell { 54 | left: unset; 55 | right: 0; 56 | } 57 | 58 | /* ---- flickity-button ---- */ 59 | 60 | .flickity-button { 61 | position: absolute; 62 | background: hsla(0, 0%, 100%, 75%); 63 | border: none; 64 | color: hsl(0, 0%, 20%); 65 | } 66 | 67 | .flickity-button:hover { 68 | background: white; 69 | cursor: pointer; 70 | } 71 | 72 | .flickity-button:focus { 73 | outline: none; 74 | box-shadow: 0 0 0 5px #19F; 75 | } 76 | 77 | .flickity-button:active { 78 | color: #19F; 79 | } 80 | 81 | .flickity-button:disabled { 82 | opacity: 0.3; 83 | cursor: auto; 84 | /* prevent disabled button from capturing pointer up event. #716 */ 85 | pointer-events: none; 86 | } 87 | 88 | .flickity-button-icon { 89 | fill: currentcolor; 90 | } 91 | 92 | /* ---- previous/next buttons ---- */ 93 | 94 | .flickity-prev-next-button { 95 | top: 50%; 96 | width: 44px; 97 | height: 44px; 98 | z-index: 1; /* above viewport */ 99 | border-radius: 50%; 100 | /* vertically center */ 101 | transform: translateY(-50%); 102 | } 103 | 104 | .flickity-prev-next-button.previous { left: 10px; } 105 | .flickity-prev-next-button.next { right: 10px; } 106 | /* right to left */ 107 | .flickity-rtl .flickity-prev-next-button.previous { 108 | left: auto; 109 | right: 10px; 110 | } 111 | 112 | .flickity-rtl .flickity-prev-next-button.next { 113 | right: auto; 114 | left: 10px; 115 | } 116 | 117 | .flickity-prev-next-button .flickity-button-icon { 118 | position: absolute; 119 | left: 20%; 120 | top: 20%; 121 | width: 60%; 122 | height: 60%; 123 | } 124 | 125 | /* ---- page dots ---- */ 126 | 127 | .flickity-page-dots { 128 | position: absolute; 129 | width: 100%; 130 | bottom: -25px; 131 | z-index: 1; /* above viewport */ 132 | text-align: center; 133 | display: flex; 134 | justify-content: center; 135 | flex-wrap: wrap; 136 | } 137 | 138 | .flickity-rtl .flickity-page-dots { direction: rtl; } 139 | 140 | .flickity-page-dot { 141 | position: relative; 142 | display: block; 143 | width: 10px; 144 | height: 10px; 145 | padding: 0; 146 | margin: 0 8px; 147 | background: hsl(0, 0%, 20%, 25%); 148 | border-radius: 50%; 149 | cursor: pointer; 150 | appearance: none; 151 | border: none; 152 | text-indent: -9999px; 153 | overflow: hidden; 154 | } 155 | 156 | .flickity-rtl .flickity-page-dot { 157 | text-indent: 9999px; 158 | } 159 | 160 | .flickity-page-dot:hover { 161 | background: hsla(0, 0%, 20%, 75%); 162 | } 163 | 164 | .flickity-page-dot:active { 165 | background: #19F; 166 | } 167 | 168 | .flickity-page-dot.is-selected { 169 | background: hsl(0, 0%, 20%); 170 | } 171 | -------------------------------------------------------------------------------- /dist/flickity.css: -------------------------------------------------------------------------------- 1 | /*! Flickity v3.0.0 2 | https://flickity.metafizzy.co 3 | ---------------------------------------------- */ 4 | 5 | .flickity-enabled { 6 | position: relative; 7 | } 8 | 9 | .flickity-enabled:focus { outline: none; } 10 | 11 | .flickity-viewport { 12 | overflow: hidden; 13 | position: relative; 14 | height: 100%; 15 | touch-action: pan-y; 16 | } 17 | 18 | .flickity-slider { 19 | position: absolute; 20 | width: 100%; 21 | height: 100%; 22 | left: 0; 23 | } 24 | 25 | .flickity-rtl .flickity-slider { 26 | left: unset; 27 | right: 0; 28 | } 29 | 30 | /* draggable */ 31 | 32 | .flickity-enabled.is-draggable { 33 | -webkit-tap-highlight-color: transparent; 34 | user-select: none; 35 | } 36 | 37 | .flickity-enabled.is-draggable .flickity-viewport { 38 | cursor: move; 39 | cursor: grab; 40 | } 41 | 42 | .flickity-enabled.is-draggable .flickity-viewport.is-pointer-down { 43 | cursor: grabbing; 44 | } 45 | 46 | /* ---- flickity-cell ---- */ 47 | 48 | .flickity-cell { 49 | position: absolute; 50 | left: 0; 51 | } 52 | 53 | .flickity-rtl .flickity-cell { 54 | left: unset; 55 | right: 0; 56 | } 57 | 58 | /* ---- flickity-button ---- */ 59 | 60 | .flickity-button { 61 | position: absolute; 62 | background: hsl(0 0% 100% / 75%); 63 | border: none; 64 | color: #333; 65 | } 66 | 67 | .flickity-button:hover { 68 | background: white; 69 | cursor: pointer; 70 | } 71 | 72 | .flickity-button:focus { 73 | outline: none; 74 | box-shadow: 0 0 0 5px #19F; 75 | } 76 | 77 | .flickity-button:active { 78 | opacity: 0.6; 79 | } 80 | 81 | .flickity-button:disabled { 82 | opacity: 0.3; 83 | cursor: auto; 84 | /* prevent disabled button from capturing pointer up event. #716 */ 85 | pointer-events: none; 86 | } 87 | 88 | .flickity-button-icon { 89 | fill: currentColor; 90 | } 91 | 92 | /* ---- previous/next buttons ---- */ 93 | 94 | .flickity-prev-next-button { 95 | top: 50%; 96 | width: 44px; 97 | height: 44px; 98 | border-radius: 50%; 99 | /* vertically center */ 100 | transform: translateY(-50%); 101 | } 102 | 103 | .flickity-prev-next-button.previous { left: 10px; } 104 | .flickity-prev-next-button.next { right: 10px; } 105 | /* right to left */ 106 | .flickity-rtl .flickity-prev-next-button.previous { 107 | left: auto; 108 | right: 10px; 109 | } 110 | 111 | .flickity-rtl .flickity-prev-next-button.next { 112 | right: auto; 113 | left: 10px; 114 | } 115 | 116 | .flickity-prev-next-button .flickity-button-icon { 117 | position: absolute; 118 | left: 20%; 119 | top: 20%; 120 | width: 60%; 121 | height: 60%; 122 | } 123 | 124 | /* ---- page dots ---- */ 125 | 126 | .flickity-page-dots { 127 | position: absolute; 128 | width: 100%; 129 | bottom: -25px; 130 | text-align: center; 131 | display: flex; 132 | justify-content: center; 133 | flex-wrap: wrap; 134 | } 135 | 136 | .flickity-rtl .flickity-page-dots { direction: rtl; } 137 | 138 | .flickity-page-dot { 139 | display: block; 140 | width: 10px; 141 | height: 10px; 142 | padding: 0; 143 | margin: 0 8px; 144 | background: hsl(0 0% 20% / 25%); 145 | border-radius: 50%; 146 | cursor: pointer; 147 | appearance: none; 148 | border: none; 149 | text-indent: -9999px; 150 | overflow: hidden; 151 | } 152 | 153 | .flickity-rtl .flickity-page-dot { 154 | text-indent: 9999px; 155 | } 156 | 157 | .flickity-page-dot:focus { 158 | outline: none; 159 | box-shadow: 0 0 0 5px #19F; 160 | } 161 | 162 | .flickity-page-dot.is-selected { 163 | background: hsl(0 0% 20% / 100%); 164 | } 165 | -------------------------------------------------------------------------------- /dist/flickity.min.css: -------------------------------------------------------------------------------- 1 | /*! Flickity v3.0.0 2 | https://flickity.metafizzy.co 3 | ---------------------------------------------- */ 4 | .flickity-enabled{position:relative}.flickity-enabled:focus{outline:0}.flickity-viewport{overflow:hidden;position:relative;height:100%;touch-action:pan-y}.flickity-slider{position:absolute;width:100%;height:100%;left:0}.flickity-rtl .flickity-slider{left:unset;right:0}.flickity-enabled.is-draggable{-webkit-tap-highlight-color:transparent;user-select:none}.flickity-enabled.is-draggable .flickity-viewport{cursor:move;cursor:grab}.flickity-enabled.is-draggable .flickity-viewport.is-pointer-down{cursor:grabbing}.flickity-cell{position:absolute;left:0}.flickity-rtl .flickity-cell{left:unset;right:0}.flickity-button{position:absolute;background:hsl(0 0% 100% / 75%);border:none;color:#333}.flickity-button:hover{background:#fff;cursor:pointer}.flickity-button:focus{outline:0;box-shadow:0 0 0 5px #19f}.flickity-button:active{opacity:.6}.flickity-button:disabled{opacity:.3;cursor:auto;pointer-events:none}.flickity-button-icon{fill:currentColor}.flickity-prev-next-button{top:50%;width:44px;height:44px;border-radius:50%;transform:translateY(-50%)}.flickity-prev-next-button.previous{left:10px}.flickity-prev-next-button.next{right:10px}.flickity-rtl .flickity-prev-next-button.previous{left:auto;right:10px}.flickity-rtl .flickity-prev-next-button.next{right:auto;left:10px}.flickity-prev-next-button .flickity-button-icon{position:absolute;left:20%;top:20%;width:60%;height:60%}.flickity-page-dots{position:absolute;width:100%;bottom:-25px;text-align:center;display:flex;justify-content:center;flex-wrap:wrap}.flickity-rtl .flickity-page-dots{direction:rtl}.flickity-page-dot{display:block;width:10px;height:10px;padding:0;margin:0 8px;background:hsl(0 0% 20% / 25%);border-radius:50%;cursor:pointer;appearance:none;border:none;text-indent:-9999px;overflow:hidden}.flickity-rtl .flickity-page-dot{text-indent:9999px}.flickity-page-dot:focus{outline:0;box-shadow:0 0 0 5px #19f}.flickity-page-dot.is-selected{background:hsl(0 0% 20% / 100%)} -------------------------------------------------------------------------------- /js/add-remove-cell.js: -------------------------------------------------------------------------------- 1 | // add, remove cell 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory( 7 | require('./core'), 8 | require('fizzy-ui-utils'), 9 | ); 10 | } else { 11 | // browser global 12 | factory( 13 | window.Flickity, 14 | window.fizzyUIUtils, 15 | ); 16 | } 17 | 18 | }( typeof window != 'undefined' ? window : this, function factory( Flickity, utils ) { 19 | 20 | // append cells to a document fragment 21 | function getCellsFragment( cells ) { 22 | let fragment = document.createDocumentFragment(); 23 | cells.forEach( ( cell ) => fragment.appendChild( cell.element ) ); 24 | return fragment; 25 | } 26 | 27 | // -------------------------- add/remove cell prototype -------------------------- // 28 | 29 | let proto = Flickity.prototype; 30 | 31 | /** 32 | * Insert, prepend, or append cells 33 | * @param {[Element, Array, NodeList]} elems - Elements to insert 34 | * @param {Integer} index - Zero-based number to insert 35 | */ 36 | proto.insert = function( elems, index ) { 37 | let cells = this._makeCells( elems ); 38 | if ( !cells || !cells.length ) return; 39 | 40 | let len = this.cells.length; 41 | // default to append 42 | index = index === undefined ? len : index; 43 | // add cells with document fragment 44 | let fragment = getCellsFragment( cells ); 45 | // append to slider 46 | let isAppend = index === len; 47 | if ( isAppend ) { 48 | this.slider.appendChild( fragment ); 49 | } else { 50 | let insertCellElement = this.cells[ index ].element; 51 | this.slider.insertBefore( fragment, insertCellElement ); 52 | } 53 | // add to this.cells 54 | if ( index === 0 ) { 55 | // prepend, add to start 56 | this.cells = cells.concat( this.cells ); 57 | } else if ( isAppend ) { 58 | // append, add to end 59 | this.cells = this.cells.concat( cells ); 60 | } else { 61 | // insert in this.cells 62 | let endCells = this.cells.splice( index, len - index ); 63 | this.cells = this.cells.concat( cells ).concat( endCells ); 64 | } 65 | 66 | this._sizeCells( cells ); 67 | this.cellChange( index ); 68 | this.positionSliderAtSelected(); 69 | }; 70 | 71 | proto.append = function( elems ) { 72 | this.insert( elems, this.cells.length ); 73 | }; 74 | 75 | proto.prepend = function( elems ) { 76 | this.insert( elems, 0 ); 77 | }; 78 | 79 | /** 80 | * Remove cells 81 | * @param {[Element, Array, NodeList]} elems - ELements to remove 82 | */ 83 | proto.remove = function( elems ) { 84 | let cells = this.getCells( elems ); 85 | if ( !cells || !cells.length ) return; 86 | 87 | let minCellIndex = this.cells.length - 1; 88 | // remove cells from collection & DOM 89 | cells.forEach( ( cell ) => { 90 | cell.remove(); 91 | let index = this.cells.indexOf( cell ); 92 | minCellIndex = Math.min( index, minCellIndex ); 93 | utils.removeFrom( this.cells, cell ); 94 | } ); 95 | 96 | this.cellChange( minCellIndex ); 97 | this.positionSliderAtSelected(); 98 | }; 99 | 100 | /** 101 | * logic to be run after a cell's size changes 102 | * @param {Element} elem - cell's element 103 | */ 104 | proto.cellSizeChange = function( elem ) { 105 | let cell = this.getCell( elem ); 106 | if ( !cell ) return; 107 | 108 | cell.getSize(); 109 | 110 | let index = this.cells.indexOf( cell ); 111 | this.cellChange( index ); 112 | // do not position slider after lazy load 113 | }; 114 | 115 | /** 116 | * logic any time a cell is changed: added, removed, or size changed 117 | * @param {Integer} changedCellIndex - index of the changed cell, optional 118 | */ 119 | proto.cellChange = function( changedCellIndex ) { 120 | let prevSelectedElem = this.selectedElement; 121 | this._positionCells( changedCellIndex ); 122 | this._updateWrapShiftCells(); 123 | this.setGallerySize(); 124 | // update selectedIndex, try to maintain position & select previous selected element 125 | let cell = this.getCell( prevSelectedElem ); 126 | if ( cell ) this.selectedIndex = this.getCellSlideIndex( cell ); 127 | this.selectedIndex = Math.min( this.slides.length - 1, this.selectedIndex ); 128 | 129 | this.emitEvent( 'cellChange', [ changedCellIndex ] ); 130 | // position slider 131 | this.select( this.selectedIndex ); 132 | }; 133 | 134 | // ----- ----- // 135 | 136 | return Flickity; 137 | 138 | } ) ); 139 | -------------------------------------------------------------------------------- /js/animate.js: -------------------------------------------------------------------------------- 1 | // animate 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory( require('fizzy-ui-utils') ); 7 | } else { 8 | // browser global 9 | window.Flickity = window.Flickity || {}; 10 | window.Flickity.animatePrototype = factory( window.fizzyUIUtils ); 11 | } 12 | 13 | }( typeof window != 'undefined' ? window : this, function factory( utils ) { 14 | 15 | // -------------------------- animate -------------------------- // 16 | 17 | let proto = {}; 18 | 19 | proto.startAnimation = function() { 20 | if ( this.isAnimating ) return; 21 | 22 | this.isAnimating = true; 23 | this.restingFrames = 0; 24 | this.animate(); 25 | }; 26 | 27 | proto.animate = function() { 28 | this.applyDragForce(); 29 | this.applySelectedAttraction(); 30 | 31 | let previousX = this.x; 32 | 33 | this.integratePhysics(); 34 | this.positionSlider(); 35 | this.settle( previousX ); 36 | // animate next frame 37 | if ( this.isAnimating ) requestAnimationFrame( () => this.animate() ); 38 | }; 39 | 40 | proto.positionSlider = function() { 41 | let x = this.x; 42 | // wrap position around 43 | if ( this.isWrapping ) { 44 | x = utils.modulo( x, this.slideableWidth ) - this.slideableWidth; 45 | this.shiftWrapCells( x ); 46 | } 47 | 48 | this.setTranslateX( x, this.isAnimating ); 49 | this.dispatchScrollEvent(); 50 | }; 51 | 52 | proto.setTranslateX = function( x, is3d ) { 53 | x += this.cursorPosition; 54 | // reverse if right-to-left and using transform 55 | if ( this.options.rightToLeft ) x = -x; 56 | let translateX = this.getPositionValue( x ); 57 | // use 3D transforms for hardware acceleration on iOS 58 | // but use 2D when settled, for better font-rendering 59 | this.slider.style.transform = is3d ? 60 | `translate3d(${translateX},0,0)` : `translateX(${translateX})`; 61 | }; 62 | 63 | proto.dispatchScrollEvent = function() { 64 | let firstSlide = this.slides[0]; 65 | if ( !firstSlide ) return; 66 | 67 | let positionX = -this.x - firstSlide.target; 68 | let progress = positionX / this.slidesWidth; 69 | this.dispatchEvent( 'scroll', null, [ progress, positionX ] ); 70 | }; 71 | 72 | proto.positionSliderAtSelected = function() { 73 | if ( !this.cells.length ) return; 74 | 75 | this.x = -this.selectedSlide.target; 76 | this.velocity = 0; // stop wobble 77 | this.positionSlider(); 78 | }; 79 | 80 | proto.getPositionValue = function( position ) { 81 | if ( this.options.percentPosition ) { 82 | // percent position, round to 2 digits, like 12.34% 83 | return ( Math.round( ( position / this.size.innerWidth ) * 10000 ) * 0.01 ) + '%'; 84 | } else { 85 | // pixel positioning 86 | return Math.round( position ) + 'px'; 87 | } 88 | }; 89 | 90 | proto.settle = function( previousX ) { 91 | // keep track of frames where x hasn't moved 92 | let isResting = !this.isPointerDown && 93 | Math.round( this.x * 100 ) === Math.round( previousX * 100 ); 94 | if ( isResting ) this.restingFrames++; 95 | // stop animating if resting for 3 or more frames 96 | if ( this.restingFrames > 2 ) { 97 | this.isAnimating = false; 98 | delete this.isFreeScrolling; 99 | // render position with translateX when settled 100 | this.positionSlider(); 101 | this.dispatchEvent( 'settle', null, [ this.selectedIndex ] ); 102 | } 103 | }; 104 | 105 | proto.shiftWrapCells = function( x ) { 106 | // shift before cells 107 | let beforeGap = this.cursorPosition + x; 108 | this._shiftCells( this.beforeShiftCells, beforeGap, -1 ); 109 | // shift after cells 110 | let afterGap = this.size.innerWidth - ( x + this.slideableWidth + this.cursorPosition ); 111 | this._shiftCells( this.afterShiftCells, afterGap, 1 ); 112 | }; 113 | 114 | proto._shiftCells = function( cells, gap, shift ) { 115 | cells.forEach( ( cell ) => { 116 | let cellShift = gap > 0 ? shift : 0; 117 | this._wrapShiftCell( cell, cellShift ); 118 | gap -= cell.size.outerWidth; 119 | } ); 120 | }; 121 | 122 | proto._unshiftCells = function( cells ) { 123 | if ( !cells || !cells.length ) return; 124 | 125 | cells.forEach( ( cell ) => this._wrapShiftCell( cell, 0 ) ); 126 | }; 127 | 128 | // @param {Integer} shift - 0, 1, or -1 129 | proto._wrapShiftCell = function( cell, shift ) { 130 | this._renderCellPosition( cell, cell.x + this.slideableWidth * shift ); 131 | }; 132 | 133 | // -------------------------- physics -------------------------- // 134 | 135 | proto.integratePhysics = function() { 136 | this.x += this.velocity; 137 | this.velocity *= this.getFrictionFactor(); 138 | }; 139 | 140 | proto.applyForce = function( force ) { 141 | this.velocity += force; 142 | }; 143 | 144 | proto.getFrictionFactor = function() { 145 | return 1 - this.options[ this.isFreeScrolling ? 'freeScrollFriction' : 'friction' ]; 146 | }; 147 | 148 | proto.getRestingPosition = function() { 149 | // my thanks to Steven Wittens, who simplified this math greatly 150 | return this.x + this.velocity / ( 1 - this.getFrictionFactor() ); 151 | }; 152 | 153 | proto.applyDragForce = function() { 154 | if ( !this.isDraggable || !this.isPointerDown ) return; 155 | 156 | // change the position to drag position by applying force 157 | let dragVelocity = this.dragX - this.x; 158 | let dragForce = dragVelocity - this.velocity; 159 | this.applyForce( dragForce ); 160 | }; 161 | 162 | proto.applySelectedAttraction = function() { 163 | // do not attract if pointer down or no slides 164 | let dragDown = this.isDraggable && this.isPointerDown; 165 | if ( dragDown || this.isFreeScrolling || !this.slides.length ) return; 166 | 167 | let distance = this.selectedSlide.target * -1 - this.x; 168 | let force = distance * this.options.selectedAttraction; 169 | this.applyForce( force ); 170 | }; 171 | 172 | return proto; 173 | 174 | } ) ); 175 | -------------------------------------------------------------------------------- /js/cell.js: -------------------------------------------------------------------------------- 1 | // Flickity.Cell 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory( require('get-size') ); 7 | } else { 8 | // browser global 9 | window.Flickity = window.Flickity || {}; 10 | window.Flickity.Cell = factory( window.getSize ); 11 | } 12 | 13 | }( typeof window != 'undefined' ? window : this, function factory( getSize ) { 14 | 15 | const cellClassName = 'flickity-cell'; 16 | 17 | function Cell( elem ) { 18 | this.element = elem; 19 | this.element.classList.add( cellClassName ); 20 | 21 | this.x = 0; 22 | this.unselect(); 23 | } 24 | 25 | let proto = Cell.prototype; 26 | 27 | proto.destroy = function() { 28 | // reset style 29 | this.unselect(); 30 | this.element.classList.remove( cellClassName ); 31 | this.element.style.transform = ''; 32 | this.element.removeAttribute('aria-hidden'); 33 | }; 34 | 35 | proto.getSize = function() { 36 | this.size = getSize( this.element ); 37 | }; 38 | 39 | proto.select = function() { 40 | this.element.classList.add('is-selected'); 41 | this.element.removeAttribute('aria-hidden'); 42 | }; 43 | 44 | proto.unselect = function() { 45 | this.element.classList.remove('is-selected'); 46 | this.element.setAttribute( 'aria-hidden', 'true' ); 47 | }; 48 | 49 | proto.remove = function() { 50 | this.element.remove(); 51 | }; 52 | 53 | return Cell; 54 | 55 | } ) ); 56 | -------------------------------------------------------------------------------- /js/drag.js: -------------------------------------------------------------------------------- 1 | // drag 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory( 7 | window, 8 | require('./core'), 9 | require('unidragger'), 10 | require('fizzy-ui-utils'), 11 | ); 12 | } else { 13 | // browser global 14 | window.Flickity = factory( 15 | window, 16 | window.Flickity, 17 | window.Unidragger, 18 | window.fizzyUIUtils, 19 | ); 20 | } 21 | 22 | }( typeof window != 'undefined' ? window : this, 23 | function factory( window, Flickity, Unidragger, utils ) { 24 | 25 | // ----- defaults ----- // 26 | 27 | Object.assign( Flickity.defaults, { 28 | draggable: '>1', 29 | dragThreshold: 3, 30 | } ); 31 | 32 | // -------------------------- drag prototype -------------------------- // 33 | 34 | let proto = Flickity.prototype; 35 | Object.assign( proto, Unidragger.prototype ); // inherit Unidragger 36 | proto.touchActionValue = ''; 37 | 38 | // -------------------------- -------------------------- // 39 | 40 | Flickity.create.drag = function() { 41 | this.on( 'activate', this.onActivateDrag ); 42 | this.on( 'uiChange', this._uiChangeDrag ); 43 | this.on( 'deactivate', this.onDeactivateDrag ); 44 | this.on( 'cellChange', this.updateDraggable ); 45 | this.on( 'pointerDown', this.handlePointerDown ); 46 | this.on( 'pointerUp', this.handlePointerUp ); 47 | this.on( 'pointerDown', this.handlePointerDone ); 48 | this.on( 'dragStart', this.handleDragStart ); 49 | this.on( 'dragMove', this.handleDragMove ); 50 | this.on( 'dragEnd', this.handleDragEnd ); 51 | this.on( 'staticClick', this.handleStaticClick ); 52 | // TODO updateDraggable on resize? if groupCells & slides change 53 | }; 54 | 55 | proto.onActivateDrag = function() { 56 | this.handles = [ this.viewport ]; 57 | this.bindHandles(); 58 | this.updateDraggable(); 59 | }; 60 | 61 | proto.onDeactivateDrag = function() { 62 | this.unbindHandles(); 63 | this.element.classList.remove('is-draggable'); 64 | }; 65 | 66 | proto.updateDraggable = function() { 67 | // disable dragging if less than 2 slides. #278 68 | if ( this.options.draggable === '>1' ) { 69 | this.isDraggable = this.slides.length > 1; 70 | } else { 71 | this.isDraggable = this.options.draggable; 72 | } 73 | this.element.classList.toggle( 'is-draggable', this.isDraggable ); 74 | }; 75 | 76 | proto._uiChangeDrag = function() { 77 | delete this.isFreeScrolling; 78 | }; 79 | 80 | // -------------------------- pointer events -------------------------- // 81 | 82 | proto.handlePointerDown = function( event ) { 83 | if ( !this.isDraggable ) { 84 | // proceed for staticClick 85 | this.bindActivePointerEvents( event ); 86 | return; 87 | } 88 | 89 | let isTouchStart = event.type === 'touchstart'; 90 | let isTouchPointer = event.pointerType === 'touch'; 91 | let isFocusNode = event.target.matches('input, textarea, select'); 92 | if ( !isTouchStart && !isTouchPointer && !isFocusNode ) event.preventDefault(); 93 | if ( !isFocusNode ) this.focus(); 94 | // blur 95 | if ( document.activeElement !== this.element ) document.activeElement.blur(); 96 | // stop if it was moving 97 | this.dragX = this.x; 98 | this.viewport.classList.add('is-pointer-down'); 99 | // track scrolling 100 | this.pointerDownScroll = getScrollPosition(); 101 | window.addEventListener( 'scroll', this ); 102 | this.bindActivePointerEvents( event ); 103 | }; 104 | 105 | // ----- move ----- // 106 | 107 | proto.hasDragStarted = function( moveVector ) { 108 | return Math.abs( moveVector.x ) > this.options.dragThreshold; 109 | }; 110 | 111 | // ----- up ----- // 112 | 113 | proto.handlePointerUp = function() { 114 | delete this.isTouchScrolling; 115 | this.viewport.classList.remove('is-pointer-down'); 116 | }; 117 | 118 | proto.handlePointerDone = function() { 119 | window.removeEventListener( 'scroll', this ); 120 | delete this.pointerDownScroll; 121 | }; 122 | 123 | // -------------------------- dragging -------------------------- // 124 | 125 | proto.handleDragStart = function() { 126 | if ( !this.isDraggable ) return; 127 | 128 | this.dragStartPosition = this.x; 129 | this.startAnimation(); 130 | window.removeEventListener( 'scroll', this ); 131 | }; 132 | 133 | proto.handleDragMove = function( event, pointer, moveVector ) { 134 | if ( !this.isDraggable ) return; 135 | 136 | event.preventDefault(); 137 | 138 | this.previousDragX = this.dragX; 139 | // reverse if right-to-left 140 | let direction = this.options.rightToLeft ? -1 : 1; 141 | // wrap around move. #589 142 | if ( this.isWrapping ) moveVector.x %= this.slideableWidth; 143 | let dragX = this.dragStartPosition + moveVector.x * direction; 144 | 145 | if ( !this.isWrapping ) { 146 | // slow drag 147 | let originBound = Math.max( -this.slides[0].target, this.dragStartPosition ); 148 | dragX = dragX > originBound ? ( dragX + originBound ) * 0.5 : dragX; 149 | let endBound = Math.min( -this.getLastSlide().target, this.dragStartPosition ); 150 | dragX = dragX < endBound ? ( dragX + endBound ) * 0.5 : dragX; 151 | } 152 | 153 | this.dragX = dragX; 154 | this.dragMoveTime = new Date(); 155 | }; 156 | 157 | proto.handleDragEnd = function() { 158 | if ( !this.isDraggable ) return; 159 | 160 | let { freeScroll } = this.options; 161 | if ( freeScroll ) this.isFreeScrolling = true; 162 | // set selectedIndex based on where flick will end up 163 | let index = this.dragEndRestingSelect(); 164 | 165 | if ( freeScroll && !this.isWrapping ) { 166 | // if free-scroll & not wrap around 167 | // do not free-scroll if going outside of bounding slides 168 | // so bounding slides can attract slider, and keep it in bounds 169 | let restingX = this.getRestingPosition(); 170 | this.isFreeScrolling = -restingX > this.slides[0].target && 171 | -restingX < this.getLastSlide().target; 172 | } else if ( !freeScroll && index === this.selectedIndex ) { 173 | // boost selection if selected index has not changed 174 | index += this.dragEndBoostSelect(); 175 | } 176 | delete this.previousDragX; 177 | // apply selection 178 | // HACK, set flag so dragging stays in correct direction 179 | this.isDragSelect = this.isWrapping; 180 | this.select( index ); 181 | delete this.isDragSelect; 182 | }; 183 | 184 | proto.dragEndRestingSelect = function() { 185 | let restingX = this.getRestingPosition(); 186 | // how far away from selected slide 187 | let distance = Math.abs( this.getSlideDistance( -restingX, this.selectedIndex ) ); 188 | // get closet resting going up and going down 189 | let positiveResting = this._getClosestResting( restingX, distance, 1 ); 190 | let negativeResting = this._getClosestResting( restingX, distance, -1 ); 191 | // use closer resting for wrap-around 192 | return positiveResting.distance < negativeResting.distance ? 193 | positiveResting.index : negativeResting.index; 194 | }; 195 | 196 | /** 197 | * given resting X and distance to selected cell 198 | * get the distance and index of the closest cell 199 | * @param {Number} restingX - estimated post-flick resting position 200 | * @param {Number} distance - distance to selected cell 201 | * @param {Integer} increment - +1 or -1, going up or down 202 | * @returns {Object} - { distance: {Number}, index: {Integer} } 203 | */ 204 | proto._getClosestResting = function( restingX, distance, increment ) { 205 | let index = this.selectedIndex; 206 | let minDistance = Infinity; 207 | let condition = this.options.contain && !this.isWrapping ? 208 | // if containing, keep going if distance is equal to minDistance 209 | ( dist, minDist ) => dist <= minDist : 210 | ( dist, minDist ) => dist < minDist; 211 | 212 | while ( condition( distance, minDistance ) ) { 213 | // measure distance to next cell 214 | index += increment; 215 | minDistance = distance; 216 | distance = this.getSlideDistance( -restingX, index ); 217 | if ( distance === null ) break; 218 | 219 | distance = Math.abs( distance ); 220 | } 221 | return { 222 | distance: minDistance, 223 | // selected was previous index 224 | index: index - increment, 225 | }; 226 | }; 227 | 228 | /** 229 | * measure distance between x and a slide target 230 | * @param {Number} x - horizontal position 231 | * @param {Integer} index - slide index 232 | * @returns {Number} - slide distance 233 | */ 234 | proto.getSlideDistance = function( x, index ) { 235 | let len = this.slides.length; 236 | // wrap around if at least 2 slides 237 | let isWrapAround = this.options.wrapAround && len > 1; 238 | let slideIndex = isWrapAround ? utils.modulo( index, len ) : index; 239 | let slide = this.slides[ slideIndex ]; 240 | if ( !slide ) return null; 241 | 242 | // add distance for wrap-around slides 243 | let wrap = isWrapAround ? this.slideableWidth * Math.floor( index/len ) : 0; 244 | return x - ( slide.target + wrap ); 245 | }; 246 | 247 | proto.dragEndBoostSelect = function() { 248 | // do not boost if no previousDragX or dragMoveTime 249 | if ( this.previousDragX === undefined || !this.dragMoveTime || 250 | // or if drag was held for 100 ms 251 | new Date() - this.dragMoveTime > 100 ) { 252 | return 0; 253 | } 254 | 255 | let distance = this.getSlideDistance( -this.dragX, this.selectedIndex ); 256 | let delta = this.previousDragX - this.dragX; 257 | if ( distance > 0 && delta > 0 ) { 258 | // boost to next if moving towards the right, and positive velocity 259 | return 1; 260 | } else if ( distance < 0 && delta < 0 ) { 261 | // boost to previous if moving towards the left, and negative velocity 262 | return -1; 263 | } 264 | return 0; 265 | }; 266 | 267 | // ----- scroll ----- // 268 | 269 | proto.onscroll = function() { 270 | let scroll = getScrollPosition(); 271 | let scrollMoveX = this.pointerDownScroll.x - scroll.x; 272 | let scrollMoveY = this.pointerDownScroll.y - scroll.y; 273 | // cancel click/tap if scroll is too much 274 | if ( Math.abs( scrollMoveX ) > 3 || Math.abs( scrollMoveY ) > 3 ) { 275 | this.pointerDone(); 276 | } 277 | }; 278 | 279 | // ----- utils ----- // 280 | 281 | function getScrollPosition() { 282 | return { 283 | x: window.pageXOffset, 284 | y: window.pageYOffset, 285 | }; 286 | } 287 | 288 | // ----- ----- // 289 | 290 | return Flickity; 291 | 292 | } ) ); 293 | -------------------------------------------------------------------------------- /js/imagesloaded.js: -------------------------------------------------------------------------------- 1 | // imagesloaded 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory( 7 | require('./core'), 8 | require('imagesloaded'), 9 | ); 10 | } else { 11 | // browser global 12 | factory( 13 | window.Flickity, 14 | window.imagesLoaded, 15 | ); 16 | } 17 | 18 | }( typeof window != 'undefined' ? window : this, 19 | function factory( Flickity, imagesLoaded ) { 20 | 21 | Flickity.create.imagesLoaded = function() { 22 | this.on( 'activate', this.imagesLoaded ); 23 | }; 24 | 25 | Flickity.prototype.imagesLoaded = function() { 26 | if ( !this.options.imagesLoaded ) return; 27 | 28 | let onImagesLoadedProgress = ( instance, image ) => { 29 | let cell = this.getParentCell( image.img ); 30 | this.cellSizeChange( cell && cell.element ); 31 | if ( !this.options.freeScroll ) this.positionSliderAtSelected(); 32 | }; 33 | imagesLoaded( this.slider ).on( 'progress', onImagesLoadedProgress ); 34 | }; 35 | 36 | return Flickity; 37 | 38 | } ) ); 39 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Flickity v3.0.0 3 | * Touch, responsive, flickable carousels 4 | * 5 | * Licensed GPLv3 for open source use 6 | * or Flickity Commercial License for commercial use 7 | * 8 | * https://flickity.metafizzy.co 9 | * Copyright 2015-2022 Metafizzy 10 | */ 11 | 12 | if ( typeof module == 'object' && module.exports ) { 13 | const Flickity = require('./core'); 14 | require('./drag'); 15 | require('./prev-next-button'); 16 | require('./page-dots'); 17 | require('./player'); 18 | require('./add-remove-cell'); 19 | require('./lazyload'); 20 | require('./imagesloaded'); 21 | 22 | module.exports = Flickity; 23 | } 24 | -------------------------------------------------------------------------------- /js/lazyload.js: -------------------------------------------------------------------------------- 1 | // lazyload 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory( 7 | require('./core'), 8 | require('fizzy-ui-utils'), 9 | ); 10 | } else { 11 | // browser global 12 | factory( 13 | window.Flickity, 14 | window.fizzyUIUtils, 15 | ); 16 | } 17 | 18 | }( typeof window != 'undefined' ? window : this, function factory( Flickity, utils ) { 19 | 20 | const lazyAttr = 'data-flickity-lazyload'; 21 | const lazySrcAttr = `${lazyAttr}-src`; 22 | const lazySrcsetAttr = `${lazyAttr}-srcset`; 23 | const imgSelector = `img[${lazyAttr}], img[${lazySrcAttr}], ` + 24 | `img[${lazySrcsetAttr}], source[${lazySrcsetAttr}]`; 25 | 26 | Flickity.create.lazyLoad = function() { 27 | this.on( 'select', this.lazyLoad ); 28 | 29 | this.handleLazyLoadComplete = this.onLazyLoadComplete.bind( this ); 30 | }; 31 | 32 | let proto = Flickity.prototype; 33 | 34 | proto.lazyLoad = function() { 35 | let { lazyLoad } = this.options; 36 | if ( !lazyLoad ) return; 37 | 38 | // get adjacent cells, use lazyLoad option for adjacent count 39 | let adjCount = typeof lazyLoad == 'number' ? lazyLoad : 0; 40 | // lazy load images 41 | this.getAdjacentCellElements( adjCount ) 42 | .map( getCellLazyImages ) 43 | .flat() 44 | .forEach( ( img ) => new LazyLoader( img, this.handleLazyLoadComplete ) ); 45 | }; 46 | 47 | function getCellLazyImages( cellElem ) { 48 | // check if cell element is lazy image 49 | if ( cellElem.matches('img') ) { 50 | let cellAttr = cellElem.getAttribute( lazyAttr ); 51 | let cellSrcAttr = cellElem.getAttribute( lazySrcAttr ); 52 | let cellSrcsetAttr = cellElem.getAttribute( lazySrcsetAttr ); 53 | if ( cellAttr || cellSrcAttr || cellSrcsetAttr ) { 54 | return cellElem; 55 | } 56 | } 57 | // select lazy images in cell 58 | return [ ...cellElem.querySelectorAll( imgSelector ) ]; 59 | } 60 | 61 | proto.onLazyLoadComplete = function( img, event ) { 62 | let cell = this.getParentCell( img ); 63 | let cellElem = cell && cell.element; 64 | this.cellSizeChange( cellElem ); 65 | 66 | this.dispatchEvent( 'lazyLoad', event, cellElem ); 67 | }; 68 | 69 | // -------------------------- LazyLoader -------------------------- // 70 | 71 | /** 72 | * class to handle loading images 73 | * @param {Image} img - Image element 74 | * @param {Function} onComplete - callback function 75 | */ 76 | function LazyLoader( img, onComplete ) { 77 | this.img = img; 78 | this.onComplete = onComplete; 79 | this.load(); 80 | } 81 | 82 | LazyLoader.prototype.handleEvent = utils.handleEvent; 83 | 84 | LazyLoader.prototype.load = function() { 85 | this.img.addEventListener( 'load', this ); 86 | this.img.addEventListener( 'error', this ); 87 | // get src & srcset 88 | let src = this.img.getAttribute( lazyAttr ) || 89 | this.img.getAttribute( lazySrcAttr ); 90 | let srcset = this.img.getAttribute( lazySrcsetAttr ); 91 | // set src & serset 92 | this.img.src = src; 93 | if ( srcset ) this.img.setAttribute( 'srcset', srcset ); 94 | // remove attr 95 | this.img.removeAttribute( lazyAttr ); 96 | this.img.removeAttribute( lazySrcAttr ); 97 | this.img.removeAttribute( lazySrcsetAttr ); 98 | }; 99 | 100 | LazyLoader.prototype.onload = function( event ) { 101 | this.complete( event, 'flickity-lazyloaded' ); 102 | }; 103 | 104 | LazyLoader.prototype.onerror = function( event ) { 105 | this.complete( event, 'flickity-lazyerror' ); 106 | }; 107 | 108 | LazyLoader.prototype.complete = function( event, className ) { 109 | // unbind events 110 | this.img.removeEventListener( 'load', this ); 111 | this.img.removeEventListener( 'error', this ); 112 | let mediaElem = this.img.parentNode.matches('picture') ? this.img.parentNode : this.img; 113 | mediaElem.classList.add( className ); 114 | 115 | this.onComplete( this.img, event ); 116 | }; 117 | 118 | // ----- ----- // 119 | 120 | Flickity.LazyLoader = LazyLoader; 121 | 122 | return Flickity; 123 | 124 | } ) ); 125 | -------------------------------------------------------------------------------- /js/page-dots.js: -------------------------------------------------------------------------------- 1 | // page dots 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory( 7 | require('./core'), 8 | require('fizzy-ui-utils'), 9 | ); 10 | } else { 11 | // browser global 12 | factory( 13 | window.Flickity, 14 | window.fizzyUIUtils, 15 | ); 16 | } 17 | 18 | }( typeof window != 'undefined' ? window : this, function factory( Flickity, utils ) { 19 | 20 | // -------------------------- PageDots -------------------------- // 21 | 22 | function PageDots() { 23 | // create holder element 24 | this.holder = document.createElement('div'); 25 | this.holder.className = 'flickity-page-dots'; 26 | // create dots, array of elements 27 | this.dots = []; 28 | } 29 | 30 | PageDots.prototype.setDots = function( slidesLength ) { 31 | // get difference between number of slides and number of dots 32 | let delta = slidesLength - this.dots.length; 33 | if ( delta > 0 ) { 34 | this.addDots( delta ); 35 | } else if ( delta < 0 ) { 36 | this.removeDots( -delta ); 37 | } 38 | }; 39 | 40 | PageDots.prototype.addDots = function( count ) { 41 | let newDots = new Array( count ).fill() 42 | .map( ( item, i ) => { 43 | let dot = document.createElement('button'); 44 | dot.setAttribute( 'type', 'button' ); 45 | let num = i + 1 + this.dots.length; 46 | dot.className = 'flickity-button flickity-page-dot'; 47 | dot.textContent = `View slide ${num}`; 48 | return dot; 49 | } ); 50 | 51 | this.holder.append( ...newDots ); 52 | this.dots = this.dots.concat( newDots ); 53 | }; 54 | 55 | PageDots.prototype.removeDots = function( count ) { 56 | // remove from this.dots collection 57 | let removeDots = this.dots.splice( this.dots.length - count, count ); 58 | // remove from DOM 59 | removeDots.forEach( ( dot ) => dot.remove() ); 60 | }; 61 | 62 | PageDots.prototype.updateSelected = function( index ) { 63 | // remove selected class on previous 64 | if ( this.selectedDot ) { 65 | this.selectedDot.classList.remove('is-selected'); 66 | this.selectedDot.removeAttribute('aria-current'); 67 | } 68 | // don't proceed if no dots 69 | if ( !this.dots.length ) return; 70 | 71 | this.selectedDot = this.dots[ index ]; 72 | this.selectedDot.classList.add('is-selected'); 73 | this.selectedDot.setAttribute( 'aria-current', 'step' ); 74 | }; 75 | 76 | Flickity.PageDots = PageDots; 77 | 78 | // -------------------------- Flickity -------------------------- // 79 | 80 | Object.assign( Flickity.defaults, { 81 | pageDots: true, 82 | } ); 83 | 84 | Flickity.create.pageDots = function() { 85 | if ( !this.options.pageDots ) return; 86 | 87 | this.pageDots = new PageDots(); 88 | this.handlePageDotsClick = this.onPageDotsClick.bind( this ); 89 | // events 90 | this.on( 'activate', this.activatePageDots ); 91 | this.on( 'select', this.updateSelectedPageDots ); 92 | this.on( 'cellChange', this.updatePageDots ); 93 | this.on( 'resize', this.updatePageDots ); 94 | this.on( 'deactivate', this.deactivatePageDots ); 95 | }; 96 | 97 | let proto = Flickity.prototype; 98 | 99 | proto.activatePageDots = function() { 100 | this.pageDots.setDots( this.slides.length ); 101 | this.focusableElems.push( ...this.pageDots.dots ); 102 | this.pageDots.holder.addEventListener( 'click', this.handlePageDotsClick ); 103 | this.element.insertBefore( this.pageDots.holder, this.viewport ); 104 | }; 105 | 106 | proto.onPageDotsClick = function( event ) { 107 | let index = this.pageDots.dots.indexOf( event.target ); 108 | if ( index === -1 ) return; // only dot clicks 109 | 110 | this.uiChange(); 111 | this.select( index ); 112 | }; 113 | 114 | proto.updateSelectedPageDots = function() { 115 | this.pageDots.updateSelected( this.selectedIndex ); 116 | }; 117 | 118 | proto.updatePageDots = function() { 119 | this.pageDots.dots.forEach( ( dot ) => { 120 | utils.removeFrom( this.focusableElems, dot ); 121 | } ); 122 | this.pageDots.setDots( this.slides.length ); 123 | this.focusableElems.push( ...this.pageDots.dots ); 124 | }; 125 | 126 | proto.deactivatePageDots = function() { 127 | this.pageDots.holder.remove(); 128 | this.pageDots.holder.removeEventListener( 'click', this.handlePageDotsClick ); 129 | }; 130 | 131 | // ----- ----- // 132 | 133 | Flickity.PageDots = PageDots; 134 | 135 | return Flickity; 136 | 137 | } ) ); 138 | -------------------------------------------------------------------------------- /js/player.js: -------------------------------------------------------------------------------- 1 | // player & autoPlay 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory( require('./core') ); 7 | } else { 8 | // browser global 9 | factory( window.Flickity ); 10 | } 11 | 12 | }( typeof window != 'undefined' ? window : this, function factory( Flickity ) { 13 | 14 | // -------------------------- Player -------------------------- // 15 | 16 | function Player( autoPlay, onTick ) { 17 | this.autoPlay = autoPlay; 18 | this.onTick = onTick; 19 | this.state = 'stopped'; 20 | // visibility change event handler 21 | this.onVisibilityChange = this.visibilityChange.bind( this ); 22 | this.onVisibilityPlay = this.visibilityPlay.bind( this ); 23 | } 24 | 25 | // start play 26 | Player.prototype.play = function() { 27 | if ( this.state === 'playing' ) return; 28 | 29 | // do not play if page is hidden, start playing when page is visible 30 | let isPageHidden = document.hidden; 31 | if ( isPageHidden ) { 32 | document.addEventListener( 'visibilitychange', this.onVisibilityPlay ); 33 | return; 34 | } 35 | 36 | this.state = 'playing'; 37 | // listen to visibility change 38 | document.addEventListener( 'visibilitychange', this.onVisibilityChange ); 39 | // start ticking 40 | this.tick(); 41 | }; 42 | 43 | Player.prototype.tick = function() { 44 | // do not tick if not playing 45 | if ( this.state !== 'playing' ) return; 46 | 47 | // default to 3 seconds 48 | let time = typeof this.autoPlay == 'number' ? this.autoPlay : 3000; 49 | // HACK: reset ticks if stopped and started within interval 50 | this.clear(); 51 | this.timeout = setTimeout( () => { 52 | this.onTick(); 53 | this.tick(); 54 | }, time ); 55 | }; 56 | 57 | Player.prototype.stop = function() { 58 | this.state = 'stopped'; 59 | this.clear(); 60 | // remove visibility change event 61 | document.removeEventListener( 'visibilitychange', this.onVisibilityChange ); 62 | }; 63 | 64 | Player.prototype.clear = function() { 65 | clearTimeout( this.timeout ); 66 | }; 67 | 68 | Player.prototype.pause = function() { 69 | if ( this.state === 'playing' ) { 70 | this.state = 'paused'; 71 | this.clear(); 72 | } 73 | }; 74 | 75 | Player.prototype.unpause = function() { 76 | // re-start play if paused 77 | if ( this.state === 'paused' ) this.play(); 78 | }; 79 | 80 | // pause if page visibility is hidden, unpause if visible 81 | Player.prototype.visibilityChange = function() { 82 | let isPageHidden = document.hidden; 83 | this[ isPageHidden ? 'pause' : 'unpause' ](); 84 | }; 85 | 86 | Player.prototype.visibilityPlay = function() { 87 | this.play(); 88 | document.removeEventListener( 'visibilitychange', this.onVisibilityPlay ); 89 | }; 90 | 91 | // -------------------------- Flickity -------------------------- // 92 | 93 | Object.assign( Flickity.defaults, { 94 | pauseAutoPlayOnHover: true, 95 | } ); 96 | 97 | Flickity.create.player = function() { 98 | this.player = new Player( this.options.autoPlay, () => { 99 | this.next( true ); 100 | } ); 101 | 102 | this.on( 'activate', this.activatePlayer ); 103 | this.on( 'uiChange', this.stopPlayer ); 104 | this.on( 'pointerDown', this.stopPlayer ); 105 | this.on( 'deactivate', this.deactivatePlayer ); 106 | }; 107 | 108 | let proto = Flickity.prototype; 109 | 110 | proto.activatePlayer = function() { 111 | if ( !this.options.autoPlay ) return; 112 | 113 | this.player.play(); 114 | this.element.addEventListener( 'mouseenter', this ); 115 | }; 116 | 117 | // Player API, don't hate the ... thanks I know where the door is 118 | 119 | proto.playPlayer = function() { 120 | this.player.play(); 121 | }; 122 | 123 | proto.stopPlayer = function() { 124 | this.player.stop(); 125 | }; 126 | 127 | proto.pausePlayer = function() { 128 | this.player.pause(); 129 | }; 130 | 131 | proto.unpausePlayer = function() { 132 | this.player.unpause(); 133 | }; 134 | 135 | proto.deactivatePlayer = function() { 136 | this.player.stop(); 137 | this.element.removeEventListener( 'mouseenter', this ); 138 | }; 139 | 140 | // ----- mouseenter/leave ----- // 141 | 142 | // pause auto-play on hover 143 | proto.onmouseenter = function() { 144 | if ( !this.options.pauseAutoPlayOnHover ) return; 145 | 146 | this.player.pause(); 147 | this.element.addEventListener( 'mouseleave', this ); 148 | }; 149 | 150 | // resume auto-play on hover off 151 | proto.onmouseleave = function() { 152 | this.player.unpause(); 153 | this.element.removeEventListener( 'mouseleave', this ); 154 | }; 155 | 156 | // ----- ----- // 157 | 158 | Flickity.Player = Player; 159 | 160 | return Flickity; 161 | 162 | } ) ); 163 | -------------------------------------------------------------------------------- /js/prev-next-button.js: -------------------------------------------------------------------------------- 1 | // prev/next buttons 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory( require('./core') ); 7 | } else { 8 | // browser global 9 | factory( window.Flickity ); 10 | } 11 | 12 | }( typeof window != 'undefined' ? window : this, function factory( Flickity ) { 13 | 14 | const svgURI = 'http://www.w3.org/2000/svg'; 15 | 16 | // -------------------------- PrevNextButton -------------------------- // 17 | 18 | function PrevNextButton( increment, direction, arrowShape ) { 19 | this.increment = increment; 20 | this.direction = direction; 21 | this.isPrevious = increment === 'previous'; 22 | this.isLeft = direction === 'left'; 23 | this._create( arrowShape ); 24 | } 25 | 26 | PrevNextButton.prototype._create = function( arrowShape ) { 27 | // properties 28 | let element = this.element = document.createElement('button'); 29 | element.className = `flickity-button flickity-prev-next-button ${this.increment}`; 30 | let label = this.isPrevious ? 'Previous' : 'Next'; 31 | // prevent button from submitting form https://stackoverflow.com/a/10836076/182183 32 | element.setAttribute( 'type', 'button' ); 33 | element.setAttribute( 'aria-label', label ); 34 | // init as disabled 35 | this.disable(); 36 | // create arrow 37 | let svg = this.createSVG( label, arrowShape ); 38 | element.append( svg ); 39 | }; 40 | 41 | PrevNextButton.prototype.createSVG = function( label, arrowShape ) { 42 | let svg = document.createElementNS( svgURI, 'svg' ); 43 | svg.setAttribute( 'class', 'flickity-button-icon' ); 44 | svg.setAttribute( 'viewBox', '0 0 100 100' ); 45 | // add title #1189 46 | let title = document.createElementNS( svgURI, 'title' ); 47 | title.append( label ); 48 | // add path 49 | let path = document.createElementNS( svgURI, 'path' ); 50 | let pathMovements = getArrowMovements( arrowShape ); 51 | path.setAttribute( 'd', pathMovements ); 52 | path.setAttribute( 'class', 'arrow' ); 53 | // rotate arrow 54 | if ( !this.isLeft ) { 55 | path.setAttribute( 'transform', 'translate(100, 100) rotate(180)' ); 56 | } 57 | svg.append( title, path ); 58 | return svg; 59 | }; 60 | 61 | // get SVG path movmement 62 | function getArrowMovements( shape ) { 63 | // use shape as movement if string 64 | if ( typeof shape == 'string' ) return shape; 65 | 66 | let { x0, x1, x2, x3, y1, y2 } = shape; 67 | 68 | // create movement string 69 | return `M ${x0}, 50 70 | L ${x1}, ${y1 + 50} 71 | L ${x2}, ${y2 + 50} 72 | L ${x3}, 50 73 | L ${x2}, ${50 - y2} 74 | L ${x1}, ${50 - y1} 75 | Z`; 76 | } 77 | 78 | // ----- ----- // 79 | 80 | PrevNextButton.prototype.enable = function() { 81 | this.element.removeAttribute('disabled'); 82 | }; 83 | 84 | PrevNextButton.prototype.disable = function() { 85 | this.element.setAttribute( 'disabled', true ); 86 | }; 87 | 88 | // -------------------------- Flickity prototype -------------------------- // 89 | 90 | Object.assign( Flickity.defaults, { 91 | prevNextButtons: true, 92 | arrowShape: { 93 | x0: 10, 94 | x1: 60, y1: 50, 95 | x2: 70, y2: 40, 96 | x3: 30, 97 | }, 98 | } ); 99 | 100 | Flickity.create.prevNextButtons = function() { 101 | if ( !this.options.prevNextButtons ) return; 102 | 103 | let { rightToLeft, arrowShape } = this.options; 104 | let prevDirection = rightToLeft ? 'right' : 'left'; 105 | let nextDirection = rightToLeft ? 'left' : 'right'; 106 | this.prevButton = new PrevNextButton( 'previous', prevDirection, arrowShape ); 107 | this.nextButton = new PrevNextButton( 'next', nextDirection, arrowShape ); 108 | this.focusableElems.push( this.prevButton.element ); 109 | this.focusableElems.push( this.nextButton.element ); 110 | 111 | this.handlePrevButtonClick = () => { 112 | this.uiChange(); 113 | this.previous(); 114 | }; 115 | 116 | this.handleNextButtonClick = () => { 117 | this.uiChange(); 118 | this.next(); 119 | }; 120 | 121 | this.on( 'activate', this.activatePrevNextButtons ); 122 | this.on( 'select', this.updatePrevNextButtons ); 123 | }; 124 | 125 | let proto = Flickity.prototype; 126 | 127 | proto.updatePrevNextButtons = function() { 128 | let lastIndex = this.slides.length ? this.slides.length - 1 : 0; 129 | this.updatePrevNextButton( this.prevButton, 0 ); 130 | this.updatePrevNextButton( this.nextButton, lastIndex ); 131 | }; 132 | 133 | proto.updatePrevNextButton = function( button, disabledIndex ) { 134 | // enable is wrapAround and at least 2 slides 135 | if ( this.isWrapping && this.slides.length > 1 ) { 136 | button.enable(); 137 | return; 138 | } 139 | 140 | let isEnabled = this.selectedIndex !== disabledIndex; 141 | button[ isEnabled ? 'enable' : 'disable' ](); 142 | // if disabling button that is focused, 143 | // shift focus to element to maintain keyboard accessibility 144 | let isDisabledFocused = !isEnabled && document.activeElement === button.element; 145 | if ( isDisabledFocused ) this.focus(); 146 | }; 147 | 148 | proto.activatePrevNextButtons = function() { 149 | this.prevButton.element.addEventListener( 'click', this.handlePrevButtonClick ); 150 | this.nextButton.element.addEventListener( 'click', this.handleNextButtonClick ); 151 | this.element.prepend( this.prevButton.element, this.nextButton.element ); 152 | this.on( 'deactivate', this.deactivatePrevNextButtons ); 153 | }; 154 | 155 | proto.deactivatePrevNextButtons = function() { 156 | this.prevButton.element.remove(); 157 | this.nextButton.element.remove(); 158 | this.prevButton.element.removeEventListener( 'click', this.handlePrevButtonClick ); 159 | this.nextButton.element.removeEventListener( 'click', this.handleNextButtonClick ); 160 | this.off( 'deactivate', this.deactivatePrevNextButtons ); 161 | }; 162 | 163 | // -------------------------- -------------------------- // 164 | 165 | Flickity.PrevNextButton = PrevNextButton; 166 | 167 | return Flickity; 168 | 169 | } ) ); 170 | -------------------------------------------------------------------------------- /js/slide.js: -------------------------------------------------------------------------------- 1 | // slide 2 | ( function( window, factory ) { 3 | // universal module definition 4 | if ( typeof module == 'object' && module.exports ) { 5 | // CommonJS 6 | module.exports = factory(); 7 | } else { 8 | // browser global 9 | window.Flickity = window.Flickity || {}; 10 | window.Flickity.Slide = factory(); 11 | } 12 | 13 | }( typeof window != 'undefined' ? window : this, function factory() { 14 | 15 | function Slide( beginMargin, endMargin, cellAlign ) { 16 | this.beginMargin = beginMargin; 17 | this.endMargin = endMargin; 18 | this.cellAlign = cellAlign; 19 | this.cells = []; 20 | this.outerWidth = 0; 21 | this.height = 0; 22 | } 23 | 24 | let proto = Slide.prototype; 25 | 26 | proto.addCell = function( cell ) { 27 | this.cells.push( cell ); 28 | this.outerWidth += cell.size.outerWidth; 29 | this.height = Math.max( cell.size.outerHeight, this.height ); 30 | // first cell stuff 31 | if ( this.cells.length === 1 ) { 32 | this.x = cell.x; // x comes from first cell 33 | this.firstMargin = cell.size[ this.beginMargin ]; 34 | } 35 | }; 36 | 37 | proto.updateTarget = function() { 38 | let lastCell = this.getLastCell(); 39 | let lastMargin = lastCell ? lastCell.size[ this.endMargin ] : 0; 40 | let slideWidth = this.outerWidth - ( this.firstMargin + lastMargin ); 41 | this.target = this.x + this.firstMargin + slideWidth * this.cellAlign; 42 | }; 43 | 44 | proto.getLastCell = function() { 45 | return this.cells[ this.cells.length - 1 ]; 46 | }; 47 | 48 | proto.select = function() { 49 | this.cells.forEach( ( cell ) => cell.select() ); 50 | }; 51 | 52 | proto.unselect = function() { 53 | this.cells.forEach( ( cell ) => cell.unselect() ); 54 | }; 55 | 56 | proto.getCellElements = function() { 57 | return this.cells.map( ( cell ) => cell.element ); 58 | }; 59 | 60 | return Slide; 61 | 62 | } ) ); 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flickity", 3 | "version": "3.0.0", 4 | "description": "Touch, responsive, flickable carousels", 5 | "main": "js/index.js", 6 | "style": "css/flickity.css", 7 | "scripts": { 8 | "test": "npm run lint && echo \"View test/ in browser\" && exit 1", 9 | "lintJs": "npx eslint .", 10 | "lintJson": "node bin/lint-json.js", 11 | "lintCss": "npx stylelint '**/*.css'", 12 | "lint": "npm run lintJson && npm run lintJs && npm run lintCss", 13 | "dist": "npm run bundleCss && npm run bundleJs", 14 | "bundleCss": "cp css/flickity.css dist/flickity.css && node bin/bundle-css.js", 15 | "bundleJs": "node bin/bundle-js.js", 16 | "version": "node bin/version.js && npm run dist && git add -A css js dist" 17 | }, 18 | "dependencies": { 19 | "ev-emitter": "^2.1.2", 20 | "fizzy-ui-utils": "^3.0.0", 21 | "get-size": "^3.0.0", 22 | "imagesloaded": "^5.0.0", 23 | "unidragger": "^3.0.1" 24 | }, 25 | "devDependencies": { 26 | "clean-css": "^5.2.2", 27 | "eslint": "^8.10.0", 28 | "eslint-plugin-metafizzy": "^2.0.1", 29 | "jquery-bridget": "^3.0.1", 30 | "qunit": "^2.17.2", 31 | "stylelint": "^14.2.0", 32 | "stylelint-config-standard": "^24.0.0", 33 | "terser": "^5.10.0" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git://github.com/metafizzy/flickity.git" 38 | }, 39 | "keywords": [ 40 | "touch", 41 | "responsive", 42 | "flick", 43 | "slider", 44 | "carousel", 45 | "gallery", 46 | "DOM", 47 | "browser" 48 | ], 49 | "author": "Metafizzy", 50 | "license": "GPL-3.0", 51 | "bugs": { 52 | "url": "https://github.com/metafizzy/flickity/issues" 53 | }, 54 | "homepage": "https://flickity.metafizzy.co", 55 | "directories": { 56 | "test": "test" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sandbox/adaptive-height.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | slides 8 | 9 | 10 | 11 | 51 | 52 | 53 | 54 | 55 |

slides

56 | 57 | 70 | 71 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /sandbox/add-remove.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | add/remove cells 8 | 9 | 10 | 11 | 12 | 20 | 21 | 22 | 23 | 24 |

add/remove cells

25 | 26 |
27 |
28 |
1
29 |
2
30 |
3
31 |
4
32 |
5
33 |
6
34 |
35 |

36 | 37 | 38 | 39 |

40 |
41 | 42 |

freeScroll

43 | 44 |
45 |
47 |
1
48 |
2
49 |
3
50 |
4
51 |
5
52 |
53 |

54 | 55 | 56 | 57 |

58 |
59 | 60 |

wrapAround

61 | 62 |
63 |
65 |
1
66 |
2
67 |
3
68 |
4
69 |
5
70 |
6
71 |
72 |

73 | 74 | 75 | 76 |

77 |
78 | 79 |

wrapAround, freeScroll

80 | 81 |
82 |
84 |
1
85 |
2
86 |
3
87 |
4
88 |
5
89 |
6
90 |
91 |

92 | 93 | 94 | 95 |

96 |
97 | 98 |

contain

99 | 100 |
101 |
103 |
1
104 |
2
105 |
3
106 |
4
107 |
5
108 |
6
109 |
110 |

111 | 112 | 113 | 114 |

115 |
116 | 117 | 118 |

reposition

119 | 120 |
121 |
122 |
1
123 |
2
124 |
3
125 |
4
126 |
5
127 |
6
128 |
7
129 |
8
130 |
9
131 |
132 |
133 | 134 |

prepend with single #492

135 |
136 |
137 |
1
138 |
139 |

140 | 141 |

142 |
143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /sandbox/ajax.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ajax 8 | 9 | 10 | 19 | 20 | 21 | 22 | 23 |

ajax

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /sandbox/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | basic1 8 | 9 | 10 | 11 | 12 | 39 | 40 | 41 | 42 | 43 |

basic1

44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 68 |
69 |
70 | 76 |
77 |
78 |

go to example.com

79 |

80 |

81 | 82 | 83 | 84 | 85 |

86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | 98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | 108 |

contain

109 | 110 |
112 |
1
113 |
2
114 |
3
115 |
4
116 |
5
117 |
6
118 |
119 | 120 |

contain, freeScroll

121 | 122 |
124 |
1
125 |
2
126 |
3
127 |
4
128 |
5
129 |
6
130 |
7
131 |
8
132 |
9
133 |
134 | 135 |

contain, few

136 | 137 |
139 |
1
140 |
2
141 | 142 |
143 | 144 |

watch, activate >900px

145 | 146 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /sandbox/freescroll.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | freescroll 8 | 9 | 10 | 44 | 45 | 46 | 47 | 48 |

freescroll

49 | 50 | 62 | 63 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /sandbox/group-cells.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | group cells 8 | 9 | 10 | 11 | 51 | 52 | 53 | 54 | 55 |

group cells

56 | 57 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /sandbox/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jquery 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

jquery

15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 26 |
27 |
1
28 |
2
29 |
3
30 |
4
31 |
5
32 |
6
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /sandbox/js/add-remove.js: -------------------------------------------------------------------------------- 1 | let cellCount = 6; 2 | 3 | function getRandom( ary ) { 4 | let index = Math.floor( Math.random() * ary.length ); 5 | return ary[ index ]; 6 | } 7 | 8 | let widthClasses = [ '', 'w2', 'w3' ]; 9 | let nClasses = 'n1 n2 n3 n4 n5 n6'.split(' '); 10 | 11 | function makeCell() { 12 | let cell = document.createElement('div'); 13 | cell.className = `cell ${getRandom( widthClasses )} ${getRandom( nClasses )}`; 14 | let b = document.createElement('b'); 15 | b.textContent = ++cellCount; 16 | cell.appendChild( b ); 17 | let removeButton = document.createElement('button'); 18 | removeButton.className = 'remove-button'; 19 | removeButton.textContent = '×'; 20 | cell.appendChild( removeButton ); 21 | return cell; 22 | } 23 | 24 | function makeCells() { 25 | return [ makeCell(), makeCell(), makeCell() ]; 26 | } 27 | 28 | // init 29 | [ ...document.querySelectorAll('.demo') ].forEach( ( demo ) => { 30 | let container = demo.querySelector('.container'); 31 | let flkty = Flickity.data( container ); 32 | 33 | demo.querySelector('.container').addEventListener( 'click', function( event ) { 34 | if ( event.target.matches('.remove-button') ) return; 35 | 36 | let cellElement = event.target.closest('.cell'); 37 | flkty.remove( cellElement ); 38 | } ); 39 | 40 | demo.querySelector('.prepend-button').addEventListener( 'click', function() { 41 | flkty.prepend( makeCells() ); 42 | } ); 43 | 44 | demo.querySelector('.insert-button').addEventListener( 'click', function() { 45 | flkty.insert( makeCells(), 3 ); 46 | } ); 47 | 48 | demo.querySelector('.append-button').addEventListener( 'click', function() { 49 | flkty.append( makeCells() ); 50 | } ); 51 | 52 | } ); 53 | 54 | // ----- reposition ----- // 55 | 56 | ( function() { 57 | let flkty = new Flickity('#reposition .container'); 58 | flkty.on( 'staticClick', function( event, pointer, cellElem ) { 59 | if ( !cellElem ) return; 60 | 61 | cellElem.classList.toggle('w3'); 62 | flkty.reposition(); 63 | } ); 64 | } )(); 65 | 66 | // ----- prepend single, #492 ----- // 67 | 68 | ( function() { 69 | let demo = document.querySelector('#prepend-single'); 70 | let flkty = new Flickity( demo.querySelector('.container') ); 71 | demo.querySelector('.prepend-button').addEventListener( 'click', function() { 72 | flkty.prepend( makeCell() ); 73 | } ); 74 | } )(); 75 | -------------------------------------------------------------------------------- /sandbox/js/basic.js: -------------------------------------------------------------------------------- 1 | let flky = window.flky = new Flickity('#full-width'); 2 | 3 | // flky.on( 'dragMove', function( event, pointer ) { 4 | // console.log( event.type, pointer.pageX, pointer.pageY ); 5 | // }); 6 | flky.on( 'select', function() { 7 | console.log( 'selected', flky.selectedIndex ); 8 | } ); 9 | 10 | flky.on( 'settle', function() { 11 | console.log( 'settled', flky.x ); 12 | } ); 13 | 14 | let halfWidthflky = new Flickity( '#half-width', { 15 | cellAlign: 'left', 16 | } ); 17 | 18 | halfWidthflky.on( 'staticClick', function( event, pointer, cellIndex, cellElement ) { 19 | console.log( cellIndex, cellElement ); 20 | } ); 21 | 22 | new Flickity( '#gallery3', { 23 | } ); 24 | 25 | document.querySelector('#gallery3 button').onclick = function() { 26 | console.log('button click'); 27 | }; 28 | -------------------------------------------------------------------------------- /sandbox/js/jquery.js: -------------------------------------------------------------------------------- 1 | /* globals $ */ 2 | 3 | let $gallery1 = $('#gallery1').flickity(); 4 | let flkty = $gallery1.data('flickity'); 5 | 6 | // $gallery1.on( 'dragMove', function( event, pointer ) { 7 | // console.log( event.type, pointer.pageX, pointer.pageY ); 8 | // }); 9 | 10 | $gallery1.on( 'cellSelect.flickity', function( event ) { 11 | console.log( 'selected', event.type, `ns:${event.namespace}`, flkty.selectedIndex ); 12 | } ); 13 | 14 | $gallery1.on( 'settle.flickity', function( event ) { 15 | console.log( 'settled', flkty.x, event.type ); 16 | } ); 17 | 18 | $gallery1.on( 'staticClick.flickity', function( event, pointer, cellElem, cellIndex ) { 19 | console.log( 'staticClick', event.type, cellIndex ); 20 | } ); 21 | 22 | $('#gallery2').flickity({ 23 | wrapAround: true, 24 | }); 25 | -------------------------------------------------------------------------------- /sandbox/js/scroll-event.js: -------------------------------------------------------------------------------- 1 | let flkty = new Flickity( '.carousel1', { 2 | initialIndex: 2, 3 | // groupCells: true, 4 | // wrapAround: true, 5 | // cellAlign: 'right' 6 | } ); 7 | 8 | let progressBar = document.querySelector('.progress-bar'); 9 | 10 | flkty.on( 'scroll', function( progress ) { 11 | console.log( progress ); 12 | let width = Math.max( 0, Math.min( 1, progress ) ); 13 | progressBar.style.width = width * 100 + '%'; 14 | } ); 15 | 16 | flkty.reposition(); 17 | 18 | // ----- ----- // 19 | 20 | let paraBG = document.querySelector('.parallax__layer--bg'); 21 | let paraFG = document.querySelector('.parallax__layer--fg'); 22 | 23 | let paraFlkty = new Flickity( '.parallax__carousel', { 24 | 25 | } ); 26 | 27 | let cellRatio = 0.6; 28 | let bgRatio = 0.8; 29 | let fgRatio = 1.25; 30 | 31 | paraFlkty.on( 'scroll', function( progress ) { 32 | // console.log( progress ); 33 | paraBG.style.left = ( 0.5 - ( 0.5 + progress * 4 ) * cellRatio * bgRatio ) * 100 + '%'; 34 | paraFG.style.left = ( 0.5 - ( 0.5 + progress * 4 ) * cellRatio * fgRatio ) * 100 + '%'; 35 | } ); 36 | 37 | paraFlkty.reposition(); 38 | 39 | // ----- ----- // 40 | 41 | let imgFlkty = new Flickity( '.image-carousel', { 42 | } ); 43 | 44 | window.onload = function() { 45 | imgFlkty.reposition(); 46 | }; 47 | 48 | let imgs = document.querySelectorAll('.image-carousel img'); 49 | 50 | imgFlkty.on( 'scroll', function() { 51 | imgFlkty.slides.forEach( ( slide, i ) => { 52 | let img = imgs[i]; 53 | let x = ( slide.target + imgFlkty.x ) * -0.333; 54 | img.style.transform = `translateX(${x}px)`; 55 | } ); 56 | } ); 57 | -------------------------------------------------------------------------------- /sandbox/js/tricky-drag.js: -------------------------------------------------------------------------------- 1 | let nonDragFlkty = new Flickity( '.carousel--non-drag', { 2 | draggable: false, 3 | } ); 4 | 5 | function onStaticClick( event, pointer, cellElem, cellIndex ) { 6 | console.log( 'staticClick', this.element.className, cellIndex ); 7 | } 8 | 9 | nonDragFlkty.on( 'staticClick', onStaticClick ); 10 | 11 | let singleCellFlkty = new Flickity('.carousel--single-cell'); 12 | singleCellFlkty.on( 'staticClick', onStaticClick ); 13 | 14 | let groupFlkty = new Flickity( '.carousel--group', { 15 | groupCells: true, 16 | } ); 17 | 18 | groupFlkty.on( 'staticClick', function( event ) { 19 | let cellElem = event.target.closest('.carousel-cell'); 20 | if ( cellElem ) groupFlkty.remove( cellElem ); 21 | } ); 22 | 23 | function makeGroupCell() { 24 | let cell = document.createElement('div'); 25 | cell.className = 'carousel-cell'; 26 | let b = document.createElement('b'); 27 | b.textContent = groupFlkty.cells.length + 1; 28 | cell.appendChild( b ); 29 | return cell; 30 | } 31 | 32 | document.querySelector('.add-group-cell-button').onclick = function() { 33 | groupFlkty.append( makeGroupCell() ); 34 | }; 35 | -------------------------------------------------------------------------------- /sandbox/js/v2-sizzle.js: -------------------------------------------------------------------------------- 1 | let flkty = new Flickity( '.carousel', { 2 | groupCells: true, 3 | adaptiveHeight: true, 4 | wrapAround: true, 5 | } ); 6 | 7 | let paraBg = document.querySelector('.parallax-layer--bg'); 8 | let paraFg = document.querySelector('.parallax-layer--fg'); 9 | let count = flkty.slides.length - 1; 10 | 11 | flkty.on( 'scroll', function( progress ) { 12 | paraBg.style.left = ( 0.5 - ( 0.5 + progress * count ) * 0.7 ) * ( 37/36 ) * 100 + '%'; 13 | paraFg.style.left = ( 0.5 - ( 0.5 + progress * count ) * 1.5 ) * ( 37/36 ) * 100 + '%'; 14 | } ); 15 | 16 | flkty.reposition(); 17 | -------------------------------------------------------------------------------- /sandbox/js/wrap-around.js: -------------------------------------------------------------------------------- 1 | window.flkty = new Flickity( '#gallery1', { 2 | wrapAround: true, 3 | } ); 4 | 5 | window.flkty2 = new Flickity( '#gallery2', { 6 | } ); 7 | 8 | window.flkty6 = new Flickity( '#gallery6', { 9 | wrapAround: true, 10 | cellAlign: 'left', 11 | } ); 12 | 13 | window.flkty4 = new Flickity( '#gallery4', { 14 | wrapAround: true, 15 | freeScroll: true, 16 | } ); 17 | 18 | window.flky5 = new Flickity( '#gallery5', { 19 | freeScroll: true, 20 | } ); 21 | -------------------------------------------------------------------------------- /sandbox/lazyload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | lazyload 8 | 9 | 10 | 11 | 49 | 50 | 51 | 52 | 53 |

lazyload

54 | 55 | 66 | 67 | 79 | 80 | 101 | 102 | 113 | 114 |

srcset

115 | 116 | 160 | 161 | 205 | 206 |

<picture>

207 | 208 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 262 | 263 | 264 | 265 | -------------------------------------------------------------------------------- /sandbox/media.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | media 8 | 9 | 10 | 11 | 12 | 40 | 41 | 42 | 43 | 44 |

media

45 | 46 | 47 | 60 | 61 | 93 | 94 | 113 | 114 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /sandbox/right-to-left.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | right to left 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 |

right to left

19 | 20 |
22 |
1
23 |
2
24 |
3
25 |
4
26 |
5
27 |
6
28 |
29 | 30 |
32 |
1
33 |
2
34 |
3
35 |
4
36 |
5
37 |
6
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /sandbox/sandbox.css: -------------------------------------------------------------------------------- 1 | * { box-sizing: border-box; } 2 | 3 | body { 4 | font-family: sans-serif; 5 | color: #333; 6 | } 7 | 8 | .container { 9 | border: 1px solid; 10 | margin-bottom: 50px; 11 | } 12 | 13 | .container::after { 14 | content: ''; 15 | display: block; 16 | clear: both; 17 | } 18 | 19 | .container:focus { border: 1px blue dotted; } 20 | 21 | .cell { 22 | width: 100%; 23 | height: 200px; 24 | border: 0 solid white; 25 | background: #CCC; 26 | } 27 | 28 | .cell.is-selected { 29 | outline: 4px solid hsla(0, 0%, 0%, 25%); 30 | outline-offset: -4px; 31 | } 32 | 33 | .cell:nth-child(6n) { background: hsl(0, 80%, 70%); } 34 | .cell:nth-child(6n+1) { background: hsl(60, 80%, 70%); } 35 | .cell:nth-child(6n+2) { background: hsl(120, 80%, 70%); } 36 | .cell:nth-child(6n+3) { background: hsl(180, 80%, 70%); } 37 | .cell:nth-child(6n+4) { background: hsl(240, 80%, 70%); } 38 | .cell:nth-child(6n+5) { background: hsl(300, 80%, 70%); } 39 | 40 | .cell.n1 { background: hsl(0, 80%, 70%); } 41 | .cell.n2 { background: hsl(60, 80%, 70%); } 42 | .cell.n3 { background: hsl(120, 80%, 70%); } 43 | .cell.n4 { background: hsl(180, 80%, 70%); } 44 | .cell.n5 { background: hsl(240, 80%, 70%); } 45 | .cell.n6 { background: hsl(300, 80%, 70%); } 46 | 47 | .variable-width .cell { 48 | width: 20%; 49 | margin-right: 3%; 50 | } 51 | 52 | .variable-width .cell.w2 { width: 30%; } 53 | .variable-width .cell.w3 { width: 40%; } 54 | 55 | .fixed-width .cell { 56 | width: 200px; 57 | margin-right: 20px; 58 | } 59 | 60 | #half-width .cell { 61 | width: 48%; 62 | margin-right: 4%; 63 | } 64 | 65 | /* big number */ 66 | .cell b { 67 | display: block; 68 | font-size: 100px; 69 | color: white; 70 | font-weight: bold; 71 | position: absolute; 72 | left: 10px; 73 | top: 10px; 74 | } 75 | 76 | /* ---- couning ---- */ 77 | .counting { 78 | counter-reset: cell; 79 | } 80 | 81 | .counting .cell::before { 82 | counter-increment: cell; 83 | content: counter(cell); 84 | display: block; 85 | font-size: 100px; 86 | color: white; 87 | font-weight: bold; 88 | position: absolute; 89 | left: 10px; 90 | top: 10px; 91 | } 92 | -------------------------------------------------------------------------------- /sandbox/scroll-event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | scroll event 8 | 9 | 10 | 11 | 125 | 126 | 127 | 128 | 129 |

scroll event

130 | 131 | 144 | 145 |
146 | 147 |
148 | 149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 | 157 | 164 | 165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 | 173 |
174 | 175 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /sandbox/single.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | single 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 |

single

21 | 22 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /sandbox/styles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | styles 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

styles

16 | 17 |
18 |
1
19 |
2
20 |
3
21 |
4
22 |
5
23 |
24 | 25 |
26 |
1
27 |
2
28 |
3
29 |
4
30 |
5
31 |
32 | 33 |
34 |
1
35 |
2
36 |
3
37 |
4
38 |
5
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /sandbox/tricky-drag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | tricky drag 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 | 38 |

tricky drag

39 | 40 | 49 | 50 | 53 | 54 | 58 | 59 |

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /sandbox/v2-sizzle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | v2 sizzle 8 | 9 | 10 | 11 | 133 | 134 | 135 | 136 | 137 |
138 |
139 | 143 |
144 |
145 |
146 | 147 | 166 | 167 |
168 |
Parallax
169 |
whoa
170 |
Flickity v2
171 |
172 |
173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /sandbox/wrap-around.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | wrap around 8 | 9 | 10 | 11 | 12 | 28 | 29 | 30 | 31 | 32 |

wrap around

33 | 34 |
35 |
1
36 |
2
37 |
3
38 |
4
39 |
5
40 |
6
41 |
42 | 43 |
44 |
1
45 |
2
46 |
3
47 |
4
48 |
5
49 |
6
50 |
51 | 52 |

Left aligned

53 | 54 |
55 |
1
56 |
2
57 |
3
58 |
4
59 |
5
60 |
6
61 |
62 | 63 |

Fixed-width cells, pixel-positioning HTML init

64 | 65 |
67 |
1
68 |
2
69 |
3
70 |
4
71 |
5
72 |
6
73 |
7
74 |
8
75 |
76 | 77 |

HTML init

78 | 79 |
81 |
1
82 |
2
83 |
3
84 |
4
85 |
5
86 |
6
87 |
88 | 89 |

freeScroll

90 | 91 |
92 |
1
93 |
2
94 |
3
95 |
4
96 |
5
97 |
6
98 |
99 | 100 |
101 |
1
102 |
2
103 |
3
104 |
4
105 |
5
106 |
6
107 |
108 | 109 |

no margin for error

110 |
112 |
1
113 |
2
114 |
3
115 |
4
116 |
5
117 |
118 | 119 |

tight wrap #589

120 |
121 |
1
122 |
2
123 |
3
124 |
4
125 |
126 | 127 |

wrapAround: 'fill', should disabled

128 |
129 |
1
130 |
2
131 |
3
132 |
4
133 |
134 | 135 |

wrapAround: 'fill', should enabled

136 |
137 |
1
138 |
2
139 |
3
140 |
4
141 |
5
142 |
143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'stylelint-config-standard', 3 | ignoreFiles: [ 'dist/*' ], 4 | rules: { 5 | 'color-function-notation': 'legacy', 6 | 'color-hex-case': 'upper', 7 | 'comment-empty-line-before': null, 8 | 'declaration-block-no-duplicate-properties': [ true, { 9 | ignore: [ 'consecutive-duplicates-with-different-values' ], 10 | } ], 11 | 'hue-degree-notation': 'number', 12 | 'property-no-vendor-prefix': null, 13 | 'selector-class-pattern': null, 14 | 'string-quotes': 'single', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /test/drag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Drag tests 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 |
45 |

drag and drag with wrapAround tests can be buggy. Try running them again.

46 | 47 |

drag

48 | 56 | 57 |

drag wrapAround

58 | 66 |
67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Flickity tests 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 |

Init

64 | 65 | 73 | 74 |

cellSelector

75 | 76 | 86 | 87 |

empty

88 | 89 | 90 | 91 |

getParentCell

92 | 93 | 101 |
102 | 103 |

position cells

104 | 105 | 113 | 114 |

contain

115 | 116 | 124 | 125 |

auto-play

126 | 131 | 132 |

prev/next buttons

133 | 141 | 142 |

page dots

143 | 151 | 152 |

getWrapCells

153 | 161 | 162 |

watch

163 | 171 | 172 |

resize

173 | 181 | 182 |

add/remove-cells

183 | 191 | 192 |

destroy

193 | 201 | 202 |

lazyload

203 | 214 | 215 |

lazyload srcset

216 | 235 | 236 |

groupCells

237 | 259 | 260 |

adaptiveHeight

261 | 273 | 274 |

selectCell

275 | 285 | 286 |

change

287 | 294 | 295 |

initialIndex

296 | 308 | 309 |

wrapAround: fill

310 | 317 | 318 |

imagesLoaded

319 | 330 | 331 | 342 | 343 | 344 | 345 | -------------------------------------------------------------------------------- /test/test.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable no-descending-specificity */ 2 | 3 | body { 4 | font-family: sans-serif; 5 | color: #333; 6 | } 7 | 8 | /* move over qunit window to reduce overlaps */ 9 | @media (min-height: 500px) { 10 | #qunit { 11 | left: 420px; 12 | } 13 | } 14 | 15 | .gallery { 16 | border: 1px solid; 17 | width: 400px; 18 | margin-bottom: 40px; 19 | } 20 | 21 | .gallery--imagesloaded img { 22 | display: block; 23 | height: 140px; 24 | } 25 | 26 | .gallery .cell { 27 | width: 100%; 28 | height: 100px; 29 | background: #F09; 30 | font-size: 40px; 31 | color: white; 32 | } 33 | 34 | .variable-width .cell { width: 25%; } /* 100px */ 35 | .variable-width .cell.width2 { 36 | width: 40%; /* 160px */ 37 | background: #F90; 38 | } 39 | 40 | .variable-width .cell.width3 { 41 | width: 60%; /* 240px */ 42 | background: #09F; 43 | } 44 | 45 | #position-cells.percent-margin .cell { margin: 0 2%; } 46 | #position-cells.pixel-margin .cell { margin: 0 10px; } 47 | 48 | .drag .cell { margin-right: 5%; } 49 | 50 | #watch.has-after::after { 51 | content: 'flickity'; 52 | display: none; 53 | } 54 | 55 | #lazyload img { 56 | display: block; 57 | max-height: 100px; 58 | } 59 | 60 | /* ---- group-cells ---- */ 61 | 62 | #group-cells .cell { 63 | width: 100px; 64 | } 65 | 66 | #group-cells .cell--width2 { width: 200px; } 67 | #group-cells .cell--width3 { width: 300px; } 68 | #group-cells .cell--width4 { width: 400px; } 69 | 70 | #group-cells.is-expanded { width: 600px; } 71 | 72 | #group-cells .cell:nth-child(2n) { background: #09F; } 73 | 74 | /* ---- adaptive-height ---- */ 75 | 76 | #adaptive-height .cell { width: 33.33%; } 77 | 78 | #adaptive-height .cell--height2 { height: 200px; } 79 | #adaptive-height .cell--height3 { height: 300px; } 80 | #adaptive-height .cell--height4 { height: 400px; } 81 | 82 | #adaptive-height .cell:nth-child(2n) { background: #09F; } 83 | 84 | /* ---- select-cell ---- */ 85 | 86 | #select-cell .cell { width: 33.33%; } 87 | 88 | /* ---- wrap-around-fill ---- */ 89 | 90 | .variable-width .cell.cell--wrap-around-short { 91 | width: 20%; 92 | } 93 | -------------------------------------------------------------------------------- /test/unit/adaptive-height.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'adaptiveHeight', function( assert ) { 2 | 3 | let flkty = new Flickity( '#adaptive-height', { 4 | adaptiveHeight: true, 5 | } ); 6 | 7 | // 2,1,3, 1,4,2, 1,2,1 8 | 9 | function checkSelectHeight( index, height ) { 10 | flkty.select( index, false, true ); 11 | assert.equal( flkty.viewport.style.height, `${height}px`, `slide ${index}` ); 12 | } 13 | 14 | checkSelectHeight( 0, 200 ); 15 | checkSelectHeight( 1, 100 ); 16 | checkSelectHeight( 2, 300 ); 17 | checkSelectHeight( 3, 100 ); 18 | checkSelectHeight( 4, 400 ); 19 | checkSelectHeight( 5, 200 ); 20 | 21 | flkty.options.groupCells = true; 22 | flkty.resize(); 23 | 24 | checkSelectHeight( 0, 300 ); 25 | checkSelectHeight( 1, 400 ); 26 | checkSelectHeight( 2, 200 ); 27 | 28 | } ); 29 | -------------------------------------------------------------------------------- /test/unit/add-remove-cells.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'add/remove cells', function( assert ) { 2 | 3 | function makeCellElem() { 4 | let cellElem = document.createElement('div'); 5 | cellElem.className = 'cell'; 6 | return cellElem; 7 | } 8 | 9 | // position values can be off by 0.1% or 1px 10 | function isPositionApprox( value, expected ) { 11 | value = value.replace( 'translateX(', '' ).replace( ')', '' ); 12 | let isPercent = value.indexOf('%') !== -1; 13 | value = parseFloat( value ); 14 | let diff = Math.abs( expected - value ); 15 | return isPercent ? diff < 0.1 : diff <= 1; 16 | } 17 | 18 | let elem = document.querySelector('#add-remove-cells'); 19 | let flkty = new Flickity( elem ); 20 | let sliderElem = elem.querySelector('.flickity-slider'); 21 | 22 | function checkCellElem( cellElem, index, message ) { 23 | assert.equal( sliderElem.children[ index ], cellElem, 24 | message + ' cell element in DOM correct' ); 25 | assert.equal( flkty.cells[ index ].element, cellElem, 26 | message + ' element added as cell' ); 27 | assert.ok( isPositionApprox( cellElem.style.transform, index * 100 ), 28 | ` element positioned ${index * 100}` ); 29 | } 30 | 31 | // prepend cell element 32 | let cellElem = makeCellElem(); 33 | flkty.prepend( cellElem ); 34 | checkCellElem( cellElem, 0, 'prepended' ); 35 | assert.equal( flkty.selectedIndex, 1, 'selectedIndex +1 after prepend' ); 36 | // append cell element 37 | cellElem = makeCellElem(); 38 | flkty.append( cellElem ); 39 | let lastIndex = flkty.cells.length - 1; 40 | checkCellElem( cellElem, lastIndex, 'appended' ); 41 | assert.equal( flkty.selectedIndex, 1, 'selectedIndex same after prepend' ); 42 | // insert single cell element 43 | cellElem = makeCellElem(); // this one gets removed first 44 | flkty.select( 2 ); 45 | flkty.insert( cellElem, 2 ); 46 | checkCellElem( cellElem, 2, 'single inserted' ); 47 | assert.equal( flkty.selectedIndex, 3, 'selectedIndex +1 after insert before' ); 48 | flkty.insert( makeCellElem(), 4 ); 49 | assert.equal( flkty.selectedIndex, 3, 'selectedIndex same after insert before' ); 50 | // insert multiple cell elements 51 | let cellElems = [ makeCellElem(), makeCellElem(), makeCellElem() ]; 52 | flkty.insert( cellElems, 3 ); 53 | checkCellElem( cellElems[0], 3, 'first multiple inserted' ); 54 | checkCellElem( cellElems[1], 4, 'second multiple inserted' ); 55 | checkCellElem( cellElems[2], 5, 'third multiple inserted' ); 56 | assert.equal( flkty.selectedIndex, 6, 'selectedIndex +6 after 3 insert before' ); 57 | 58 | function checkCellPositions() { 59 | let isGap = false; 60 | flkty.cells.forEach( ( cell, i ) => { 61 | if ( !isPositionApprox( cell.element.style.transform, i * 100 ) ) { 62 | assert.ok( false, `gap in cell position ${i} after removal` ); 63 | isGap = true; 64 | } 65 | } ); 66 | assert.ok( !isGap, 'no gaps in cell positions' ); 67 | } 68 | 69 | // remove single cell element that was inserted 70 | let len = flkty.cells.length; 71 | flkty.remove( cellElem ); 72 | assert.equal( len - sliderElem.children.length, 1, 'element removed from DOM' ); 73 | assert.equal( len - flkty.cells.length, 1, 'cell removed' ); 74 | assert.equal( flkty.selectedIndex, 5, 'selectedIndex -1 after remove before' ); 75 | checkCellPositions(); 76 | // remove multiple 77 | len = flkty.cells.length; 78 | flkty.select( 4 ); 79 | flkty.remove([ cellElems[2], cellElems[0], cellElems[1] ]); 80 | assert.equal( len - sliderElem.children.length, 3, 'elements removed from DOM' ); 81 | assert.equal( len - flkty.cells.length, 3, 'cells removed' ); 82 | checkCellPositions(); 83 | 84 | // remove all cells 85 | flkty.remove( flkty.getCellElements() ); 86 | assert.equal( flkty.cells.length, 0, 'all cells removed' ); 87 | flkty.resize(); 88 | assert.ok( true, 'resize with zero items didnt freak out' ); 89 | 90 | } ); 91 | -------------------------------------------------------------------------------- /test/unit/auto-play.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'auto play', function( assert ) { 2 | 3 | let done = assert.async(); 4 | 5 | let flkty = new Flickity( '#auto-play', { 6 | autoPlay: 200, 7 | } ); 8 | 9 | let selectCount = 0; 10 | let testDelay = flkty.options.autoPlay + 100; 11 | 12 | let tests; 13 | 14 | function nextTest() { 15 | if ( tests.length ) { 16 | let next = tests.shift(); 17 | return next(); 18 | } else { 19 | flkty.stopPlayer(); 20 | done(); 21 | } 22 | } 23 | 24 | tests = [ 25 | // check that player runs 26 | function() { 27 | flkty.on( 'select', function onSelect() { 28 | selectCount++; 29 | if ( selectCount < 5 ) { 30 | assert.equal( flkty.selectedIndex, selectCount % flkty.cells.length, 31 | `auto-played to ${flkty.selectedIndex}` ); 32 | } else if ( selectCount === 5 ) { 33 | // HACK do async, should be able to stop after a tick 34 | flkty.off( 'select', onSelect ); 35 | nextTest(); 36 | } 37 | } ); 38 | }, 39 | // pause & unpause 40 | function() { 41 | function onPauseSelect() { 42 | assert.ok( false, 'player ticked during pause' ); 43 | } 44 | flkty.on( 'select', onPauseSelect ); 45 | flkty.pausePlayer(); 46 | setTimeout( function() { 47 | assert.ok( true, 'player did not tick during pause' ); 48 | flkty.off( 'select', onPauseSelect ); 49 | flkty.once( 'select', function() { 50 | assert.ok( true, 'player resumed after unpausing' ); 51 | nextTest(); 52 | } ); 53 | flkty.unpausePlayer(); 54 | }, testDelay ); 55 | }, 56 | // stopPlayer 57 | function() { 58 | let ticks = 0; 59 | function onSelect() { 60 | ticks++; 61 | } 62 | flkty.stopPlayer(); 63 | setTimeout( function() { 64 | flkty.off( 'select', onSelect ); 65 | assert.equal( ticks, 0, 'no ticks after stopped' ); 66 | nextTest(); 67 | }, testDelay * 2 ); 68 | }, 69 | // double playPlayer() 70 | function() { 71 | let ticks = 0; 72 | function onSelect() { 73 | ticks++; 74 | } 75 | flkty.stopPlayer(); 76 | flkty.on( 'select', onSelect ); 77 | flkty.playPlayer(); 78 | flkty.playPlayer(); 79 | setTimeout( function() { 80 | flkty.off( 'select', onSelect ); 81 | assert.equal( ticks, 1, 'only one tick after double playPlayer' ); 82 | nextTest(); 83 | }, testDelay ); 84 | }, 85 | ]; 86 | 87 | nextTest(); 88 | 89 | } ); 90 | -------------------------------------------------------------------------------- /test/unit/cell-selector.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'cellSelector', function( assert ) { 2 | 3 | let elem = document.querySelector('#cell-selector'); 4 | let notCell1 = elem.querySelector('.not-cell1'); 5 | let notCell2 = elem.querySelector('.not-cell2'); 6 | 7 | let flkty = new Flickity( elem, { 8 | cellSelector: '.cell', 9 | } ); 10 | 11 | let cellsMatchSelector = true; 12 | for ( let cell of flkty.cells ) { 13 | let isMatch = cell.element.matches( flkty.options.cellSelector ); 14 | cellsMatchSelector = cellsMatchSelector && isMatch; 15 | } 16 | 17 | // getCellElements() 18 | let cellElems = flkty.getCellElements(); 19 | let queriedCellElems = elem.querySelectorAll( flkty.options.cellSelector ); 20 | cellElems.forEach( ( cellElem, i ) => { 21 | assert.equal( cellElem, queriedCellElems[i], 22 | 'cell element same as queried cell element' ); 23 | } ); 24 | 25 | assert.ok( cellsMatchSelector, 'all cell elements match cellSelector' ); 26 | 27 | assert.equal( notCell1.parentNode, elem, 'notCell1 parent node is still gallery' ); 28 | assert.equal( notCell2.parentNode, elem, 'notCell2 parent node is still gallery' ); 29 | 30 | } ); 31 | -------------------------------------------------------------------------------- /test/unit/change.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-invalid-this */ 2 | 3 | QUnit.test( 'change', function( assert ) { 4 | 5 | let done = assert.async(); 6 | 7 | function onInitChange() { 8 | assert.ok( false, 'change should not trigger on init' ); 9 | } 10 | 11 | new Flickity( '#change', { 12 | on: { 13 | change: onInitChange, 14 | ready: function() { 15 | // define events last to first for strict 16 | function onChangeC( index ) { 17 | assert.equal( index, 0, 'change triggered on select back to 0' ); 18 | done(); 19 | } 20 | 21 | function onChangeB() { 22 | assert.ok( false, 'change should not trigger on same select' ); 23 | } 24 | 25 | function onSelectB( index ) { 26 | assert.equal( index, 1, 'select triggered on same select 1' ); 27 | this.off( 'change', onChangeB ); 28 | this.once( 'change', onChangeC ); 29 | this.select( 0, false, true ); 30 | } 31 | 32 | function onChangeA( index ) { 33 | assert.equal( index, 1, 'change triggered, selected 1' ); 34 | this.once( 'change', onChangeB ); 35 | this.once( 'select', onSelectB ); 36 | // select 1 again 37 | this.select( 1, false, true ); 38 | } 39 | 40 | // kick off 41 | this.off( 'change', onInitChange ); 42 | this.once( 'change', onChangeA ); 43 | this.select( 1, false, true ); 44 | }, 45 | }, 46 | } ); 47 | 48 | } ); 49 | -------------------------------------------------------------------------------- /test/unit/contain.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'contain', function( assert ) { 2 | 3 | let flkty = new Flickity( '#contain', { 4 | contain: true, 5 | } ); 6 | 7 | assert.equal( Math.round( flkty.x + flkty.cursorPosition ), 0, 8 | 'selected at 0, position left edge' ); 9 | flkty.select( 1 ); 10 | flkty.positionSliderAtSelected(); 11 | assert.equal( Math.round( flkty.x + flkty.cursorPosition ), 0, 12 | 'selected at 1, position left edge' ); 13 | flkty.select( 4 ); 14 | flkty.positionSliderAtSelected(); 15 | let endLimit = flkty.slideableWidth - flkty.size.innerWidth * ( 1 - flkty.cellAlign ); 16 | assert.equal( Math.round( -endLimit ), Math.round( flkty.x ), 17 | 'selected at 4, position right edge' ); 18 | flkty.select( 5 ); 19 | flkty.positionSliderAtSelected(); 20 | assert.equal( Math.round( -endLimit ), Math.round( flkty.x ), 21 | 'selected at 5, position right edge' ); 22 | 23 | } ); 24 | -------------------------------------------------------------------------------- /test/unit/destroy.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'destroy', function( assert ) { 2 | 3 | let elem = document.querySelector('#destroy'); 4 | let flkty = new Flickity( elem ); 5 | 6 | let done = assert.async(); 7 | // do it async 8 | setTimeout( function() { 9 | flkty.destroy(); 10 | assert.strictEqual( elem.flickityGUID, undefined, 'flickityGUID removed' ); 11 | assert.ok( !flkty.isActive, 'not active' ); 12 | assert.ok( !Flickity.data( elem ), '.data() returns falsey' ); 13 | assert.ok( elem.children[0], '.cell', 'cell is back as first child' ); 14 | assert.ok( !elem.matches('.flickity-enabled'), 'flickity-enabled class removed' ); 15 | assert.ok( !elem.querySelector('.flickity-prev-next-button'), 'no buttons' ); 16 | assert.ok( !elem.querySelector('.flickity-page-dots'), 'no page dots' ); 17 | assert.ok( !elem.style.height, 'no height set' ); 18 | assert.ok( !elem.children[0].style.left, 'first cell has no left position' ); 19 | 20 | done(); 21 | }, 20 ); 22 | 23 | } ); 24 | -------------------------------------------------------------------------------- /test/unit/drag.js: -------------------------------------------------------------------------------- 1 | ( function() { 2 | 3 | function noop() {} 4 | 5 | let fakeDrag = window.fakeDrag = function( flkty, positions ) { 6 | 7 | function fakeEvent( type, pageX ) { 8 | return { 9 | type: type, 10 | pageX: pageX, 11 | pageY: 0, 12 | preventDefault: noop, 13 | target: flkty.viewport, 14 | }; 15 | } 16 | 17 | let hasBeenDown = false; 18 | 19 | function triggerEvent() { 20 | let position = positions.shift(); 21 | // down or move event 22 | if ( !hasBeenDown ) { 23 | let downEvent = fakeEvent( 'mousedown', position ); 24 | flkty.pointerDown( downEvent, downEvent ); 25 | hasBeenDown = true; 26 | } else { 27 | let moveEvent = fakeEvent( 'mousemove', position ); 28 | flkty.pointerMove( moveEvent, moveEvent ); 29 | } 30 | 31 | if ( positions.length ) { 32 | // loop next 33 | setTimeout( triggerEvent, 40 ); 34 | } else { 35 | // up event 36 | let upEvent = fakeEvent( 'mouseup', position ); 37 | flkty.pointerUp( upEvent, upEvent ); 38 | } 39 | } 40 | 41 | triggerEvent(); 42 | }; 43 | 44 | let dragTests; 45 | // do each drag test one after another 46 | function getDoNextDragTest( done ) { 47 | return function doNextDragTest() { 48 | if ( dragTests.length ) { 49 | let dragTest = dragTests.shift(); 50 | dragTest(); 51 | } else { 52 | done(); 53 | } 54 | }; 55 | } 56 | 57 | // flickity, dragPositions, index, onSettle, message 58 | function getFakeDragTest( args ) { 59 | let assert = args.assert; 60 | let flkty = args.flickity; 61 | let msgCell = `'slide[${args.index}]'`; 62 | 63 | return function fakeDragTest() { 64 | let selectMsg = `${args.message ? args.message + '. ' : ''} selected ${msgCell}`; 65 | flkty.once( 'select', function() { 66 | assert.equal( flkty.selectedIndex, args.index, selectMsg ); 67 | } ); 68 | 69 | let settleMsg = `${args.message ? args.message + '. ' : ''} settled ${msgCell}`; 70 | let target = flkty.slides[ args.index ].target; 71 | flkty.once( 'settle', function() { 72 | assert.equal( Math.round( -flkty.x ), Math.round( target ), settleMsg ); 73 | setTimeout( args.onSettle ); 74 | } ); 75 | 76 | fakeDrag( args.flickity, args.dragPositions ); 77 | }; 78 | } 79 | 80 | QUnit.test( 'drag', function( assert ) { 81 | // async test 82 | let done = assert.async(); 83 | 84 | let flkty = new Flickity('#drag'); 85 | 86 | let doNextDragTest = getDoNextDragTest( done ); 87 | 88 | function getDragTest( args ) { 89 | args = Object.assign( args, { 90 | assert: assert, 91 | flickity: flkty, 92 | onSettle: doNextDragTest, 93 | } ); 94 | return getFakeDragTest( args ); 95 | } 96 | 97 | dragTests = [ 98 | getDragTest({ 99 | message: 'drag to 2nd cell', 100 | index: 1, 101 | dragPositions: [ 0, -10, -20 ], 102 | }), 103 | getDragTest({ 104 | message: 'drag back to 1st cell', 105 | index: 0, 106 | dragPositions: [ 0, 10, 20 ], 107 | }), 108 | getDragTest({ 109 | message: 'big flick to 3rd cell', 110 | index: 2, 111 | dragPositions: [ 0, -10, -80 ], 112 | }), 113 | // minimal movement to trigger static click 114 | function() { 115 | flkty.once( 'staticClick', function() { 116 | assert.ok( true, 'staticClick fired on non-drag' ); 117 | assert.equal( flkty.selectedIndex, 2, 'selected index still at 2 after click' ); 118 | setTimeout( doNextDragTest ); 119 | } ); 120 | fakeDrag( flkty, [ 0, 1, 0, -2, -1 ] ); 121 | }, 122 | // move out then back to where it started 123 | function() { 124 | flkty.once( 'settle', function() { 125 | assert.equal( flkty.selectedIndex, 2, 'move out then back. same cell' ); 126 | setTimeout( doNextDragTest ); 127 | } ); 128 | fakeDrag( flkty, [ 0, 10, 20, 30, 20 ] ); 129 | }, 130 | getDragTest({ 131 | message: 'drag and try to flick past 6th cell', 132 | index: 5, 133 | dragPositions: [ 0, -10, -50, -77, -100, -125, -150, -175, -250, -350 ], 134 | }), 135 | ]; 136 | 137 | doNextDragTest(); 138 | 139 | } ); 140 | 141 | QUnit.test( 'drag with wrapAround', function( assert ) { 142 | // async test 143 | let done = assert.async(); 144 | 145 | let flkty = new Flickity( '#drag-wrap-around', { 146 | wrapAround: true, 147 | } ); 148 | 149 | let doNextDragTest = getDoNextDragTest( done ); 150 | 151 | function getDragTest( args ) { 152 | args = Object.assign( args, { 153 | assert: assert, 154 | flickity: flkty, 155 | onSettle: doNextDragTest, 156 | } ); 157 | return getFakeDragTest( args ); 158 | } 159 | 160 | dragTests = [ 161 | getDragTest({ 162 | message: 'drag to last cell via wrap-around', 163 | index: 5, 164 | dragPositions: [ 0, 10, 20 ], 165 | }), 166 | getDragTest({ 167 | message: 'drag to first cell via wrap-around', 168 | index: 0, 169 | dragPositions: [ 0, -10, -20 ], 170 | }), 171 | getDragTest({ 172 | message: 'big flick to 5th cell via wrap-around', 173 | index: 4, 174 | dragPositions: [ 0, 10, 80 ], 175 | }), 176 | ]; 177 | 178 | doNextDragTest(); 179 | 180 | } ); 181 | 182 | } )(); 183 | -------------------------------------------------------------------------------- /test/unit/empty.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'empty', function( assert ) { 2 | 3 | let gallery = document.querySelector('#empty'); 4 | 5 | let flkty = new Flickity( gallery ); 6 | 7 | assert.ok( true, 'empty gallery ok' ); 8 | assert.ok( flkty.prevButton.element.disabled, 'previous button disabled' ); 9 | assert.ok( flkty.nextButton.element.disabled, 'next button disabled' ); 10 | assert.equal( flkty.pageDots.dots.length, 0, '0 page dots' ); 11 | 12 | flkty.resize(); 13 | assert.ok( true, 'resize with empty gallery ok' ); 14 | 15 | function makeCellElem() { 16 | let cellElem = document.createElement('div'); 17 | cellElem.className = 'cell'; 18 | return cellElem; 19 | } 20 | 21 | flkty.append( makeCellElem() ); 22 | assert.equal( flkty.cells.length, 1, 'added cell to empty gallery' ); 23 | 24 | assert.ok( flkty.prevButton.element.disabled, 'previous button disabled' ); 25 | assert.ok( flkty.nextButton.element.disabled, 'next button disabled' ); 26 | assert.equal( flkty.pageDots.dots.length, 1, '1 page dots' ); 27 | 28 | // destroy and re-init with higher initialIndex 29 | flkty.destroy(); 30 | flkty = new Flickity( gallery, { 31 | initialIndex: 2, 32 | } ); 33 | 34 | // #291 35 | assert.ok( true, 'initializing with initialIndex > cells doesnt throw error' ); 36 | 37 | } ); 38 | -------------------------------------------------------------------------------- /test/unit/get-parent-cell.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'getParentCell', function( assert ) { 2 | 3 | let gallery = document.querySelector('#get-parent-cell'); 4 | let flkty = new Flickity( gallery ); 5 | 6 | // cell1 7 | let cell = flkty.getParentCell( gallery.querySelector('.cell1') ); 8 | assert.ok( cell, 'getParentCell( cell ) ok' ); 9 | assert.ok( cell instanceof Flickity.Cell, 'cell is Flickity.Cell' ); 10 | let index = flkty.cells.indexOf( cell ); 11 | assert.equal( index, 0, 'cell is index 0' ); 12 | // cell3 13 | cell = flkty.getParentCell( gallery.querySelector('.cell3') ); 14 | assert.ok( cell, 'getParentCell( cell ) ok' ); 15 | assert.ok( cell instanceof Flickity.Cell, 'cell is Flickity.Cell' ); 16 | index = flkty.cells.indexOf( cell ); 17 | assert.equal( index, 2, 'cell is index 2' ); 18 | // child1 19 | cell = flkty.getParentCell( gallery.querySelector('.child1') ); 20 | assert.ok( cell, 'getParentCell( cell ) ok' ); 21 | assert.ok( cell instanceof Flickity.Cell, 'cell is Flickity.Cell' ); 22 | index = flkty.cells.indexOf( cell ); 23 | assert.equal( index, 0, 'cell is index 0' ); 24 | // child2 25 | cell = flkty.getParentCell( gallery.querySelector('.child2') ); 26 | assert.ok( cell, 'getParentCell( cell ) ok' ); 27 | assert.ok( cell instanceof Flickity.Cell, 'cell is Flickity.Cell' ); 28 | index = flkty.cells.indexOf( cell ); 29 | assert.equal( index, 1, 'cell is index 1' ); 30 | // outside 31 | cell = flkty.getParentCell( document.querySelector('.outside') ); 32 | assert.ok( !cell, 'getParentCell( notCell ) not ok' ); 33 | index = flkty.cells.indexOf( cell ); 34 | assert.equal( index, -1, 'not cell is index -1' ); 35 | 36 | } ); 37 | -------------------------------------------------------------------------------- /test/unit/get-wrap-cells.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'getWrapCells', function( assert ) { 2 | 3 | let flkty = new Flickity( '#get-wrap-cells', { 4 | wrapAround: true, 5 | } ); 6 | // cells are 25% width 7 | // center align, 2 cells on each side 8 | assert.equal( flkty.beforeShiftCells.length, 2, 'center align, 2 before shift cells' ); 9 | assert.equal( flkty.afterShiftCells.length, 2, 'center align, 2 after shift cells' ); 10 | 11 | flkty.options.cellAlign = 'left'; 12 | flkty.resize(); 13 | // left align, 0, 4 14 | assert.equal( flkty.beforeShiftCells.length, 0, 'left align, 1 before shift cells' ); 15 | assert.equal( flkty.afterShiftCells.length, 4, 'left align, 4 after shift cells' ); 16 | 17 | flkty.options.cellAlign = 'right'; 18 | flkty.resize(); 19 | // right align, 4, 0 20 | assert.equal( flkty.beforeShiftCells.length, 4, 'right align, 4 before shift cells' ); 21 | assert.equal( flkty.afterShiftCells.length, 0, 'right align, 0 after shift cells' ); 22 | 23 | } ); 24 | -------------------------------------------------------------------------------- /test/unit/group-cells.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'groupCells', function( assert ) { 2 | 3 | let done = assert.async(); 4 | 5 | let flkty = new Flickity( '#group-cells', { 6 | groupCells: true, 7 | } ); 8 | 9 | function getSlideCellsCount() { 10 | let counts = flkty.slides.map( function( slide ) { 11 | return slide.cells.length; 12 | } ); 13 | return counts.join(','); 14 | } 15 | 16 | assert.equal( getSlideCellsCount(), '3,2,2,1,1,3,2', 'groupCells: true' ); 17 | let targets = flkty.slides.map( function( slide ) { 18 | return slide.target; 19 | } ); 20 | assert.deepEqual( targets, [ 200, 600, 1000, 1300, 1600, 2000, 2300 ], 'targets' ); 21 | 22 | flkty.selectCell( 6 ); 23 | assert.equal( flkty.selectedIndex, 2, 'selectCell(6) selects 3rd slide' ); 24 | flkty.selectCell( flkty.cells[2].element ); 25 | assert.equal( flkty.selectedIndex, 0, 'selectCell(3rd elem) selects 1st slide' ); 26 | 27 | flkty.options.groupCells = 2; 28 | flkty.reposition(); 29 | assert.equal( getSlideCellsCount(), '2,2,2,2,2,2,2', 'groupCells: 2' ); 30 | 31 | flkty.options.groupCells = '75%'; 32 | flkty.reposition(); 33 | assert.equal( getSlideCellsCount(), '2,1,1,2,1,1,1,2,2,1', 'groupCells: 75%' ); 34 | 35 | flkty.once( 'settle', function() { 36 | flkty.element.classList.add('is-expanded'); // 600px wide 37 | flkty.options.groupCells = true; 38 | flkty.resize(); 39 | assert.equal( getSlideCellsCount(), '3,3,2,3,3', 40 | 'groupCells: true, container @ 600px' ); 41 | done(); 42 | } ); 43 | 44 | } ); 45 | -------------------------------------------------------------------------------- /test/unit/imagesloaded.js: -------------------------------------------------------------------------------- 1 | /* globals imagesLoaded */ 2 | 3 | ( function() { 4 | 5 | // position values can be off by 0.1% or 1px 6 | function isPositionApprox( value, expected ) { 7 | let isPercent = value.indexOf('%') !== -1; 8 | value = parseFloat( value ); 9 | let diff = Math.abs( expected - value ); 10 | return isPercent ? diff < 0.1 : diff <= 1; 11 | } 12 | 13 | QUnit.test( 'imagesloaded', function( assert ) { 14 | let done = assert.async(); 15 | let gallery = document.querySelector('#imagesloaded'); 16 | 17 | let flkty = new Flickity( gallery, { 18 | imagesLoaded: true, 19 | percentPosition: false, 20 | } ); 21 | 22 | imagesLoaded( gallery, function() { 23 | flkty.cells.forEach( ( cell, i ) => { 24 | assert.ok( cell.size.width > 10, `cell ${i} has width` ); 25 | let transform = cell.element.style.transform; 26 | let position = transform.replace( 'translateX(', '' ).replace( ')', '' ); 27 | let isApprox = isPositionApprox( position, cell.x ); 28 | assert.ok( isApprox, `cell ${i} at proper position` ); 29 | } ); 30 | 31 | assert.equal( flkty.viewport.style.height, '140px', 'gallery height set' ); 32 | 33 | done(); 34 | } ); 35 | 36 | } ); 37 | 38 | QUnit.test( 'imagesloaded-in-divs', function( assert ) { 39 | 40 | let done = assert.async(); 41 | let gallery = document.querySelector('#imagesloaded-in-divs'); 42 | 43 | let flkty = new Flickity( gallery, { 44 | imagesLoaded: true, 45 | percentPosition: false, 46 | } ); 47 | 48 | imagesLoaded( gallery, function() { 49 | flkty.cells.forEach( ( cell, i ) => { 50 | assert.ok( cell.size.width > 10, `cell ${i} has width` ); 51 | let transform = cell.element.style.transform; 52 | let position = transform.replace( 'translateX(', '' ).replace( ')', '' ); 53 | let isApprox = isPositionApprox( position, cell.x ); 54 | assert.ok( isApprox, `cell ${i} at proper position` ); 55 | } ); 56 | 57 | assert.equal( flkty.viewport.style.height, '140px', 'gallery height set' ); 58 | 59 | done(); 60 | } ); 61 | 62 | } ); 63 | 64 | } )(); 65 | 66 | -------------------------------------------------------------------------------- /test/unit/init.js: -------------------------------------------------------------------------------- 1 | ( function() { 2 | 3 | QUnit.module('Flickity'); 4 | 5 | let utils = window.fizzyUIUtils; 6 | 7 | QUnit.test( 'init', function( assert ) { 8 | 9 | let elem = document.querySelector('#init'); 10 | let flkty = new Flickity( elem ); 11 | 12 | for ( let prop in Flickity.defaults ) { 13 | assert.equal( flkty.options[ prop ], Flickity.defaults[ prop ], 14 | `${prop} option matches default` ); 15 | } 16 | 17 | assert.equal( flkty.element, elem, '.element is proper element' ); 18 | let children = utils.makeArray( flkty.element.children ); 19 | assert.notEqual( children.indexOf( flkty.viewport ), -1, 20 | 'viewport element is a child element' ); 21 | assert.equal( flkty.viewport.children[0], flkty.slider, 'slider is in viewport' ); 22 | assert.equal( flkty.viewport.style.height, '100px', 'viewport height set' ); 23 | 24 | assert.ok( flkty.isActive, 'isActive' ); 25 | assert.ok( elem.matches('.flickity-enabled'), 'flickity-enabled class added' ); 26 | 27 | assert.equal( flkty.cells.length, 6, 'has 6 cells' ); 28 | assert.equal( getComputedStyle( flkty.cells[0].element ).left, '0px', 29 | 'first cell left: 0px' ); 30 | assert.equal( flkty.cells[0].element.style.transform, 31 | 'translateX(0%)', 'first cell translateX: 0%' ); 32 | assert.equal( flkty.cells[5].element.style.transform, 33 | 'translateX(500%)', '6th cell translateX: 500%' ); 34 | 35 | assert.equal( flkty.selectedIndex, 0, 'selectedIndex = 0' ); 36 | assert.equal( flkty.cursorPosition, 200, 'cursorPosition = 200' ); 37 | assert.equal( flkty.x + flkty.cursorPosition, 0, 'x + cursorPosition = 0' ); 38 | 39 | } ); 40 | 41 | } )(); 42 | -------------------------------------------------------------------------------- /test/unit/initial-index.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'initialIndex', function( assert ) { 2 | // initialIndex number 3 | let flkty = new Flickity( '#initial-index', { 4 | initialIndex: 3, 5 | } ); 6 | assert.equal( flkty.selectedIndex, 3, 'initialIndex number' ); 7 | // selectedIndex remains same after reactivation 8 | flkty.deactivate(); 9 | flkty.activate(); 10 | assert.equal( flkty.selectedIndex, 3, 'reactivated selectedIndex stays the same' ); 11 | flkty.destroy(); 12 | // initialIndex selector string 13 | flkty = new Flickity( '#initial-index', { 14 | initialIndex: '.cell--initial', 15 | } ); 16 | assert.equal( flkty.selectedIndex, 4, 'initialIndex selector string' ); 17 | flkty.destroy(); 18 | // initialIndex selector string with groupCells #881 19 | flkty = new Flickity( '#initial-index', { 20 | groupCells: 3, 21 | initialIndex: '.cell--initial', 22 | } ); 23 | assert.equal( flkty.selectedIndex, 1, 'initialIndex selector string with groupCells' ); 24 | 25 | } ); 26 | -------------------------------------------------------------------------------- /test/unit/lazyload-srcset.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'lazyload srcset', function( assert ) { 2 | 3 | let done = assert.async(); 4 | 5 | let gallery = document.querySelector('#lazyload-srcset'); 6 | let flkty = new Flickity( gallery, { 7 | lazyLoad: 1, 8 | } ); 9 | 10 | let loadCount = 0; 11 | flkty.on( 'lazyLoad', function( event, cellElem ) { 12 | loadCount++; 13 | 14 | assert.equal( event.type, 'load', 'event.type == load' ); 15 | assert.ok( event.target.complete, `img ${loadCount} is complete` ); 16 | assert.ok( cellElem, 'cellElement argument there' ); 17 | let srcset = event.target.getAttribute('srcset'); 18 | assert.ok( srcset, 'srcset attribute set' ); 19 | let lazyAttr = event.target.getAttribute('data-flickity-lazyload-srcset'); 20 | assert.ok( !lazyAttr, 'data-flickity-lazyload attribute removed' ); 21 | 22 | // after first 2 have loaded, select 7th cell 23 | if ( loadCount === 2 ) { 24 | done(); 25 | } 26 | } ); 27 | 28 | } ); 29 | -------------------------------------------------------------------------------- /test/unit/lazyload.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'lazyload', function( assert ) { 2 | 3 | let done = assert.async(); 4 | 5 | let gallery = document.querySelector('#lazyload'); 6 | let flkty = new Flickity( gallery, { 7 | lazyLoad: 1, 8 | } ); 9 | 10 | let loadCount = 0; 11 | flkty.on( 'lazyLoad', function( event, cellElem ) { 12 | loadCount++; 13 | 14 | assert.equal( event.type, 'load', 'event.type == load' ); 15 | assert.ok( event.target.complete, `img ${loadCount} is complete` ); 16 | assert.ok( cellElem, 'cellElement argument there' ); 17 | let lazyAttr = event.target.getAttribute('data-flickity-lazyload'); 18 | assert.ok( !lazyAttr, 'data-flickity-lazyload attribute removed' ); 19 | 20 | // after first 2 have loaded, select 7th cell 21 | if ( loadCount === 2 ) { 22 | flkty.select( 6 ); 23 | } 24 | if ( loadCount === 5 ) { 25 | let loadedImgs = gallery.querySelectorAll('.flickity-lazyloaded'); 26 | assert.equal( loadedImgs.length, '5', 'only 5 images loaded' ); 27 | done(); 28 | } 29 | } ); 30 | 31 | } ); 32 | -------------------------------------------------------------------------------- /test/unit/page-dots.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'pageDots', function( assert ) { 2 | 3 | let elem = document.querySelector('#page-dots'); 4 | let flkty = new Flickity( elem ); 5 | 6 | let dotsHolder = elem.querySelector('.flickity-page-dots'); 7 | let dotsElems = [ ...dotsHolder.querySelectorAll('.flickity-page-dot') ]; 8 | 9 | assert.ok( dotsHolder, 'dots holder in DOM' ); 10 | assert.equal( flkty.pageDots.holder, dotsHolder, 11 | 'dots holder element matches flkty.pageDots.holder' ); 12 | assert.equal( dotsElems.length, flkty.cells.length, 13 | 'number of dots matches number of cells' ); 14 | 15 | function getSelectedDotIndex() { 16 | return dotsElems.indexOf( dotsHolder.querySelector('.is-selected') ); 17 | } 18 | 19 | assert.equal( getSelectedDotIndex(), 0, 'first dot is selected' ); 20 | flkty.select( 2 ); 21 | assert.equal( getSelectedDotIndex(), 2, '3rd dot is selected' ); 22 | 23 | // fake click 24 | flkty.onPageDotsClick({ target: dotsElems[4] }); 25 | assert.equal( flkty.selectedIndex, 4, 'tap dot selects cell' ); 26 | 27 | } ); 28 | -------------------------------------------------------------------------------- /test/unit/position-cells.js: -------------------------------------------------------------------------------- 1 | ( function() { 2 | 3 | // position values can be off by 0.1% or 1px 4 | function isPositionApprox( value, expected ) { 5 | let isPercent = value.indexOf('%') !== -1; 6 | value = parseFloat( value ); 7 | let diff = Math.abs( expected - value ); 8 | return isPercent ? diff < 0.1 : diff <= 1; 9 | } 10 | 11 | // loop through cells and check position values against expecteds 12 | function checkCellPositions( flkty, expecteds ) { 13 | let isOK; 14 | for ( let i = 0; i < expecteds.length; i++ ) { 15 | let expected = expecteds[i]; 16 | let cell = flkty.cells[i]; 17 | let transform = cell.element.style.transform; 18 | let position = transform.replace( 'translateX(', '' ).replace( ')', '' ); 19 | isOK = isPositionApprox( position, expected ); 20 | if ( !isOK ) { 21 | console.error(`wrong cell position, index: ${i}. 22 | expected: ${expected}. position: ${position}`); 23 | break; 24 | } 25 | } 26 | return isOK; 27 | } 28 | 29 | QUnit.test( 'position cells', function( assert ) { 30 | 31 | let flkty = new Flickity('#position-cells'); 32 | 33 | assert.ok( checkCellPositions( flkty, [ 0, 160, 108.3, 312.5, 275, 900 ] ), 34 | 'percent cell position' ); 35 | // .cell { margin: 0 2%; } 36 | flkty.element.classList.add('percent-margin'); 37 | flkty.positionCells(); 38 | assert.ok( checkCellPositions( flkty, [ 0, 176, 121.67, 342.5, 301.67, 980 ] ), 39 | 'percent cell position with margin' ); 40 | flkty.element.classList.remove('percent-margin'); 41 | // pixel-based position 42 | flkty.options.percentPosition = false; 43 | flkty.positionCells(); 44 | assert.ok( checkCellPositions( flkty, [ 0, 160, 260, 500, 660, 900 ] ), 45 | 'pixel cell position' ); 46 | // pixel margin, { margin: 0 10px; } 47 | flkty.element.classList.add('pixel-margin'); 48 | flkty.positionCells(); 49 | assert.ok( checkCellPositions( flkty, [ 0, 180, 300, 560, 740, 1000 ] ), 50 | 'pixel cell position with margin' ); 51 | 52 | } ); 53 | 54 | } )(); 55 | -------------------------------------------------------------------------------- /test/unit/prev-next-buttons.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'prev-next-buttons', function( assert ) { 2 | 3 | let elem = document.querySelector('#prev-next-buttons'); 4 | let flkty = new Flickity( elem ); 5 | 6 | let prevElem = elem.querySelector('.flickity-prev-next-button.previous'); 7 | let nextElem = elem.querySelector('.flickity-prev-next-button.next'); 8 | assert.ok( prevElem, 'previous button in DOM' ); 9 | assert.ok( nextElem, 'next button in DOM' ); 10 | assert.equal( flkty.prevButton.element, prevElem, 11 | 'previous button element matches prevButton.element' ); 12 | assert.equal( flkty.nextButton.element, nextElem, 13 | 'next button element matches nextButton.element' ); 14 | assert.ok( prevElem.disabled, 'previous button is disabled at first index' ); 15 | 16 | prevElem.focus(); 17 | prevElem.click(); 18 | assert.equal( flkty.selectedIndex, 0, 'selectedIndex still at 0' ); 19 | nextElem.focus(); 20 | nextElem.click(); 21 | assert.equal( flkty.selectedIndex, 1, 'next button clicked, selectedIndex at 1' ); 22 | prevElem.focus(); 23 | prevElem.click(); 24 | assert.equal( flkty.selectedIndex, 0, 25 | 'previous button clicked, selectedIndex back at 0' ); 26 | flkty.select( 5 ); 27 | assert.ok( nextElem.disabled, 'next button disabled when at last cell' ); 28 | 29 | } ); 30 | -------------------------------------------------------------------------------- /test/unit/resize.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'resize', function( assert ) { 2 | 3 | let elem = document.querySelector('#resize'); 4 | let flkty = new Flickity( elem, { 5 | initialIndex: 2, 6 | } ); 7 | elem.style.width = '500px'; 8 | flkty.resize(); 9 | 10 | assert.equal( flkty.selectedIndex, 2, 'selectedIndex = 2' ); 11 | assert.equal( flkty.cursorPosition, 250, 'cursorPosition = 250' ); 12 | assert.equal( flkty.x + flkty.cursorPosition, -1000, 'x + cursorPosition = -1000' ); 13 | 14 | } ); 15 | -------------------------------------------------------------------------------- /test/unit/select-cell.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'selectCell', function( assert ) { 2 | 3 | let gallery = document.querySelector('#select-cell'); 4 | let cellElems = gallery.querySelectorAll('.cell'); 5 | let flkty = new Flickity( gallery, { 6 | groupCells: true, // groups of 3 7 | } ); 8 | 9 | flkty.selectCell( 3 ); 10 | assert.equal( flkty.selectedIndex, 1, 'selectCell number' ); 11 | flkty.selectCell( cellElems[1] ); 12 | assert.equal( flkty.selectedIndex, 0, 'selectCell element' ); 13 | flkty.selectCell('.select-cell__6'); 14 | assert.equal( flkty.selectedIndex, 2, 'selectCell selector string' ); 15 | flkty.selectCell('none'); 16 | assert.equal( flkty.selectedIndex, 2, 'selectCell bad string is okay' ); 17 | } ); 18 | -------------------------------------------------------------------------------- /test/unit/watch.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'watch fallback', function( assert ) { 2 | 3 | let elem = document.querySelector('#watch'); 4 | let flkty = new Flickity( elem, { 5 | watchCSS: true, 6 | } ); 7 | 8 | assert.ok( !flkty.isActive, 'fallback not active, watchCSS: true' ); 9 | } ); 10 | -------------------------------------------------------------------------------- /test/unit/wrap-around-fill.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'wrapAround: "fill"', function( assert ) { 2 | 3 | let elem = document.querySelector('#wrap-around-fill'); 4 | let flkty = new Flickity( elem, { 5 | wrapAround: 'fill', 6 | } ); 7 | 8 | assert.ok( !flkty.isWrapping, 'total cell width not big enough, not wrapping' ); 9 | 10 | let shortCell = elem.querySelector('.cell--wrap-around-short'); 11 | shortCell.classList.remove('cell--wrap-around-short'); 12 | flkty.resize(); 13 | assert.ok( flkty.isWrapping, 'cell width big enough, wrapping' ); 14 | 15 | } ); 16 | --------------------------------------------------------------------------------