├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bower.json
├── dist
├── threesixty.js
└── threesixty.min.js
├── example
├── demo.details
├── demo.html
├── demo.js
├── images
│ ├── sequence-00.png
│ ├── sequence-01.png
│ ├── sequence-02.png
│ ├── sequence-03.png
│ ├── sequence-04.png
│ ├── sequence-05.png
│ ├── sequence-06.png
│ ├── sequence-07.png
│ ├── sequence-08.png
│ ├── sequence-09.png
│ ├── sequence-10.png
│ ├── sequence-11.png
│ ├── sequence-12.png
│ ├── sequence-13.png
│ ├── sequence-14.png
│ ├── sequence-15.png
│ ├── sequence-16.png
│ ├── sequence-17.png
│ ├── sequence-18.png
│ ├── sequence-19.png
│ ├── sequence-20.png
│ ├── sequence-21.png
│ ├── sequence-22.png
│ ├── sequence-23.png
│ ├── sequence-24.png
│ ├── sequence-25.png
│ └── sequence-26.png
└── screenshot.png
├── package-lock.json
├── package.json
├── src
└── index.js
├── test
├── index.spec.js
├── setup.js
└── transparent.png
├── webpack.config.babel.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ],
5 | "ignore": [
6 | "test/setup.js"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | max_line_length = 80
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | max_line_length = 0
15 | trim_trailing_whitespace = false
16 |
17 | [{.travis.yml,package.json}]
18 | indent_size = 2
19 | indent_style = space
20 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
2 | dist
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true,
6 | "mocha": true
7 | },
8 | "extends": "eslint:recommended",
9 | "parserOptions": {
10 | "ecmaVersion": 6,
11 | "sourceType": "module"
12 | },
13 | "rules": {
14 | "indent": ["error", 2],
15 | "linebreak-style": ["error", "unix"],
16 | "quotes": ["error", "single"],
17 | "semi": ["error", "never"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | npm-debug.log
4 | lib
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | coverage
4 | example
5 | src
6 | test
7 | .babelrc
8 | .editorconfig
9 | .eslintignore
10 | .eslintrc
11 | .gitattributes
12 | .travis.yml
13 | webpack.config.babel.js
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 | - "9"
5 | - "10"
6 | branches:
7 | only:
8 | - master
9 | before_install:
10 | - sudo apt-get install -y python-software-properties
11 | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
12 | - sudo apt-get -qq update
13 | - sudo apt-get install -y libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential gcc-5 g++-5
14 | - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 80 --slave /usr/bin/g++ g++ /usr/bin/g++-5
15 | - sudo update-alternatives --set gcc /usr/bin/gcc-5
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.2.0 (November 13, 2016)
2 |
3 | - Add support for touch-enabled laptops. ([@aegypius](https://github.com/aegypius) in [#1](https://github.com/rbartoli/threesixty/pull/1))
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Dependencies
9 | __IMPORTANT:__ To be able run the tests, [node-canvas](https://github.com/Automattic/node-canvas/tree/v1.x) package is required to provide a Node.js canvas implementation for [jsdom](https://github.com/tmpvar/jsdom). Please have a look at [their project page](https://github.com/Automattic/node-canvas/tree/v1.x) so you can see the requirements needed to install it.
10 |
11 | ## Setup
12 | ```js
13 | git clone https://github.com/rbartoli/threesixty.git
14 | cd threesixty
15 | npm install
16 | ```
17 |
18 | To run the entire test suite use:
19 |
20 | ```js
21 | npm test
22 | ```
23 |
24 | ## Submitting Changes
25 |
26 | After getting some feedback, push to your fork and submit a pull request. We
27 | may suggest some changes or improvements or alternatives, but for small changes
28 | your pull request should be accepted quickly.
29 |
30 | Some things that will increase the chance that your pull request is accepted:
31 |
32 | * Write tests
33 | * Follow the existing coding style
34 | * Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
35 |
36 | ## Contributor Code of Conduct
37 |
38 | As contributors and maintainers of this project, and in the interest of fostering an open and
39 | welcoming community, we pledge to respect all people who contribute through reporting issues,
40 | posting feature requests, updating documentation, submitting pull requests or patches, and other
41 | activities.
42 |
43 | We are committed to making participation in this project a harassment-free experience for everyone,
44 | regardless of level of experience, gender, gender identity and expression, sexual orientation,
45 | disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
46 |
47 | Examples of unacceptable behavior by participants include:
48 |
49 | * The use of sexualized language or imagery
50 | * Personal attacks
51 | * Trolling or insulting/derogatory comments
52 | * Public or private harassment
53 | * Publishing other's private information, such as physical or electronic addresses, without explicit
54 | permission
55 | * Other unethical or unprofessional conduct.
56 |
57 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits,
58 | code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By
59 | adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently
60 | applying these principles to every aspect of managing this project. Project maintainers who do not
61 | follow or enforce the Code of Conduct may be permanently removed from the project team.
62 |
63 | This code of conduct applies both within project spaces and in public spaces when an individual is
64 | representing the project or its community.
65 |
66 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an
67 | issue or contacting one or more of the project maintainers.
68 |
69 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org),
70 | version 1.4.0, available at
71 | [http://contributor-covenant.org/version/1/4/0/](http://contributor-covenant.org/version/1/2/0/)
72 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Riccardo Bartoli
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Threesixty
2 |
3 | [](https://www.npmjs.com/package/threesixty)
4 | [](https://travis-ci.org/rbartoli/threesixty)
5 | [](https://www.npmjs.com/package/threesixty)
6 | [](https://www.npmjs.com/package/threesixty)
7 | [](/LICENSE)
8 |
9 | A minimal, dependency-free vanilla 360° slider.
10 |
11 | ## Demo
12 |
13 |
14 | [Demo](http://jsfiddle.net/gh/get/library/pure/rbartoli/threesixty/tree/master/example)
15 |
16 | ## Features
17 | - Super easy to set up
18 | - No dependencies
19 | - Touch events
20 | - Touch-enabled laptops support (touch + mouse)
21 |
22 | ## Installation
23 | ```bash
24 | npm install --save threesixty
25 | ```
26 |
27 | ## Usage
28 | ### `threesixty(container*, images*, options?)`
29 | Initialise `threesixty` by passing both `container` and `images` required arguments.
30 |
31 | #### `container`
32 | The _Element_ to display the slider in.
33 |
34 | #### `images `
35 | An _Array_ containing a list of images.
36 |
37 | ```js
38 | var container = document.querySelector('#slider');
39 | var images = [
40 | 'images/sequence-01.jpg',
41 | ...
42 | 'images/sequence-50.jpg'
43 | ]
44 |
45 | var slider = threesixty(container, images);
46 | slider.init()
47 | ```
48 |
49 | ## Options
50 | You can also provide an `options` object. Here's an **overview of the default values**.
51 |
52 | ```js
53 | threesixty(container, images, {
54 | interactive: true,
55 | currentImage: 0,
56 | reverse: false
57 | });
58 | ```
59 |
60 | #### `options.interactive`
61 | Enable or disable mouse interactivity.
62 |
63 | #### `options.currentImage`
64 | Set the current image index.
65 |
66 | #### `options.reverse`
67 | Reverses the direction the image rotates when dragging.
68 |
69 | ## API
70 | Method | Arguments | Method Description
71 | -----------|----------------------------------|-------------------------------------------------------------------------------------
72 | `init` | | Initialise the slider
73 | `previous` | | Go back to the previous frame
74 | `next` | | Advance to the next frame
75 | `isInteractive` | | Returns `options.interactive` value
76 | `isReverse` | | Returns `options.reverse` value
77 | `getCurrentFrame` | | Returns `options.currentFrame` value
78 |
79 | ## Tests
80 | ```bash
81 | npm test
82 | ```
83 |
84 | ## Contributing
85 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
86 |
87 | ## License
88 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
89 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "threesixty",
3 | "version": "0.2.0",
4 | "description": "A minimal, dependency-free vanilla 360 slider",
5 | "main": [
6 | "dist/threesixty.js",
7 | "dist/threesixty.min.js"
8 | ],
9 | "authors": [
10 | "Riccardo Bartoli "
11 | ],
12 | "license": "MIT",
13 | "keywords": [
14 | "threesixty",
15 | "360",
16 | "slider",
17 | "slider",
18 | "image",
19 | "sequence"
20 | ],
21 | "homepage": "https://github.com/rbartoli/threesixty",
22 | "ignore": [
23 | "**/.*",
24 | "bower_components",
25 | "coverage",
26 | "example",
27 | "node_modules",
28 | "src",
29 | "test",
30 | "webpack.config.babel.js"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/dist/threesixty.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory();
4 | else if(typeof define === 'function' && define.amd)
5 | define("threesixty", [], factory);
6 | else if(typeof exports === 'object')
7 | exports["threesixty"] = factory();
8 | else
9 | root["threesixty"] = factory();
10 | })(window, function() {
11 | return /******/ (function(modules) { // webpackBootstrap
12 | /******/ // The module cache
13 | /******/ var installedModules = {};
14 | /******/
15 | /******/ // The require function
16 | /******/ function __webpack_require__(moduleId) {
17 | /******/
18 | /******/ // Check if module is in cache
19 | /******/ if(installedModules[moduleId]) {
20 | /******/ return installedModules[moduleId].exports;
21 | /******/ }
22 | /******/ // Create a new module (and put it into the cache)
23 | /******/ var module = installedModules[moduleId] = {
24 | /******/ i: moduleId,
25 | /******/ l: false,
26 | /******/ exports: {}
27 | /******/ };
28 | /******/
29 | /******/ // Execute the module function
30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31 | /******/
32 | /******/ // Flag the module as loaded
33 | /******/ module.l = true;
34 | /******/
35 | /******/ // Return the exports of the module
36 | /******/ return module.exports;
37 | /******/ }
38 | /******/
39 | /******/
40 | /******/ // expose the modules object (__webpack_modules__)
41 | /******/ __webpack_require__.m = modules;
42 | /******/
43 | /******/ // expose the module cache
44 | /******/ __webpack_require__.c = installedModules;
45 | /******/
46 | /******/ // define getter function for harmony exports
47 | /******/ __webpack_require__.d = function(exports, name, getter) {
48 | /******/ if(!__webpack_require__.o(exports, name)) {
49 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
50 | /******/ }
51 | /******/ };
52 | /******/
53 | /******/ // define __esModule on exports
54 | /******/ __webpack_require__.r = function(exports) {
55 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
56 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
57 | /******/ }
58 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
59 | /******/ };
60 | /******/
61 | /******/ // create a fake namespace object
62 | /******/ // mode & 1: value is a module id, require it
63 | /******/ // mode & 2: merge all properties of value into the ns
64 | /******/ // mode & 4: return value when already ns object
65 | /******/ // mode & 8|1: behave like require
66 | /******/ __webpack_require__.t = function(value, mode) {
67 | /******/ if(mode & 1) value = __webpack_require__(value);
68 | /******/ if(mode & 8) return value;
69 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
70 | /******/ var ns = Object.create(null);
71 | /******/ __webpack_require__.r(ns);
72 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
73 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
74 | /******/ return ns;
75 | /******/ };
76 | /******/
77 | /******/ // getDefaultExport function for compatibility with non-harmony modules
78 | /******/ __webpack_require__.n = function(module) {
79 | /******/ var getter = module && module.__esModule ?
80 | /******/ function getDefault() { return module['default']; } :
81 | /******/ function getModuleExports() { return module; };
82 | /******/ __webpack_require__.d(getter, 'a', getter);
83 | /******/ return getter;
84 | /******/ };
85 | /******/
86 | /******/ // Object.prototype.hasOwnProperty.call
87 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
88 | /******/
89 | /******/ // __webpack_public_path__
90 | /******/ __webpack_require__.p = "";
91 | /******/
92 | /******/
93 | /******/ // Load entry module and return exports
94 | /******/ return __webpack_require__(__webpack_require__.s = 0);
95 | /******/ })
96 | /************************************************************************/
97 | /******/ ({
98 |
99 | /***/ "./src/index.js":
100 | /*!**********************!*\
101 | !*** ./src/index.js ***!
102 | \**********************/
103 | /*! exports provided: default */
104 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
105 |
106 | "use strict";
107 | eval("__webpack_require__.r(__webpack_exports__);\nvar threesixty = function threesixty(container, images, options) {\n if (!container) {\n throw new Error('A container argument is required');\n }\n\n if (!images) {\n throw new Error('An images argument is required');\n }\n\n var defaults = {\n interactive: true,\n reverse: false,\n currentFrame: 0\n };\n var o = Object.assign({}, defaults, options);\n var totalFrames = images.length;\n var mouseX = 0;\n var oldMouseX = 0; //------------------------------------------------------------------------------\n //\n // Initialisation\n //\n //------------------------------------------------------------------------------\n\n var init = function init() {\n preloadimages(images, start);\n };\n\n var preloadimages = function preloadimages(sourceImages, cb) {\n var total = sourceImages.length;\n var loaded = 0;\n\n var onload = function onload() {\n if (++loaded >= total) cb(finalImages);\n };\n\n var finalImages = sourceImages.map(function (item) {\n var image = new Image();\n image.src = item;\n image.onload = onload;\n image.onerror = onload;\n image.onabort = onload;\n image.draggable = false;\n return image;\n });\n };\n\n var start = function start(loadedImages) {\n images = loadedImages;\n emptyDomNode(container);\n container.appendChild(images[o.currentFrame]);\n\n if (o.interactive) {\n initListeners();\n }\n }; //------------------------------------------------------------------------------\n //\n // Events\n //\n //------------------------------------------------------------------------------\n\n\n var initListeners = function initListeners() {\n container.addEventListener('touchstart', startDrag, {\n passive: true\n });\n container.addEventListener('mousedown', startDrag);\n };\n\n var drag = function drag(e) {\n if (!isTouchEvent(e)) {\n e.preventDefault();\n }\n\n mouseX = e.pageX !== undefined ? e.pageX : e.changedTouches[0].pageX;\n\n if (o.reverse) {\n if (mouseX > oldMouseX) {\n previous();\n } else if (mouseX < oldMouseX) {\n next();\n }\n } else {\n if (mouseX < oldMouseX) {\n previous();\n } else if (mouseX > oldMouseX) {\n next();\n }\n }\n\n oldMouseX = mouseX;\n };\n\n var startDrag = function startDrag(e) {\n if (!isTouchEvent(e)) {\n e.preventDefault();\n }\n\n document.addEventListener('touchmove', drag, {\n passive: true\n });\n document.addEventListener('mousemove', drag);\n document.addEventListener('touchend', stopDrag);\n document.addEventListener('mouseup', stopDrag);\n };\n\n var stopDrag = function stopDrag(e) {\n if (!isTouchEvent(e)) {\n e.preventDefault();\n }\n\n document.removeEventListener('touchmove', drag);\n document.removeEventListener('mousemove', drag);\n document.addEventListener('touchend', stopDrag, {\n passive: true\n });\n document.addEventListener('mouseup', stopDrag);\n }; //------------------------------------------------------------------------------\n //\n // Sequence management\n //\n //------------------------------------------------------------------------------\n\n\n var replaceImage = function replaceImage() {\n container.replaceChild(images[o.currentFrame], container.childNodes[0]);\n };\n\n var previous = function previous() {\n o.currentFrame--;\n if (o.currentFrame < 0) o.currentFrame = totalFrames - 1;\n replaceImage();\n };\n\n var next = function next() {\n o.currentFrame++;\n if (o.currentFrame === totalFrames) o.currentFrame = 0;\n replaceImage();\n };\n\n var isInteractive = function isInteractive() {\n return o.interactive;\n };\n\n var isReverse = function isReverse() {\n return o.reverse;\n };\n\n var getCurrentFrame = function getCurrentFrame() {\n return o.currentFrame;\n }; //------------------------------------------------------------------------------\n //\n // API\n //\n //------------------------------------------------------------------------------\n\n\n return {\n init: init,\n previous: previous,\n next: next,\n isInteractive: isInteractive,\n isReverse: isReverse,\n getCurrentFrame: getCurrentFrame\n };\n}; //------------------------------------------------------------------------------\n//\n// Utilities\n//\n//------------------------------------------------------------------------------\n\n\nvar emptyDomNode = function emptyDomNode(element) {\n if (element.hasChildNodes()) {\n while (element.firstChild) {\n element.removeChild(element.firstChild);\n }\n }\n};\n\nvar isTouchEvent = function isTouchEvent(e) {\n return e.touches;\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (threesixty);\n\n//# sourceURL=webpack://threesixty/./src/index.js?");
108 |
109 | /***/ }),
110 |
111 | /***/ 0:
112 | /*!****************************!*\
113 | !*** multi ./src/index.js ***!
114 | \****************************/
115 | /*! no static exports found */
116 | /***/ (function(module, exports, __webpack_require__) {
117 |
118 | eval("module.exports = __webpack_require__(/*! ./src/index.js */\"./src/index.js\");\n\n\n//# sourceURL=webpack://threesixty/multi_./src/index.js?");
119 |
120 | /***/ })
121 |
122 | /******/ });
123 | });
--------------------------------------------------------------------------------
/dist/threesixty.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("threesixty",[],t):"object"==typeof exports?exports.threesixty=t():e.threesixty=t()}(window,function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";n.r(t);var r=function(e){if(e.hasChildNodes())for(;e.firstChild;)e.removeChild(e.firstChild)},o=function(e){return e.touches};t.default=function(e,t,n){if(!e)throw new Error("A container argument is required");if(!t)throw new Error("An images argument is required");var u=Object.assign({},{interactive:!0,reverse:!1,currentFrame:0},n),i=t.length,c=0,a=0,d=function(e,t){var n=e.length,r=0,o=function(){++r>=n&&t(u)},u=e.map(function(e){var t=new Image;return t.src=e,t.onload=o,t.onerror=o,t.onabort=o,t.draggable=!1,t})},f=function(n){t=n,r(e),e.appendChild(t[u.currentFrame]),u.interactive&&s()},s=function(){e.addEventListener("touchstart",v,{passive:!0}),e.addEventListener("mousedown",v)},m=function(e){o(e)||e.preventDefault(),c=void 0!==e.pageX?e.pageX:e.changedTouches[0].pageX,u.reverse?c>a?h():ca&&g(),a=c},v=function(e){o(e)||e.preventDefault(),document.addEventListener("touchmove",m,{passive:!0}),document.addEventListener("mousemove",m),document.addEventListener("touchend",l),document.addEventListener("mouseup",l)},l=function e(t){o(t)||t.preventDefault(),document.removeEventListener("touchmove",m),document.removeEventListener("mousemove",m),document.addEventListener("touchend",e,{passive:!0}),document.addEventListener("mouseup",e)},p=function(){e.replaceChild(t[u.currentFrame],e.childNodes[0])},h=function(){u.currentFrame--,u.currentFrame<0&&(u.currentFrame=i-1),p()},g=function(){u.currentFrame++,u.currentFrame===i&&(u.currentFrame=0),p()};return{init:function(){d(t,f)},previous:h,next:g,isInteractive:function(){return u.interactive},isReverse:function(){return u.reverse},getCurrentFrame:function(){return u.currentFrame}}}}])});
--------------------------------------------------------------------------------
/example/demo.details:
--------------------------------------------------------------------------------
1 | /*
2 | ---
3 | name: threesixty example
4 | description: Shows how to get started with threesixty
5 | authors:
6 | - Riccardo Bartoli
7 | normalize_css: no
8 | js_wrap: b
9 | ...
10 | */
11 |
--------------------------------------------------------------------------------
/example/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
34 |
35 |
36 |
37 | threesixty demo
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/example/demo.js:
--------------------------------------------------------------------------------
1 | /*global threesixty:true*/
2 |
3 | function generateImagesToPreload(totalImages) {
4 | for (var i = 0, images = [], index; i < totalImages; i++) {
5 | index = (i < 10) ? '0' + i : i
6 | images.push('https://github.com/rbartoli/threesixty/raw/master/example/images/sequence-' + index + '.png')
7 | }
8 |
9 | return images
10 | }
11 |
12 | document.addEventListener('DOMContentLoaded', function(){
13 | var instance = threesixty(
14 | document.querySelector('#one'),
15 | generateImagesToPreload(27)
16 | )
17 | instance.init()
18 | })
19 |
--------------------------------------------------------------------------------
/example/images/sequence-00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-00.png
--------------------------------------------------------------------------------
/example/images/sequence-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-01.png
--------------------------------------------------------------------------------
/example/images/sequence-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-02.png
--------------------------------------------------------------------------------
/example/images/sequence-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-03.png
--------------------------------------------------------------------------------
/example/images/sequence-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-04.png
--------------------------------------------------------------------------------
/example/images/sequence-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-05.png
--------------------------------------------------------------------------------
/example/images/sequence-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-06.png
--------------------------------------------------------------------------------
/example/images/sequence-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-07.png
--------------------------------------------------------------------------------
/example/images/sequence-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-08.png
--------------------------------------------------------------------------------
/example/images/sequence-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-09.png
--------------------------------------------------------------------------------
/example/images/sequence-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-10.png
--------------------------------------------------------------------------------
/example/images/sequence-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-11.png
--------------------------------------------------------------------------------
/example/images/sequence-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-12.png
--------------------------------------------------------------------------------
/example/images/sequence-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-13.png
--------------------------------------------------------------------------------
/example/images/sequence-14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-14.png
--------------------------------------------------------------------------------
/example/images/sequence-15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-15.png
--------------------------------------------------------------------------------
/example/images/sequence-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-16.png
--------------------------------------------------------------------------------
/example/images/sequence-17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-17.png
--------------------------------------------------------------------------------
/example/images/sequence-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-18.png
--------------------------------------------------------------------------------
/example/images/sequence-19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-19.png
--------------------------------------------------------------------------------
/example/images/sequence-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-20.png
--------------------------------------------------------------------------------
/example/images/sequence-21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-21.png
--------------------------------------------------------------------------------
/example/images/sequence-22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-22.png
--------------------------------------------------------------------------------
/example/images/sequence-23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-23.png
--------------------------------------------------------------------------------
/example/images/sequence-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-24.png
--------------------------------------------------------------------------------
/example/images/sequence-25.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-25.png
--------------------------------------------------------------------------------
/example/images/sequence-26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/images/sequence-26.png
--------------------------------------------------------------------------------
/example/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rbartoli/threesixty/05796ea87fb9743520ba9914ed142ffb9f8c1ecc/example/screenshot.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "threesixty",
3 | "version": "0.3.0",
4 | "description": "A minimal, dependency-free vanilla 360 slider",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "build": "babel src --out-dir lib",
8 | "build:umd": "webpack --mode=development",
9 | "build:umd:min": "cross-env NODE_ENV=production webpack --mode=production",
10 | "clean": "rimraf coverage dist lib",
11 | "lint": "eslint src test example",
12 | "prepublish": "npm run lint && npm run test && npm run clean && npm run build && npm run build:umd && npm run build:umd:min",
13 | "test": "cross-env NODE_ENV=test mocha --require @babel/register --recursive --require ./test/setup.js",
14 | "test:coverage": "babel-node node_modules/.bin/isparta cover node_modules/.bin/_mocha --root src/ --report text",
15 | "test:watch": "npm test -- --watch"
16 | },
17 | "engines": {
18 | "node": ">=8"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/rbartoli/threesixty.git"
23 | },
24 | "keywords": [
25 | "threesixty",
26 | "360 slider",
27 | "slider",
28 | "image sequence"
29 | ],
30 | "author": {
31 | "name": "Riccardo Bartoli",
32 | "email": "info@rblab.com",
33 | "url": "http://rblab.com"
34 | },
35 | "license": "MIT",
36 | "bugs": {
37 | "url": "https://github.com/rbartoli/threesixty/issues"
38 | },
39 | "homepage": "https://github.com/rbartoli/threesixty",
40 | "devDependencies": {
41 | "@babel/cli": "^7.0.0",
42 | "@babel/core": "^7.0.0",
43 | "@babel/node": "^7.0.0",
44 | "@babel/preset-env": "^7.0.0",
45 | "@babel/register": "^7.0.0",
46 | "babel-loader": "^8.0.0",
47 | "canvas": "^2.4.0",
48 | "chai": "^4.2.0",
49 | "cross-env": "^5.2.0",
50 | "eslint": "^4.19.1",
51 | "eslint-plugin-import": "^2.16.0",
52 | "isparta": "^4.1.1",
53 | "jsdom": "^14.0.0",
54 | "mocha": "^5.2.0",
55 | "rimraf": "^2.6.3",
56 | "uglifyjs-webpack-plugin": "^1.3.0",
57 | "webpack": "^4.29.6",
58 | "webpack-cli": "^3.3.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const threesixty = (container, images, options) => {
2 | if (!container) {
3 | throw new Error('A container argument is required')
4 | }
5 |
6 | if (!images) {
7 | throw new Error('An images argument is required')
8 | }
9 |
10 | const defaults = {
11 | interactive: true,
12 | reverse: false,
13 | currentFrame: 0
14 | }
15 |
16 | const o = Object.assign({}, defaults, options)
17 | const totalFrames = images.length
18 |
19 | let mouseX = 0
20 | let oldMouseX = 0
21 |
22 | //------------------------------------------------------------------------------
23 | //
24 | // Initialisation
25 | //
26 | //------------------------------------------------------------------------------
27 |
28 | const init = () => {
29 | preloadimages(images, start)
30 | }
31 |
32 | const preloadimages = (sourceImages, cb) => {
33 | const total = sourceImages.length
34 | let loaded = 0
35 |
36 | const onload = () => {
37 | if (++loaded >= total) cb(finalImages)
38 | }
39 |
40 | const finalImages = sourceImages.map((item) => {
41 | const image = new Image()
42 | image.src = item
43 | image.onload = onload
44 | image.onerror = onload
45 | image.onabort = onload
46 | image.draggable = false
47 | return image
48 | })
49 | }
50 |
51 | const start = (loadedImages) => {
52 | images = loadedImages
53 |
54 | emptyDomNode(container)
55 | container.appendChild(images[o.currentFrame])
56 |
57 | if (o.interactive) {
58 | initListeners()
59 | }
60 | }
61 |
62 | //------------------------------------------------------------------------------
63 | //
64 | // Events
65 | //
66 | //------------------------------------------------------------------------------
67 |
68 | const initListeners = () => {
69 | container.addEventListener('touchstart', startDrag, {passive: true})
70 | container.addEventListener('mousedown', startDrag)
71 | }
72 |
73 | const drag = (e) => {
74 | if (!isTouchEvent(e)) {
75 | e.preventDefault()
76 | }
77 |
78 | mouseX = (e.pageX !== undefined) ? e.pageX : e.changedTouches[0].pageX
79 |
80 | if (o.reverse) {
81 | if (mouseX > oldMouseX) {
82 | previous()
83 | } else if (mouseX < oldMouseX) {
84 | next()
85 | }
86 | } else {
87 | if (mouseX < oldMouseX) {
88 | previous()
89 | } else if (mouseX > oldMouseX) {
90 | next()
91 | }
92 | }
93 |
94 | oldMouseX = mouseX
95 | }
96 |
97 | const startDrag = (e) => {
98 | if (!isTouchEvent(e)) {
99 | e.preventDefault()
100 | }
101 |
102 | document.addEventListener('touchmove', drag, {passive: true})
103 | document.addEventListener('mousemove', drag)
104 | document.addEventListener('touchend', stopDrag)
105 | document.addEventListener('mouseup', stopDrag)
106 | }
107 |
108 | const stopDrag = (e) => {
109 | if (!isTouchEvent(e)) {
110 | e.preventDefault()
111 | }
112 |
113 | document.removeEventListener('touchmove', drag)
114 | document.removeEventListener('mousemove', drag)
115 | document.addEventListener('touchend', stopDrag, {passive: true})
116 | document.addEventListener('mouseup', stopDrag)
117 | }
118 |
119 | //------------------------------------------------------------------------------
120 | //
121 | // Sequence management
122 | //
123 | //------------------------------------------------------------------------------
124 |
125 | const replaceImage = () => {
126 | container.replaceChild(images[o.currentFrame], container.childNodes[0])
127 | }
128 |
129 | const previous = () => {
130 | o.currentFrame--
131 | if (o.currentFrame < 0) o.currentFrame = totalFrames - 1
132 | replaceImage()
133 | }
134 |
135 | const next = () => {
136 | o.currentFrame++
137 | if (o.currentFrame === totalFrames) o.currentFrame = 0
138 | replaceImage()
139 | }
140 |
141 | const isInteractive = () => o.interactive
142 | const isReverse = () => o.reverse
143 | const getCurrentFrame = () => o.currentFrame
144 |
145 | //------------------------------------------------------------------------------
146 | //
147 | // API
148 | //
149 | //------------------------------------------------------------------------------
150 |
151 | return {
152 | init,
153 | previous,
154 | next,
155 | isInteractive,
156 | isReverse,
157 | getCurrentFrame
158 | }
159 | }
160 |
161 | //------------------------------------------------------------------------------
162 | //
163 | // Utilities
164 | //
165 | //------------------------------------------------------------------------------
166 |
167 | const emptyDomNode = (element) => {
168 | if (element.hasChildNodes()) {
169 | while (element.firstChild) {
170 | element.removeChild(element.firstChild)
171 | }
172 | }
173 | }
174 |
175 | const isTouchEvent = (e) => {
176 | return e.touches
177 | }
178 |
179 | export default threesixty
180 |
--------------------------------------------------------------------------------
/test/index.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai'
2 | import threesixty from '../src'
3 |
4 | const TIMEOUT = 200
5 | const DUMMY_IMAGE = 'transparent.png'
6 | const IMAGES = [DUMMY_IMAGE, DUMMY_IMAGE, DUMMY_IMAGE, DUMMY_IMAGE]
7 | const IMAGES_LENGTH = IMAGES.length
8 | const LAST_IMAGE_INDEX = IMAGES_LENGTH - 1
9 | let container
10 |
11 |
12 |
13 | const clickAndDrag = (distance = 1) => {
14 | let event = new MouseEvent('mousedown')
15 | container.dispatchEvent(event)
16 |
17 | event = new MouseEvent('mousemove')
18 | event.pageX = distance
19 | document.dispatchEvent(event)
20 |
21 | event = new MouseEvent('mouseup')
22 | document.dispatchEvent(event)
23 | }
24 |
25 |
26 |
27 | describe('public api', () => {
28 | let instance
29 |
30 | beforeEach(() => {
31 | container = document.createElement('div')
32 | instance = threesixty(container, IMAGES)
33 | })
34 |
35 | it('should expose init method', () => {
36 | const actual = instance.init
37 | const expected = 'function'
38 |
39 | expect(actual).to.be.a(expected)
40 | })
41 |
42 | it('should expose previous method', () => {
43 | const actual = instance.previous
44 | const expected = 'function'
45 |
46 | expect(actual).to.be.a(expected)
47 | })
48 |
49 | it('should expose next method', () => {
50 | const actual = instance.next
51 | const expected = 'function'
52 |
53 | expect(actual).to.be.a(expected)
54 | })
55 |
56 | it('should expose isInteractive method', () => {
57 | const actual = instance.isInteractive
58 | const expected = 'function'
59 |
60 | expect(actual).to.be.a(expected)
61 | })
62 |
63 | it('should expose getCurrentFrame method', () => {
64 | const actual = instance.getCurrentFrame
65 | const expected = 'function'
66 |
67 | expect(actual).to.be.a(expected)
68 | })
69 | })
70 |
71 |
72 |
73 | describe('arguments', () => {
74 |
75 | before(() => {
76 | container = document.createElement('div')
77 | })
78 |
79 | it('should require a container argument', function() {
80 | const actual = () => { threesixty() }
81 | const expected = Error
82 |
83 | expect(actual).to.throw(expected)
84 | })
85 |
86 | it('should require an IMAGES argument', () => {
87 | const actual = () => { threesixty(container) }
88 | const expected = Error
89 |
90 | expect(actual).to.throw(expected)
91 | })
92 |
93 | it('should instantiate if the required arguments are provided', () => {
94 | const actual = () => { threesixty(container, IMAGES) }
95 | const expected = Error
96 |
97 | expect(actual).to.not.throw(expected)
98 | })
99 | })
100 |
101 |
102 |
103 | describe('options', () => {
104 | const options = {
105 | interactive: false,
106 | currentFrame: 2,
107 | reverse: true
108 | }
109 |
110 | before(() => {
111 | container = document.createElement('div')
112 | })
113 |
114 | it('should instantiate without providing options', () => {
115 | const actual = () => { threesixty(container, IMAGES) }
116 | const expected = Error
117 |
118 | expect(actual).to.not.throw(expected)
119 | })
120 |
121 | it('should instantiate providing options', () => {
122 | const actual = () => { threesixty(container, IMAGES, options) }
123 | const expected = Error
124 |
125 | expect(actual).to.not.throw(expected)
126 | })
127 |
128 | it('should use defaults if no option is provided', () => {
129 | const instance = threesixty(container, IMAGES)
130 | const actual = instance.isInteractive()
131 | const expected = true
132 |
133 | expect(actual).to.equal(expected)
134 | })
135 |
136 | it('should use provided interactive option instead of default', () => {
137 | const instance = threesixty(container, IMAGES, options)
138 | const actual = instance.isInteractive()
139 | const expected = false
140 |
141 | expect(actual).to.equal(expected)
142 | })
143 |
144 | it('should use provided currentFrame option instead of default', () => {
145 | const instance = threesixty(container, IMAGES, options)
146 | const actual = instance.getCurrentFrame()
147 | const expected = 2
148 |
149 | expect(actual).to.equal(expected)
150 | })
151 |
152 | it('should use defaults if no option is provided', () => {
153 | const instance = threesixty(container, IMAGES)
154 | const actual = instance.isReverse()
155 | const expected = false
156 |
157 | expect(actual).to.equal(expected)
158 | })
159 |
160 | it('should use provided reverse option instead of default', () => {
161 | const instance = threesixty(container, IMAGES, options)
162 | const actual = instance.isReverse()
163 | const expected = true
164 |
165 | expect(actual).to.equal(expected)
166 | })
167 | })
168 |
169 |
170 |
171 | describe('dom manipulation', () => {
172 | let instance
173 |
174 | before(() => {
175 | container = document.createElement('div')
176 | instance = threesixty(container, IMAGES)
177 | instance.init()
178 | })
179 |
180 | it('should add the first image to the container', (done) => {
181 | const expected = ``
182 | setTimeout(() => {
183 | try {
184 | instance.next()
185 | const actual = container.outerHTML
186 |
187 | expect(actual).to.equal(expected)
188 | done()
189 | } catch(e) {
190 | done(e)
191 | }
192 | }, TIMEOUT)
193 | })
194 | })
195 |
196 |
197 |
198 | describe('previous/next image', () => {
199 | let instance
200 |
201 | beforeEach(() => {
202 | container = document.createElement('div')
203 | instance = threesixty(container, IMAGES)
204 | instance.init()
205 | })
206 |
207 | it('should advance to the next image', (done) => {
208 | const expected = 1
209 | setTimeout(() => {
210 | try {
211 | instance.next()
212 | const actual = instance.getCurrentFrame()
213 |
214 | expect(actual).to.equal(expected)
215 | done()
216 | } catch(e) {
217 | done(e)
218 | }
219 | }, TIMEOUT)
220 | })
221 |
222 | it('should go back to the previous image', (done) => {
223 | const expected = LAST_IMAGE_INDEX
224 | setTimeout(() => {
225 | try {
226 | instance.previous()
227 | const actual = instance.getCurrentFrame()
228 |
229 | expect(actual).to.equal(expected)
230 | done()
231 | } catch(e) {
232 | done(e)
233 | }
234 | }, TIMEOUT)
235 | })
236 |
237 | it('should display the first image if advancing from the last', (done) => {
238 | const expected = 0
239 | setTimeout(() => {
240 | try {
241 | for (let i = 0, l = IMAGES.length; i < l; i++) {
242 | instance.next()
243 | }
244 | const actual = instance.getCurrentFrame()
245 |
246 | expect(actual).to.equal(expected)
247 | done()
248 | } catch(e) {
249 | done(e)
250 | }
251 | }, TIMEOUT)
252 | })
253 |
254 | it('should display the last image if going back from the first', (done) => {
255 | const expected = LAST_IMAGE_INDEX
256 | setTimeout(() => {
257 | try {
258 | instance.previous()
259 | const actual = instance.getCurrentFrame()
260 |
261 | expect(actual).to.equal(expected)
262 | done()
263 | } catch(e) {
264 | done(e)
265 | }
266 | }, TIMEOUT)
267 | })
268 | })
269 |
270 |
271 |
272 | describe('mouse interaction', () => {
273 | let instance
274 |
275 | describe('non-reverse', () => {
276 | beforeEach(() => {
277 | container = document.createElement('div')
278 | instance = threesixty(container, IMAGES)
279 | instance.init()
280 | })
281 |
282 | it('should advance to the next image if dragging towards right', (done) => {
283 | const expected = 1
284 | setTimeout(() => {
285 | try {
286 | clickAndDrag()
287 |
288 | const actual = instance.getCurrentFrame()
289 |
290 | expect(actual).to.equal(expected)
291 | done()
292 | } catch(e) {
293 | done(e)
294 | }
295 | }, TIMEOUT)
296 | })
297 |
298 | it('should go back to the previous image if dragging towards left', (done) => {
299 | const expected = LAST_IMAGE_INDEX
300 | setTimeout(() => {
301 | try {
302 | clickAndDrag(-1)
303 |
304 | const actual = instance.getCurrentFrame()
305 |
306 | expect(actual).to.equal(expected)
307 | done()
308 | } catch(e) {
309 | done(e)
310 | }
311 | }, TIMEOUT)
312 | })
313 |
314 | it('should go back to the first image if dragging times is equal to total number of slides', (done) => {
315 | const expected = 0
316 | setTimeout(() => {
317 | try {
318 | IMAGES.forEach((item, i) => {
319 | clickAndDrag(i + 1)
320 | })
321 |
322 | const actual = instance.getCurrentFrame()
323 |
324 | expect(actual).to.equal(expected)
325 | done()
326 | } catch(e) {
327 | done(e)
328 | }
329 | }, TIMEOUT)
330 | })
331 | })
332 |
333 | describe('reverse', () => {
334 | beforeEach(() => {
335 | container = document.createElement('div')
336 | instance = threesixty(container, IMAGES, { reverse: true })
337 | instance.init()
338 | })
339 |
340 | it('should advance to the next image if dragging towards right', (done) => {
341 | const expected = 3
342 | setTimeout(() => {
343 | try {
344 | clickAndDrag()
345 |
346 | const actual = instance.getCurrentFrame()
347 |
348 | expect(actual).to.equal(expected)
349 | done()
350 | } catch(e) {
351 | done(e)
352 | }
353 | }, TIMEOUT)
354 | })
355 |
356 | it('should go back to the previous image if dragging towards left', (done) => {
357 | const expected = 1
358 | setTimeout(() => {
359 | try {
360 | clickAndDrag(-1)
361 |
362 | const actual = instance.getCurrentFrame()
363 |
364 | expect(actual).to.equal(expected)
365 | done()
366 | } catch(e) {
367 | done(e)
368 | }
369 | }, TIMEOUT)
370 | })
371 |
372 | it('should go back to the first image if dragging times is equal to total number of slides', (done) => {
373 | const expected = 0
374 | setTimeout(() => {
375 | try {
376 | IMAGES.forEach((item, i) => {
377 | clickAndDrag(i + 1)
378 | })
379 |
380 | const actual = instance.getCurrentFrame()
381 |
382 | expect(actual).to.equal(expected)
383 | done()
384 | } catch(e) {
385 | done(e)
386 | }
387 | }, TIMEOUT)
388 | })
389 | })
390 | })
391 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom')
2 |
3 | class CustomResourceLoader extends jsdom.ResourceLoader {
4 | constructor () {
5 | super({ strictSSL: false })
6 | }
7 |
8 | fetch (url, options) {
9 | // Override the contents of this script to do something unusual.
10 | if (url === 'transparent.png') {
11 | const fs = require('fs')
12 | const path = require('path')
13 | const transparentImage = path.join(__dirname, 'transparent.png')
14 | return Promise.resolve(fs.readFileSync(transparentImage))
15 | }
16 |
17 | return super.fetch(url, options)
18 | }
19 | }
20 |
21 | const resourceLoader = new CustomResourceLoader()
22 |
23 | const dom = new jsdom.JSDOM('