├── .eslintignore ├── test └── e2e │ ├── .eslintrc │ ├── runner.js │ ├── nightwatch.config.js │ └── specs │ └── gallery.js ├── images └── demo.gif ├── src ├── index.js └── component │ └── GallerySlideshow.vue ├── .gitignore ├── .eslintrc.js ├── .editorconfig ├── .npmignore ├── .babelrc ├── .travis.yml ├── rollup.config.js ├── LICENSE ├── selenium-debug.log ├── examples └── gallery │ └── index.html ├── CHANGELOG.md ├── package.json ├── README.md └── dist └── js ├── vue-gallery-slideshow.min.js └── vue-gallery-slideshow.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /test 2 | /dist 3 | /node_modules 4 | /build 5 | -------------------------------------------------------------------------------- /test/e2e/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": 0 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KitchenStories/vue-gallery-slideshow/HEAD/images/demo.gif -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import GallerySlideshow from "./component/GallerySlideshow.vue"; 2 | 3 | export default GallerySlideshow; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .code-workspace 4 | node_modules 5 | npm-debug.log 6 | test/e2e/reports 7 | test/e2e/screenshots 8 | selenium-server.log -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": [ 3 | "plugin:vue/recommended" 4 | ], 5 | "rules": { 6 | "semi": ["error", "always"], 7 | "quotes": ["error", "double"] 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .gitignore 3 | .npmignore 4 | .idea/ 5 | .travis.yml 6 | doc/ 7 | .git/ 8 | _config.yml 9 | *.md 10 | .codeclimate.yml 11 | examples/ 12 | src/ 13 | test/ 14 | images/ 15 | CHANGELOG.md 16 | rollup.config.js 17 | .eslintignore 18 | .eslintrc.js 19 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": [ 7 | "> 1%", 8 | "Chrome >= 14", 9 | "Safari >= 4", 10 | "Firefox >= 4", 11 | "Opera >= 10", 12 | "Edge >= 41", 13 | "not ie <= 7", 14 | "iOS >= 6", 15 | "ChromeAndroid >= 4", 16 | "OperaMobile >= 12" 17 | ] 18 | } 19 | }] 20 | ], 21 | "plugins": [ "@babel/plugin-transform-object-assign"] 22 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "stable" 5 | 6 | services: 7 | - xvfb 8 | 9 | before_install: 10 | - export CHROME_BIN=/usr/bin/google-chrome 11 | - export DISPLAY=:99.0 12 | # - sh -e /etc/init.d/xvfb start 13 | - sudo apt-get update 14 | - sudo apt-get install -y libappindicator1 fonts-liberation 15 | - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 16 | - sudo dpkg -i google-chrome*.deb 17 | 18 | install: 19 | - npm install 20 | 21 | script: 22 | - npm run lint 23 | - npm run build 24 | - npm test 25 | 26 | notifications: 27 | email: false 28 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import vue from "rollup-plugin-vue"; 2 | import babel from "rollup-plugin-babel"; 3 | import { terser } from "rollup-plugin-terser"; 4 | import commonjs from "rollup-plugin-commonjs"; 5 | 6 | export default { 7 | input: "src/index.js", 8 | plugins: [ 9 | commonjs(), 10 | vue(), 11 | babel({ 12 | babelrc: true, 13 | runtimeHelpers: true, 14 | externalHelpers: false, 15 | exclude: "node_modules/**", 16 | }), 17 | (process.env.BUILD_MODE === "minify" && terser()) 18 | ], 19 | output: { 20 | file: process.env.BUILD_MODE === "minify" ? "dist/js/vue-gallery-slideshow.min.js" : "dist/js/vue-gallery-slideshow.js", 21 | format: "umd", 22 | name: "VueGallerySlideshow", 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var spawn = require('cross-spawn') 3 | var httpServer = require('http-server') 4 | var server = httpServer.createServer({ 5 | root: path.resolve(__dirname, '../../') 6 | }) 7 | 8 | server.listen(8080) 9 | 10 | var args = process.argv.slice(2) 11 | if (args.indexOf('--config') === -1) { 12 | args = args.concat(['--config', 'test/e2e/nightwatch.config.js']) 13 | } 14 | if (args.indexOf('--env') === -1) { 15 | // args = args.concat(['--env', 'chrome,phantomjs']) 16 | args = args.concat(['--env', 'chrome']) 17 | } 18 | var i = args.indexOf('--test') 19 | if (i > -1) { 20 | args[i + 1] = 'test/e2e/specs/' + args[i + 1] + '.js' 21 | } 22 | 23 | var runner = spawn('./node_modules/.bin/nightwatch', args, { 24 | stdio: 'inherit' 25 | }) 26 | 27 | runner.on('exit', function (code) { 28 | server.close() 29 | process.exit(code) 30 | }) 31 | 32 | runner.on('error', function (err) { 33 | server.close() 34 | throw err 35 | }) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Norman Sander 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /selenium-debug.log: -------------------------------------------------------------------------------- 1 | 10:37:44.150 INFO [GridLauncherV3.parse] - Selenium server version: 3.141.59, revision: e82be7d358 2 | 10:37:44.256 INFO [GridLauncherV3.lambda$buildLaunchers$3] - Launching a standalone Selenium Server on port 4444 3 | 2019-10-30 10:37:44.354:INFO::main: Logging initialized @621ms to org.seleniumhq.jetty9.util.log.StdErrLog 4 | 10:37:44.716 INFO [WebDriverServlet.] - Initialising WebDriverServlet 5 | 10:37:44.833 INFO [SeleniumServer.boot] - Selenium Server is up and running on port 4444 6 | 10:37:45.024 INFO [ActiveSessionFactory.apply] - Capabilities are: { 7 | "acceptSslCerts": true, 8 | "browserName": "chrome", 9 | "javascriptEnabled": true, 10 | "name": "Gallery" 11 | } 12 | 10:37:45.025 INFO [ActiveSessionFactory.lambda$apply$11] - Matched factory org.openqa.selenium.grid.session.remote.ServicedSession$Factory (provider: org.openqa.selenium.chrome.ChromeDriverService) 13 | Starting ChromeDriver 75.0.3770.90 (a6dcaf7e3ec6f70a194cc25e8149475c6590e025-refs/branch-heads/3770@{#1003}) on port 42848 14 | Only local connections are allowed. 15 | Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code. 16 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.config.js: -------------------------------------------------------------------------------- 1 | // http://nightwatchjs.org/guide#settings-file 2 | module.exports = { 3 | 'src_folders': ['test/e2e/specs'], 4 | 'output_folder': 'test/e2e/reports', 5 | 'custom_commands_path': ['node_modules/nightwatch-helpers/commands'], 6 | 'custom_assertions_path': ['node_modules/nightwatch-helpers/assertions'], 7 | 8 | 'selenium': { 9 | 'start_process': true, 10 | 'server_path': require('selenium-server').path, 11 | 'host': '127.0.0.1', 12 | 'port': 4444, 13 | 'cli_args': { 14 | 'webdriver.chrome.driver': require('chromedriver').path 15 | // , 'webdriver.gecko.driver': require('geckodriver').path 16 | } 17 | }, 18 | 19 | 'test_settings': { 20 | 'default': { 21 | 'selenium_port': 4444, 22 | 'selenium_host': 'localhost', 23 | 'silent': true, 24 | 'screenshots': { 25 | 'enabled': true, 26 | 'on_failure': true, 27 | 'on_error': false, 28 | 'path': 'test/e2e/screenshots' 29 | } 30 | }, 31 | 32 | 'chrome': { 33 | 'desiredCapabilities': { 34 | 'browserName': 'chrome', 35 | 'javascriptEnabled': true, 36 | 'acceptSslCerts': true 37 | } 38 | }, 39 | 40 | 'firefox': { 41 | 'desiredCapabilities': { 42 | 'browserName': 'firefox', 43 | 'javascriptEnabled': true, 44 | 'acceptSslCerts': true, 45 | 'marionette': true 46 | } 47 | }, 48 | 49 | 'phantomjs': { 50 | 'desiredCapabilities': { 51 | 'browserName': 'phantomjs', 52 | 'javascriptEnabled': true, 53 | 'acceptSslCerts': true 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/gallery/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 23 | 24 | 25 |
26 | 27 | 28 |
29 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [1.5.2] -2020-08-25 6 | 7 | ### Fixed 8 | 9 | - On close event, you should return last index `imgIndex` https://github.com/KitchenStories/vue-gallery-slideshow/issues/17 10 | 11 | ## [1.5.1] -2020-07-14 12 | 13 | ### Fixed 14 | 15 | - Added vue-runtime-helpers to project's bundle 16 | 17 | ## [1.5.0] -2019-10-30 18 | 19 | ### Added 20 | 21 | - Alt text option 22 | 23 | ## [1.4.0] -2019-08-10 24 | 25 | ### Added 26 | 27 | - Close gallery on ESC 28 | 29 | ## [1.3.2] -2019-07-28 30 | 31 | ### Updated 32 | 33 | - all dependencies 34 | 35 | ## [1.3.1] -2019-02-06 36 | 37 | ### Fixed 38 | 39 | - images with landscape formats bigger than ratio 1:0.672 are not completely visible 40 | 41 | ## [1.3.0] -2019-01-16 42 | 43 | ### Fixed 44 | 45 | - Prevent scrolling if not needed 46 | - Hide arrows and gallery stripe if only one image is shown 47 | 48 | ## [1.2.5] -2018-10-27 49 | 50 | ### Fixed 51 | 52 | - gallery opens on left and right keys 53 | 54 | ## [1.2.4] -2018-08-03 55 | 56 | ### Fixed 57 | 58 | - build dist with babel 59 | 60 | ### Updated 61 | 62 | - all packages 63 | 64 | ## [1.2.3] -2018-06-11 65 | 66 | ### Updated 67 | 68 | - all packages 69 | 70 | ## [1.2.2] -2018-05-18 71 | 72 | ### Fixed 73 | 74 | - open same picture after close not working 75 | 76 | ## [1.2.1] -2018-05-18 77 | 78 | ### Added 79 | 80 | - minimize package size 81 | 82 | ## [1.2.0] -2018-05-18 83 | 84 | ### Added 85 | 86 | - linting 87 | - style fixes 88 | - transition fade effect 89 | 90 | ### Changed 91 | 92 | - example page 93 | 94 | ## [1.1.0] - 2018-05-17 95 | 96 | ### Changed 97 | 98 | - style ui elements 99 | 100 | ### Fixed 101 | 102 | - update position of thumbnails 103 | - main path in package.json 104 | 105 | ### Updated 106 | 107 | - rollup 108 | -------------------------------------------------------------------------------- /test/e2e/specs/gallery.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'gallery': function (browser) { 3 | browser 4 | .url('http://localhost:8080/examples/gallery/') 5 | .waitForElementVisible('#app', 1000) 6 | .assert.elementNotPresent('.vgs') 7 | .waitFor(500) 8 | .keys([browser.Keys.RIGHT_ARROW], () => { 9 | browser.assert.elementNotPresent('.vgs') 10 | }) 11 | .waitFor(500) 12 | .click('img:nth-child(1)') 13 | .waitFor(500) 14 | .assert.elementPresent('.vgs') 15 | .assert.cssClassPresent('.vgs__gallery__container__img:nth-child(1)', 'vgs__gallery__container__img--active') 16 | .assert.cssClassNotPresent('.vgs__gallery__container__img:nth-child(2)', 'vgs__gallery__container__img--active') 17 | .assert.containsText('.vgs__gallery__title', '1 / 10') 18 | .click('.vgs__next') 19 | .waitFor(500) 20 | .assert.cssClassPresent('.vgs__gallery__container__img:nth-child(2)', 'vgs__gallery__container__img--active') 21 | .assert.containsText('.vgs__gallery__title', '2 / 10') 22 | .click('.vgs__container__img') 23 | .waitFor(500) 24 | .assert.cssClassPresent('.vgs__gallery__container__img:nth-child(3)', 'vgs__gallery__container__img--active') 25 | .assert.containsText('.vgs__gallery__title', '3 / 10') 26 | .click('.vgs__prev') 27 | .waitFor(500) 28 | .assert.cssClassPresent('.vgs__gallery__container__img:nth-child(2)', 'vgs__gallery__container__img--active') 29 | .assert.containsText('.vgs__gallery__title', '2 / 10') 30 | .click('.vgs__gallery__container__img:nth-child(4)') 31 | .waitFor(500) 32 | .assert.cssClassPresent('.vgs__gallery__container__img:nth-child(4)', 'vgs__gallery__container__img--active') 33 | .assert.containsText('.vgs__gallery__title', '4 / 10') 34 | .waitFor(500) 35 | // .keys([browser.Keys.RIGHT_ARROW], () => { 36 | // browser.assert.containsText('.vgs__gallery__title', '5 / 10') 37 | // }) 38 | .click('.vgs__close') 39 | .waitFor(500) 40 | .assert.elementNotPresent('.vgs') 41 | .end() 42 | }, 43 | // TODO: Fix key events 44 | // 'gallery closes with esc': function (browser) { 45 | // browser 46 | // .url('http://localhost:8080/examples/gallery/') 47 | // .waitForElementVisible('#app', 1000) 48 | // .waitFor(500) 49 | // .click('img:nth-child(1)') 50 | // .waitFor(500) 51 | // .assert.elementPresent('.vgs') 52 | // .keys(browser.Keys.ESC) 53 | // .waitFor(500) 54 | // .assert.elementNotPresent('.vgs') 55 | // .end() 56 | // } 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-gallery-slideshow", 3 | "version": "1.5.2", 4 | "description": "Lightweight and responsive image gallery for Vue.js.", 5 | "main": "dist/js/vue-gallery-slideshow.js", 6 | "jsnext:main": "src/index.js", 7 | "unpkg": "dist/js/vue-gallery-slideshow.min.js", 8 | "files": [ 9 | "dist/js/vue-gallery-slideshow.min.js", 10 | "dist/js/vue-gallery-slideshow.js" 11 | ], 12 | "scripts": { 13 | "prepare": "npm run build", 14 | "test": "npm run build && node test/e2e/runner.js", 15 | "lint": "eslint --ext .js,.vue src", 16 | "build": "NODE_ENV=production node_modules/.bin/rollup -c ./rollup.config.js && NODE_ENV=production BUILD_MODE=minify node_modules/.bin/rollup -c ./rollup.config.js", 17 | "patch": "npm version patch", 18 | "minor": "npm version minor", 19 | "major": "npm version major", 20 | "release": "./build/publish.sh" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/KitchenStories/vue-gallery-slideshow.git" 25 | }, 26 | "keywords": [ 27 | "vue-gallery-slideshow", 28 | "gallery", 29 | "slideshow", 30 | "images", 31 | "photos", 32 | "viewer", 33 | "vue-component", 34 | "vue-plugin", 35 | "slider", 36 | "viewer", 37 | "carousel", 38 | "vue", 39 | "vuejs", 40 | "vuejs-plugin" 41 | ], 42 | "author": "Norman Sander", 43 | "license": "MIT", 44 | "bugs": { 45 | "url": "https://github.com/KitchenStories/vue-gallery-slideshow/issues" 46 | }, 47 | "homepage": "https://github.com/KitchenStories/vue-gallery-slideshow#readme", 48 | "devDependencies": { 49 | "@babel/core": "^7.6.4", 50 | "@babel/plugin-transform-object-assign": "^7.10.4", 51 | "@babel/preset-env": "^7.6.3", 52 | "chromedriver": "^75.1.0", 53 | "cross-spawn": "^6.0.5", 54 | "eslint": "^6.6.0", 55 | "eslint-plugin-vue": "^5.2.3", 56 | "http-server": "^0.11.1", 57 | "nightwatch": "^0.9.21", 58 | "nightwatch-helpers": "^1.2.0", 59 | "node-sass": "^4.13.0", 60 | "path": "^0.12.7", 61 | "phantomjs-prebuilt": "^2.1.16", 62 | "rollup": "^1.26.0", 63 | "rollup-plugin-babel": "^4.4.0", 64 | "rollup-plugin-commonjs": "^10.1.0", 65 | "rollup-plugin-terser": "^5.1.2", 66 | "rollup-plugin-vue": "^5.1.2", 67 | "selenium-server": "^3.141.59", 68 | "vue-template-compiler": "^2.6.11", 69 | "webdriver-manager": "^12.1.7" 70 | }, 71 | "browserslist": [ 72 | "> 1%", 73 | "Chrome >= 14", 74 | "Safari >= 4", 75 | "Firefox >= 4", 76 | "Opera >= 10", 77 | "Edge >= 41", 78 | "not ie <= 7", 79 | "iOS >= 6", 80 | "ChromeAndroid >= 4", 81 | "OperaMobile >= 12" 82 | ], 83 | "dependencies": { 84 | "vue-runtime-helpers": "^1.1.2" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![npm](https://img.shields.io/npm/dt/vue-gallery-slideshow.svg) 2 | 3 | # vue-gallery-slideshow 4 | 5 | Lightweight and responsive image gallery for Vue.js. 6 | 7 | ![](https://github.com/KitchenStories/vue-gallery-slideshow/blob/master/images/demo.gif) 8 | 9 | ## Live Demo 10 | 11 | [https://jsfiddle.net/headione/szk73x45/show/](https://jsfiddle.net/headione/szk73x45/show/) 12 | 13 | ## Installation 14 | 15 | #### By CDN 16 | 17 | ```html 18 | 19 | ``` 20 | 21 | #### By package manager 22 | 23 | ```bash 24 | npm install vue-gallery-slideshow 25 | ``` 26 | 27 | ```bash 28 | yarn add vue-gallery-slideshow 29 | ``` 30 | 31 | ## Usage 32 | 33 | #### HTML 34 | 35 | ```html 36 |
37 | 38 | 39 |
40 | ``` 41 | 42 | #### JavaScript 43 | 44 | ```javascript 45 | import VueGallerySlideshow from 'vue-gallery-slideshow'; 46 | 47 | const app = new Vue({ 48 | el: '#app', 49 | components: { 50 | VueGallerySlideshow 51 | }, 52 | data: { 53 | images: [ 54 | 'https://placekitten.com/801/800', 55 | 'https://placekitten.com/802/800', 56 | 'https://placekitten.com/803/800', 57 | 'https://placekitten.com/804/800', 58 | 'https://placekitten.com/805/800', 59 | 'https://placekitten.com/806/800', 60 | 'https://placekitten.com/807/800', 61 | 'https://placekitten.com/808/800', 62 | 'https://placekitten.com/809/800', 63 | 'https://placekitten.com/810/800' 64 | ], 65 | index: null 66 | } 67 | }); 68 | ``` 69 | 70 | ### Options 71 | 72 | ## Adding alt text 73 | 74 | If you want to add alt tags to the images, you can do by wrapping it in an object and adding an `alt` property: 75 | 76 | ```javascript 77 | images: [ 78 | { url: 'https://placem.at/places?w=800&h=600&random=1', alt:'My alt text' }, 79 | ... 80 | ] 81 | ``` 82 | 83 | ## Usage with Nuxt.js 84 | 85 | When used with server-side rendering frameworks like Nuxt.js, please wrap the component in a `` element like shown below: 86 | 87 | ```html 88 | ... 89 | 90 | 91 | 92 | 93 | ... 94 | ``` 95 | 96 | ## Contributing 97 | 98 | Please refer to each project's style guidelines and guidelines for submitting patches and additions. In general, we follow the "fork-and-pull" Git workflow. 99 | 100 | 1. **Fork** the repo on GitHub 101 | 2. **Clone** the project to your machine 102 | 3. **Commit** changes to your branch 103 | 4. **Push** your work back up to your fork 104 | 5. Submit a **Pull request** so that we can review your changes 105 | 106 | NOTE: Be sure to merge the latest from "upstream" before making a pull request! 107 | 108 | ## Author 109 | 110 | Norman Sander 111 | 112 | ## License 113 | 114 | vue-gallery-slideshow is available under the MIT license. See the LICENSE file for more info. 115 | -------------------------------------------------------------------------------- /dist/js/vue-gallery-slideshow.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).VueGallerySlideshow=e()}(this,(function(){"use strict";function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e={props:{images:{type:Array,required:!0},index:{type:Number,required:!1,default:null}},data:function(){return{imgIndex:this.index,image:null,galleryXPos:0,thumbnailWidth:120}},computed:{imageUrl:function(){var t=this.images[this.imgIndex];return"string"==typeof t?t:t.url},alt:function(){var e=this.images[this.imgIndex];return"object"===t(e)?e.alt:""},isMultiple:function(){return this.images.length>1}},watch:{index:function(t,e){var i=this;this.imgIndex=t,null==e&&null!=t&&this.$nextTick((function(){i.updateThumbails()}))}},mounted:function(){var t=this;window.addEventListener("keydown",(function(e){37===e.keyCode?t.onPrev():39===e.keyCode?t.onNext():27===e.keyCode&&t.close()}))},methods:{close:function(){var t={imgIndex:this.imgIndex};this.imgIndex=null,this.$emit("close",t)},onPrev:function(){null!==this.imgIndex&&(this.imgIndex>0?this.imgIndex--:this.imgIndex=this.images.length-1,this.updateThumbails())},onNext:function(){null!==this.imgIndex&&(this.imgIndexthis.images.length*this.thumbnailWidth-t+n?this.galleryXPos=-(this.images.length*this.thumbnailWidth-t-20):this.galleryXPos=-this.imgIndex*this.thumbnailWidth+n)}}}};function i(t,e,i,n,o,s,a,r,l,d){"boolean"!=typeof a&&(l=r,r=a,a=!1);const c="function"==typeof i?i.options:i;let h;if(t&&t.render&&(c.render=t.render,c.staticRenderFns=t.staticRenderFns,c._compiled=!0,o&&(c.functional=!0)),n&&(c._scopeId=n),s?(h=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),e&&e.call(this,l(t)),t&&t._registeredComponents&&t._registeredComponents.add(s)},c._ssrRegister=h):e&&(h=a?function(t){e.call(this,d(t,this.$root.$options.shadowRoot))}:function(t){e.call(this,r(t))}),h)if(c.functional){const t=c.render;c.render=function(e,i){return h.call(i),t(e,i)}}else{const t=c.beforeCreate;c.beforeCreate=t?[].concat(t,h):[h]}return i}const n="undefined"!=typeof navigator&&/msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());function o(t){return(t,e)=>function(t,e){const i=n?e.media||"default":t,o=a[i]||(a[i]={ids:new Set,styles:[]});if(!o.ids.has(t)){o.ids.add(t);let i=e.source;if(e.map&&(i+="\n/*# sourceURL="+e.map.sources[0]+" */",i+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e.map))))+" */"),o.element||(o.element=document.createElement("style"),o.element.type="text/css",e.media&&o.element.setAttribute("media",e.media),void 0===s&&(s=document.head||document.getElementsByTagName("head")[0]),s.appendChild(o.element)),"styleSheet"in o.element)o.styles.push(i),o.element.styleSheet.cssText=o.styles.filter(Boolean).join("\n");else{const t=o.ids.size-1,e=document.createTextNode(i),n=o.element.childNodes;n[t]&&o.element.removeChild(n[t]),n.length?o.element.insertBefore(e,n[t]):o.element.appendChild(e)}}}(t,e)}let s;const a={};return i({render:function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("transition",{attrs:{name:"modal"}},[null!==t.imgIndex?i("div",{staticClass:"vgs",on:{click:t.close}},[i("button",{staticClass:"vgs__close",attrs:{type:"button"},on:{click:t.close}},[t._v("\n ×\n ")]),t._v(" "),t.isMultiple?i("button",{staticClass:"vgs__prev",attrs:{type:"button"},on:{click:function(e){return e.stopPropagation(),t.onPrev(e)}}},[t._v("\n ‹\n ")]):t._e(),t._v(" "),t.images?i("div",{staticClass:"vgs__container",on:{click:function(e){return e.stopPropagation(),t.onNext(e)}}},[i("img",{staticClass:"vgs__container__img",attrs:{src:t.imageUrl,alt:t.alt},on:{click:function(e){return e.stopPropagation(),t.onNext(e)}}}),t._v(" "),t._t("default")],2):t._e(),t._v(" "),t.isMultiple?i("button",{staticClass:"vgs__next",attrs:{type:"button"},on:{click:function(e){return e.stopPropagation(),t.onNext(e)}}},[t._v("\n ›\n ")]):t._e(),t._v(" "),t.isMultiple?i("div",{ref:"gallery",staticClass:"vgs__gallery"},[t.images?i("div",{staticClass:"vgs__gallery__title"},[t._v("\n "+t._s(t.imgIndex+1)+" / "+t._s(t.images.length)+"\n ")]):t._e(),t._v(" "),t.images?i("div",{staticClass:"vgs__gallery__container",style:{transform:"translate("+t.galleryXPos+"px, 0)"}},t._l(t.images,(function(e,n){return i("img",{key:n,staticClass:"vgs__gallery__container__img",class:{"vgs__gallery__container__img--active":n===t.imgIndex},attrs:{src:"string"==typeof e?e:e.url,alt:"string"==typeof e?"":e.alt},on:{click:function(i){return i.stopPropagation(),t.onClickThumb(e,n)}}})})),0):t._e()]):t._e()]):t._e()])},staticRenderFns:[]},(function(t){t&&t("data-v-e9cc33d2_0",{source:".vgs{transition:opacity .2s ease;position:fixed;z-index:9998;top:0;left:0;width:100%;min-height:100%;height:100vh;background-color:rgba(0,0,0,.8);display:table}.vgs__close{color:#fff;position:absolute;top:0;right:0;background-color:transparent;border:none;font-size:25px;width:50px;height:50px;cursor:pointer;z-index:999}.vgs__close:focus{outline:0}.vgs__next,.vgs__prev{position:absolute;top:50%;margin-top:-25px;width:50px;height:50px;z-index:999;cursor:pointer;font-size:40px;color:#fff;background-color:transparent;border:none}.vgs__next:focus,.vgs__prev:focus{outline:0}.vgs__prev{left:0}.vgs__next{right:0}.vgs__container{position:absolute;overflow:hidden;cursor:pointer;overflow:hidden;max-width:100vh;margin:.5rem auto 0;left:.5rem;right:.5rem;height:60vh;border-radius:12px;background-color:#000}@media (max-width:767px){.vgs__container{width:100%;max-width:100%;top:50%;margin-top:-140px;left:0;right:0;border-radius:0;height:280px}}.vgs__container__img{width:100%;height:100%;object-fit:contain}.vgs__gallery{overflow-x:hidden;overflow-y:hidden;position:absolute;bottom:10px;margin:auto;max-width:100vh;white-space:nowrap;left:.5rem;right:.5rem}@media (max-width:767px){.vgs__gallery{display:none}}.vgs__gallery__title{color:#fff;margin-bottom:.5rem}.vgs__gallery__container{overflow:visible;display:block;height:100px;white-space:nowrap;transition:all .2s ease-in-out;width:100%}.vgs__gallery__container__img{width:100px;height:100px;object-fit:cover;display:inline-block;float:none;margin-right:20px;cursor:pointer;opacity:.6;border-radius:8px}.vgs__gallery__container__img--active{width:100px;display:inline-block;float:none;opacity:1}.modal-enter{opacity:0}.modal-leave-active{opacity:0}",map:void 0,media:void 0})}),e,void 0,!1,void 0,!1,o,void 0,void 0)})); 2 | -------------------------------------------------------------------------------- /src/component/GallerySlideshow.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 204 | 205 | 381 | -------------------------------------------------------------------------------- /dist/js/vue-gallery-slideshow.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global = global || self, global.VueGallerySlideshow = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | function _typeof(obj) { 8 | "@babel/helpers - typeof"; 9 | 10 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 11 | _typeof = function (obj) { 12 | return typeof obj; 13 | }; 14 | } else { 15 | _typeof = function (obj) { 16 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 17 | }; 18 | } 19 | 20 | return _typeof(obj); 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 | // 64 | // 65 | // 66 | // 67 | // 68 | // 69 | // 70 | // 71 | // 72 | // 73 | // 74 | // 75 | // 76 | // 77 | // 78 | // 79 | // 80 | // 81 | // 82 | // 83 | // 84 | // 85 | // 86 | // 87 | // 88 | // 89 | // 90 | // 91 | // 92 | // 93 | // 94 | // 95 | // 96 | // 97 | var script = { 98 | props: { 99 | images: { 100 | type: Array, 101 | required: true 102 | }, 103 | index: { 104 | type: Number, 105 | required: false, 106 | "default": null 107 | } 108 | }, 109 | data: function data() { 110 | return { 111 | imgIndex: this.index, 112 | image: null, 113 | galleryXPos: 0, 114 | thumbnailWidth: 120 115 | }; 116 | }, 117 | computed: { 118 | imageUrl: function imageUrl() { 119 | var img = this.images[this.imgIndex]; 120 | 121 | if (typeof img === "string") { 122 | return img; 123 | } 124 | 125 | return img.url; 126 | }, 127 | alt: function alt() { 128 | var img = this.images[this.imgIndex]; 129 | 130 | if (_typeof(img) === "object") { 131 | return img.alt; 132 | } 133 | 134 | return ""; 135 | }, 136 | isMultiple: function isMultiple() { 137 | return this.images.length > 1; 138 | } 139 | }, 140 | watch: { 141 | index: function index(val, prev) { 142 | var _this = this; 143 | 144 | this.imgIndex = val; // updateThumbails when popup 145 | 146 | if (prev == null && val != null) { 147 | this.$nextTick(function () { 148 | _this.updateThumbails(); 149 | }); 150 | } 151 | } 152 | }, 153 | mounted: function mounted() { 154 | var _this2 = this; 155 | 156 | window.addEventListener("keydown", function (e) { 157 | if (e.keyCode === 37) { 158 | _this2.onPrev(); 159 | } else if (e.keyCode === 39) { 160 | _this2.onNext(); 161 | } else if (e.keyCode === 27) { 162 | _this2.close(); 163 | } 164 | }); 165 | }, 166 | methods: { 167 | close: function close() { 168 | var eventData = { 169 | imgIndex: this.imgIndex 170 | }; 171 | this.imgIndex = null; 172 | this.$emit("close", eventData); 173 | }, 174 | onPrev: function onPrev() { 175 | if (this.imgIndex === null) return; 176 | 177 | if (this.imgIndex > 0) { 178 | this.imgIndex--; 179 | } else { 180 | this.imgIndex = this.images.length - 1; 181 | } 182 | 183 | this.updateThumbails(); 184 | }, 185 | onNext: function onNext() { 186 | if (this.imgIndex === null) return; 187 | 188 | if (this.imgIndex < this.images.length - 1) { 189 | this.imgIndex++; 190 | } else { 191 | this.imgIndex = 0; 192 | } 193 | 194 | this.updateThumbails(); 195 | }, 196 | onClickThumb: function onClickThumb(image, index) { 197 | this.imgIndex = index; 198 | this.updateThumbails(); 199 | }, 200 | updateThumbails: function updateThumbails() { 201 | if (!this.$refs.gallery) { 202 | return; 203 | } 204 | 205 | var galleryWidth = this.$refs.gallery.clientWidth; 206 | var currThumbsWidth = this.imgIndex * this.thumbnailWidth; 207 | var maxThumbsWidth = this.images.length * this.thumbnailWidth; 208 | var centerPos = Math.floor(galleryWidth / (this.thumbnailWidth * 2)) * this.thumbnailWidth; // Prevent scrolling of images if not needed 209 | 210 | if (maxThumbsWidth < galleryWidth) { 211 | return; 212 | } 213 | 214 | if (currThumbsWidth < centerPos) { 215 | this.galleryXPos = 0; 216 | } else if (currThumbsWidth > this.images.length * this.thumbnailWidth - galleryWidth + centerPos) { 217 | this.galleryXPos = -(this.images.length * this.thumbnailWidth - galleryWidth - 20); 218 | } else { 219 | this.galleryXPos = -(this.imgIndex * this.thumbnailWidth) + centerPos; 220 | } 221 | } 222 | } 223 | }; 224 | 225 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 226 | if (typeof shadowMode !== 'boolean') { 227 | createInjectorSSR = createInjector; 228 | createInjector = shadowMode; 229 | shadowMode = false; 230 | } 231 | // Vue.extend constructor export interop. 232 | const options = typeof script === 'function' ? script.options : script; 233 | // render functions 234 | if (template && template.render) { 235 | options.render = template.render; 236 | options.staticRenderFns = template.staticRenderFns; 237 | options._compiled = true; 238 | // functional template 239 | if (isFunctionalTemplate) { 240 | options.functional = true; 241 | } 242 | } 243 | // scopedId 244 | if (scopeId) { 245 | options._scopeId = scopeId; 246 | } 247 | let hook; 248 | if (moduleIdentifier) { 249 | // server build 250 | hook = function (context) { 251 | // 2.3 injection 252 | context = 253 | context || // cached call 254 | (this.$vnode && this.$vnode.ssrContext) || // stateful 255 | (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional 256 | // 2.2 with runInNewContext: true 257 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 258 | context = __VUE_SSR_CONTEXT__; 259 | } 260 | // inject component styles 261 | if (style) { 262 | style.call(this, createInjectorSSR(context)); 263 | } 264 | // register component module identifier for async chunk inference 265 | if (context && context._registeredComponents) { 266 | context._registeredComponents.add(moduleIdentifier); 267 | } 268 | }; 269 | // used by ssr in case component is cached and beforeCreate 270 | // never gets called 271 | options._ssrRegister = hook; 272 | } 273 | else if (style) { 274 | hook = shadowMode 275 | ? function (context) { 276 | style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot)); 277 | } 278 | : function (context) { 279 | style.call(this, createInjector(context)); 280 | }; 281 | } 282 | if (hook) { 283 | if (options.functional) { 284 | // register for functional component in vue file 285 | const originalRender = options.render; 286 | options.render = function renderWithStyleInjection(h, context) { 287 | hook.call(context); 288 | return originalRender(h, context); 289 | }; 290 | } 291 | else { 292 | // inject component registration as beforeCreate hook 293 | const existing = options.beforeCreate; 294 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 295 | } 296 | } 297 | return script; 298 | } 299 | 300 | const isOldIE = typeof navigator !== 'undefined' && 301 | /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase()); 302 | function createInjector(context) { 303 | return (id, style) => addStyle(id, style); 304 | } 305 | let HEAD; 306 | const styles = {}; 307 | function addStyle(id, css) { 308 | const group = isOldIE ? css.media || 'default' : id; 309 | const style = styles[group] || (styles[group] = { ids: new Set(), styles: [] }); 310 | if (!style.ids.has(id)) { 311 | style.ids.add(id); 312 | let code = css.source; 313 | if (css.map) { 314 | // https://developer.chrome.com/devtools/docs/javascript-debugging 315 | // this makes source maps inside style tags work properly in Chrome 316 | code += '\n/*# sourceURL=' + css.map.sources[0] + ' */'; 317 | // http://stackoverflow.com/a/26603875 318 | code += 319 | '\n/*# sourceMappingURL=data:application/json;base64,' + 320 | btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) + 321 | ' */'; 322 | } 323 | if (!style.element) { 324 | style.element = document.createElement('style'); 325 | style.element.type = 'text/css'; 326 | if (css.media) 327 | style.element.setAttribute('media', css.media); 328 | if (HEAD === undefined) { 329 | HEAD = document.head || document.getElementsByTagName('head')[0]; 330 | } 331 | HEAD.appendChild(style.element); 332 | } 333 | if ('styleSheet' in style.element) { 334 | style.styles.push(code); 335 | style.element.styleSheet.cssText = style.styles 336 | .filter(Boolean) 337 | .join('\n'); 338 | } 339 | else { 340 | const index = style.ids.size - 1; 341 | const textNode = document.createTextNode(code); 342 | const nodes = style.element.childNodes; 343 | if (nodes[index]) 344 | style.element.removeChild(nodes[index]); 345 | if (nodes.length) 346 | style.element.insertBefore(textNode, nodes[index]); 347 | else 348 | style.element.appendChild(textNode); 349 | } 350 | } 351 | } 352 | 353 | /* script */ 354 | const __vue_script__ = script; 355 | 356 | /* template */ 357 | var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('transition',{attrs:{"name":"modal"}},[(_vm.imgIndex !== null)?_c('div',{staticClass:"vgs",on:{"click":_vm.close}},[_c('button',{staticClass:"vgs__close",attrs:{"type":"button"},on:{"click":_vm.close}},[_vm._v("\n ×\n ")]),_vm._v(" "),(_vm.isMultiple)?_c('button',{staticClass:"vgs__prev",attrs:{"type":"button"},on:{"click":function($event){$event.stopPropagation();return _vm.onPrev($event)}}},[_vm._v("\n ‹\n ")]):_vm._e(),_vm._v(" "),(_vm.images)?_c('div',{staticClass:"vgs__container",on:{"click":function($event){$event.stopPropagation();return _vm.onNext($event)}}},[_c('img',{staticClass:"vgs__container__img",attrs:{"src":_vm.imageUrl,"alt":_vm.alt},on:{"click":function($event){$event.stopPropagation();return _vm.onNext($event)}}}),_vm._v(" "),_vm._t("default")],2):_vm._e(),_vm._v(" "),(_vm.isMultiple)?_c('button',{staticClass:"vgs__next",attrs:{"type":"button"},on:{"click":function($event){$event.stopPropagation();return _vm.onNext($event)}}},[_vm._v("\n ›\n ")]):_vm._e(),_vm._v(" "),(_vm.isMultiple)?_c('div',{ref:"gallery",staticClass:"vgs__gallery"},[(_vm.images)?_c('div',{staticClass:"vgs__gallery__title"},[_vm._v("\n "+_vm._s(_vm.imgIndex + 1)+" / "+_vm._s(_vm.images.length)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.images)?_c('div',{staticClass:"vgs__gallery__container",style:({ transform: 'translate(' + _vm.galleryXPos + 'px, 0)' })},_vm._l((_vm.images),function(img,i){return _c('img',{key:i,staticClass:"vgs__gallery__container__img",class:{ 'vgs__gallery__container__img--active': i === _vm.imgIndex},attrs:{"src":typeof img === 'string' ? img : img.url,"alt":typeof img === 'string' ? '' : img.alt},on:{"click":function($event){$event.stopPropagation();return _vm.onClickThumb(img, i)}}})}),0):_vm._e()]):_vm._e()]):_vm._e()])}; 358 | var __vue_staticRenderFns__ = []; 359 | 360 | /* style */ 361 | const __vue_inject_styles__ = function (inject) { 362 | if (!inject) return 363 | inject("data-v-e9cc33d2_0", { source: ".vgs{transition:opacity .2s ease;position:fixed;z-index:9998;top:0;left:0;width:100%;min-height:100%;height:100vh;background-color:rgba(0,0,0,.8);display:table}.vgs__close{color:#fff;position:absolute;top:0;right:0;background-color:transparent;border:none;font-size:25px;width:50px;height:50px;cursor:pointer;z-index:999}.vgs__close:focus{outline:0}.vgs__next,.vgs__prev{position:absolute;top:50%;margin-top:-25px;width:50px;height:50px;z-index:999;cursor:pointer;font-size:40px;color:#fff;background-color:transparent;border:none}.vgs__next:focus,.vgs__prev:focus{outline:0}.vgs__prev{left:0}.vgs__next{right:0}.vgs__container{position:absolute;overflow:hidden;cursor:pointer;overflow:hidden;max-width:100vh;margin:.5rem auto 0;left:.5rem;right:.5rem;height:60vh;border-radius:12px;background-color:#000}@media (max-width:767px){.vgs__container{width:100%;max-width:100%;top:50%;margin-top:-140px;left:0;right:0;border-radius:0;height:280px}}.vgs__container__img{width:100%;height:100%;object-fit:contain}.vgs__gallery{overflow-x:hidden;overflow-y:hidden;position:absolute;bottom:10px;margin:auto;max-width:100vh;white-space:nowrap;left:.5rem;right:.5rem}@media (max-width:767px){.vgs__gallery{display:none}}.vgs__gallery__title{color:#fff;margin-bottom:.5rem}.vgs__gallery__container{overflow:visible;display:block;height:100px;white-space:nowrap;transition:all .2s ease-in-out;width:100%}.vgs__gallery__container__img{width:100px;height:100px;object-fit:cover;display:inline-block;float:none;margin-right:20px;cursor:pointer;opacity:.6;border-radius:8px}.vgs__gallery__container__img--active{width:100px;display:inline-block;float:none;opacity:1}.modal-enter{opacity:0}.modal-leave-active{opacity:0}", map: undefined, media: undefined }); 364 | 365 | }; 366 | /* scoped */ 367 | const __vue_scope_id__ = undefined; 368 | /* module identifier */ 369 | const __vue_module_identifier__ = undefined; 370 | /* functional template */ 371 | const __vue_is_functional_template__ = false; 372 | /* style inject SSR */ 373 | 374 | /* style inject shadow dom */ 375 | 376 | 377 | 378 | const __vue_component__ = /*#__PURE__*/normalizeComponent( 379 | { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }, 380 | __vue_inject_styles__, 381 | __vue_script__, 382 | __vue_scope_id__, 383 | __vue_is_functional_template__, 384 | __vue_module_identifier__, 385 | false, 386 | createInjector, 387 | undefined, 388 | undefined 389 | ); 390 | 391 | return __vue_component__; 392 | 393 | }))); 394 | --------------------------------------------------------------------------------