├── .prettierignore ├── demo ├── img │ ├── head.jpg │ ├── large │ │ ├── gm1.jpg │ │ ├── gm2.jpg │ │ ├── gm3.jpg │ │ ├── gm4.jpg │ │ ├── gm5.jpg │ │ ├── gm6.jpg │ │ ├── gm7.jpg │ │ ├── gm8.jpg │ │ ├── gm9.jpg │ │ └── gm122.jpg │ └── small │ │ ├── gm1.jpg │ │ ├── gm10.jpg │ │ ├── gm11.jpg │ │ ├── gm12.jpg │ │ ├── gm13.jpg │ │ ├── gm14.jpg │ │ ├── gm15.jpg │ │ ├── gm16.jpg │ │ ├── gm2.jpg │ │ ├── gm3.jpg │ │ ├── gm4.jpg │ │ ├── gm5.jpg │ │ ├── gm6.jpg │ │ ├── gm7.jpg │ │ ├── gm8.jpg │ │ └── gm9.jpg ├── css │ ├── fonts │ │ ├── Gilroy-Bold.woff │ │ ├── Gilroy-Bold.woff2 │ │ ├── Gilroy-Regular.woff │ │ ├── Gilroy-Regular.woff2 │ │ ├── Gilroy-SemiBold.woff │ │ ├── Gilroy-SemiBold.woff2 │ │ ├── Gilroy-RegularItalic.woff │ │ └── Gilroy-RegularItalic.woff2 │ └── style.css ├── pexels-video-1550080.mp4 └── js │ └── site.js ├── src ├── js │ ├── .jshintrc │ ├── slides │ │ ├── iframe.js │ │ ├── image.js │ │ ├── inline.js │ │ └── video.js │ ├── core │ │ ├── keyboard-navigation.js │ │ ├── zoom.js │ │ ├── slide-parser.js │ │ ├── drag.js │ │ ├── slide.js │ │ ├── touch-navigation.js │ │ └── touch-events.js │ └── utils │ │ └── helpers.js └── postcss │ └── glightbox.css ├── .lando.yml ├── .editorconfig ├── development ├── notifications.js ├── postcss.js ├── watcher.js ├── jscompiler.js └── package.js ├── .gitignore ├── .npmignore ├── .github ├── workflows │ └── greetings.yml ├── stale.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .prettierrc.json ├── bower.json ├── license.md ├── package.json ├── CONTRIBUTING.md ├── .eslintrc.json ├── CHANGELOG.md ├── dist └── css │ ├── glightbox.min.css │ └── glightbox.css └── index.html /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/*.js 2 | -------------------------------------------------------------------------------- /demo/img/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/head.jpg -------------------------------------------------------------------------------- /demo/img/large/gm1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm1.jpg -------------------------------------------------------------------------------- /demo/img/large/gm2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm2.jpg -------------------------------------------------------------------------------- /demo/img/large/gm3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm3.jpg -------------------------------------------------------------------------------- /demo/img/large/gm4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm4.jpg -------------------------------------------------------------------------------- /demo/img/large/gm5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm5.jpg -------------------------------------------------------------------------------- /demo/img/large/gm6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm6.jpg -------------------------------------------------------------------------------- /demo/img/large/gm7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm7.jpg -------------------------------------------------------------------------------- /demo/img/large/gm8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm8.jpg -------------------------------------------------------------------------------- /demo/img/large/gm9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm9.jpg -------------------------------------------------------------------------------- /demo/img/small/gm1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm1.jpg -------------------------------------------------------------------------------- /demo/img/small/gm10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm10.jpg -------------------------------------------------------------------------------- /demo/img/small/gm11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm11.jpg -------------------------------------------------------------------------------- /demo/img/small/gm12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm12.jpg -------------------------------------------------------------------------------- /demo/img/small/gm13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm13.jpg -------------------------------------------------------------------------------- /demo/img/small/gm14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm14.jpg -------------------------------------------------------------------------------- /demo/img/small/gm15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm15.jpg -------------------------------------------------------------------------------- /demo/img/small/gm16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm16.jpg -------------------------------------------------------------------------------- /demo/img/small/gm2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm2.jpg -------------------------------------------------------------------------------- /demo/img/small/gm3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm3.jpg -------------------------------------------------------------------------------- /demo/img/small/gm4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm4.jpg -------------------------------------------------------------------------------- /demo/img/small/gm5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm5.jpg -------------------------------------------------------------------------------- /demo/img/small/gm6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm6.jpg -------------------------------------------------------------------------------- /demo/img/small/gm7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm7.jpg -------------------------------------------------------------------------------- /demo/img/small/gm8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm8.jpg -------------------------------------------------------------------------------- /demo/img/small/gm9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/small/gm9.jpg -------------------------------------------------------------------------------- /demo/img/large/gm122.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/img/large/gm122.jpg -------------------------------------------------------------------------------- /demo/css/fonts/Gilroy-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/css/fonts/Gilroy-Bold.woff -------------------------------------------------------------------------------- /demo/pexels-video-1550080.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/pexels-video-1550080.mp4 -------------------------------------------------------------------------------- /src/js/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "esversion": 6, 4 | "maxparams": 4, 5 | "asi": false 6 | } -------------------------------------------------------------------------------- /demo/css/fonts/Gilroy-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/css/fonts/Gilroy-Bold.woff2 -------------------------------------------------------------------------------- /demo/css/fonts/Gilroy-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/css/fonts/Gilroy-Regular.woff -------------------------------------------------------------------------------- /demo/css/fonts/Gilroy-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/css/fonts/Gilroy-Regular.woff2 -------------------------------------------------------------------------------- /demo/css/fonts/Gilroy-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/css/fonts/Gilroy-SemiBold.woff -------------------------------------------------------------------------------- /demo/css/fonts/Gilroy-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/css/fonts/Gilroy-SemiBold.woff2 -------------------------------------------------------------------------------- /demo/css/fonts/Gilroy-RegularItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/css/fonts/Gilroy-RegularItalic.woff -------------------------------------------------------------------------------- /demo/css/fonts/Gilroy-RegularItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polydice/glightbox/master/demo/css/fonts/Gilroy-RegularItalic.woff2 -------------------------------------------------------------------------------- /.lando.yml: -------------------------------------------------------------------------------- 1 | name: glightbox 2 | recipe: lemp 3 | config: 4 | webroot: . 5 | services: 6 | node: 7 | type: node:14 8 | tooling: 9 | npm: 10 | service: node 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_size = 4 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.{json,yml}] 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /development/notifications.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const notifier = require('node-notifier'); 3 | 4 | function notify(title, body) { 5 | const icon = path.join(__dirname, 'icon.png'); 6 | 7 | notifier.notify({ 8 | title: title, 9 | message: body, 10 | icon: icon 11 | }); 12 | console.log(`${title}, ${body}`); 13 | } 14 | 15 | module.exports = notify; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .DS_Store? 3 | ._* 4 | .Spotlight-V100 5 | .Trashes 6 | ehthumbs.db 7 | Thumbs.db 8 | *.7z 9 | *.dmg 10 | *.gz 11 | *.iso 12 | *.jar 13 | *.rar 14 | *.tar 15 | *.zip 16 | /sitepsd 17 | /node_modules 18 | /release 19 | /release.zip 20 | /icons.zip 21 | /.vscode 22 | /.nova 23 | /dist/js/glightboxTest.min.js 24 | /dist/js/glightboxTest.js 25 | /src/js/glightboxTest.js 26 | sitepsd.psd 27 | package-lock.json 28 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo 2 | .DS_Store 3 | .DS_Store? 4 | ._* 5 | .Spotlight-V100 6 | .Trashes 7 | ehthumbs.db 8 | Thumbs.db 9 | *.7z 10 | *.dmg 11 | *.gz 12 | *.iso 13 | *.jar 14 | *.rar 15 | *.tar 16 | *.zip 17 | index.html 18 | /sitepsd 19 | /node_modules 20 | /release 21 | /release.zip 22 | /.vscode 23 | /.nova 24 | /dist/js/glightboxTest.min.js 25 | /dist/js/glightboxTest.js 26 | /src/js/glightboxTest.js 27 | sitepsd.psd 28 | package-lock.json 29 | icons.zip 30 | glightbox-master.zip 31 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: '' 13 | pr-message: 'Thank you for your contribution to this project, please make sure that you have read the CONTRIBUTING.md file and that your PR follows the guidelines mentioned there, if everything is ok we will review it as soon as posible' 14 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "tabWidth": 4, 4 | "printWidth": 220, 5 | "proseWrap": "never", 6 | "semi": true, 7 | "singleQuote": true, 8 | "jsxBracketSameLine": true, 9 | "arrowParens": "always", 10 | "htmlWhitespaceSensitivity": "ignore", 11 | "overrides": [ 12 | { 13 | "files": "*.json", 14 | "options": { 15 | "tabWidth": 2 16 | } 17 | }, 18 | { 19 | "files": "*.md", 20 | "options": { 21 | "tabWidth": 2 22 | } 23 | }, 24 | { 25 | "files": "*.yml", 26 | "options": { 27 | "tabWidth": 2 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/js/slides/iframe.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set slide iframe content 3 | * 4 | * @param {node} slide 5 | * @param {object} data 6 | * @param {int} index 7 | * @param {function} callback 8 | */ 9 | 10 | import { createIframe } from '../utils/helpers.js'; 11 | 12 | export default function slideIframe(slide, data, index, callback) { 13 | const slideMedia = slide.querySelector('.gslide-media'); 14 | const iframe = createIframe({ 15 | url: data.href, 16 | callback: callback 17 | }); 18 | 19 | slideMedia.parentNode.style.maxWidth = data.width; 20 | slideMedia.parentNode.style.height = data.height; 21 | slideMedia.appendChild(iframe); 22 | 23 | return; 24 | } 25 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glightbox", 3 | "description": "JavaScript animation engine", 4 | "main": "dist/js/glightbox.min.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/mcstudios/glightbox" 8 | }, 9 | "keywords": [ 10 | "lightbox", 11 | "javascript", 12 | "gallery", 13 | "popup" 14 | ], 15 | "authors": [ 16 | "MC Studios " 17 | ], 18 | "license": "MIT", 19 | "homepage": "https://glightbox.mcstudios.com.mx", 20 | "ignore": [ 21 | "**/.*", 22 | "node_modules", 23 | "bower_components", 24 | "*.psd", 25 | "demo", 26 | "test", 27 | "tests" 28 | ] 29 | } -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 180 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - enhancement 10 | - bug 11 | - wontfix 12 | - feature-request 13 | - feature request 14 | # Label to use when marking an issue as stale 15 | staleLabel: wontfix 16 | # Comment to post when marking an issue as stale. Set to `false` to disable 17 | markComment: > 18 | This issue has been automatically marked as stale because it has not had 19 | recent activity. It will be closed if no further activity occurs. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: false 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Important.** 11 | Before posting a feature request please make sure to search the closed issues, maybe the feature you want to implement has already been asked and denied for not providing a valid explanation or simply because it was something the user wanted for him or his project. 12 | 13 | **Please describe the feature you want to be implemented.** 14 | Please note: features should be something that everyone can use and not just something you need for your project. 15 | 16 | **Explain why the feature is useful** 17 | Describe some usage cases. 18 | 19 | **Have you seen it somewhere else?** 20 | If so please provide live examples, images, videos, etc. anything that help us understand the feature request 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Are you able to reproduce the bug in the demo site** 14 | Yes|No. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Post the code you are using** 27 | Help me solve issues faster, if I can copy paste your code I can try to reproduce and fix the bug faster. 28 | 29 | **Screenshots** 30 | If applicable, add screenshots to help explain your problem. 31 | 32 | **Desktop:** 33 | - OS: [e.g. iOS] 34 | - Browser [e.g. chrome, safari] 35 | - Version [e.g. 22] 36 | 37 | **Smartphone:** 38 | - Device: [e.g. iPhone6] 39 | - OS: [e.g. iOS8.1] 40 | - Browser [e.g. stock browser, safari] 41 | - Version [e.g. 22] 42 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Biati Digital https://www.biati.digital 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glightbox", 3 | "version": "3.0.9", 4 | "description": "Pure Javascript lightbox", 5 | "main": "dist/js/glightbox.min.js", 6 | "scripts": { 7 | "watch": "node development/watcher.js" 8 | }, 9 | "author": "Biati Digital", 10 | "keywords": [ 11 | "lightbox", 12 | "javascript", 13 | "gallery", 14 | "popup" 15 | ], 16 | "license": "MIT", 17 | "homepage": "https://biati-digital.github.io/glightbox/", 18 | "repository": { 19 | "url": "https://github.com/biati-digital/glightbox", 20 | "type": "git" 21 | }, 22 | "bugs": "https://github.com/biati-digital/glightbox/issues", 23 | "devDependencies": { 24 | "@babel/cli": "^7.10.3", 25 | "@babel/core": "^7.10.3", 26 | "@babel/node": "^7.10.3", 27 | "@babel/preset-env": "^7.10.3", 28 | "@babel/register": "^7.10.3", 29 | "archiver": "^3.1.1", 30 | "babel-plugin-transform-runtime": "^6.23.0", 31 | "chokidar": "^3.4.0", 32 | "clean-css": "^4.2.3", 33 | "css-mqpacker": "^7.0.0", 34 | "eslint": "^7.16.0", 35 | "fs-extra": "^8.1.0", 36 | "fs-jetpack": "^2.4.0", 37 | "install": "^0.13.0", 38 | "node-notifier": "^5.4.0", 39 | "npm": "^6.14.10", 40 | "postcss": "^7.0.32", 41 | "postcss-nested": "^4.1.1", 42 | "postcss-preset-env": "^6.5.0", 43 | "postcss-prettify": "^0.3.4", 44 | "rollup": "^1.32.1", 45 | "rollup-plugin-babel": "^4.4.0", 46 | "rollup-plugin-commonjs": "^9.2.0", 47 | "rollup-plugin-node-resolve": "^4.0.0", 48 | "temporary": "^1.1.0", 49 | "terser": "^4.8.0", 50 | "uninstall": "0.0.0" 51 | }, 52 | "dependencies": {} 53 | } 54 | -------------------------------------------------------------------------------- /src/js/slides/image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set slide inline content 3 | * we'll extend this to make http 4 | * requests using the fetch api 5 | * but for now we keep it simple 6 | * 7 | * @param {node} slide 8 | * @param {object} data 9 | * @param {int} index 10 | * @param {function} callback 11 | */ 12 | 13 | import { isFunction } from '../utils/helpers.js'; 14 | 15 | export default function slideImage(slide, data, index, callback) { 16 | const slideMedia = slide.querySelector('.gslide-media'); 17 | 18 | let img = new Image(); 19 | let titleID = 'gSlideTitle_' + index; 20 | let textID = 'gSlideDesc_' + index; 21 | 22 | // prettier-ignore 23 | img.addEventListener('load', () => { 24 | if (isFunction(callback)) { 25 | callback(); 26 | } 27 | }, false); 28 | 29 | img.src = data.href; 30 | img.alt = ''; // https://davidwalsh.name/accessibility-tip-empty-alt-attributes 31 | 32 | if (data.title !== '') { 33 | img.setAttribute('aria-labelledby', titleID); 34 | } 35 | if (data.description !== '') { 36 | // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-describedby_attribute#Example_2_A_Close_Button 37 | img.setAttribute('aria-describedby', textID); 38 | } 39 | 40 | if (data.hasOwnProperty('_hasCustomWidth') && data._hasCustomWidth) { 41 | img.style.width = data.width; 42 | } 43 | if (data.hasOwnProperty('_hasCustomHeight') && data._hasCustomHeight) { 44 | img.style.height = data.height; 45 | } 46 | 47 | slideMedia.insertBefore(img, slideMedia.firstChild); 48 | return; 49 | } 50 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to GLightbox.js 2 | 3 | Please read these guidelines before contributing code. 4 | 5 | ## :nut_and_bolt: Setting up the development environment 6 | 7 | - Fork and clone the repository 8 | - `npm install` 9 | - `npm run watch` 10 | - To update js and css files, use the `src` folder: src/js/glightbox.js and src/postcss/glightbox.css These files will be compiled automatically with every change to the `dist` folder (do not modify the dist files directly) 11 | 12 | ## :bug: Fixing a Bug 13 | 14 | When fixing a bug please make sure to test it in several browsers including ie11. If you are not able to do so, mention that in a PR comment, so other contributors can do it. once your code is working please run the following command "eslint src/js" to verify that your code is following the same coding standards 15 | 16 | ## :tada: Proposing a Change 17 | 18 | When implementing a feature please create an issue first explaining your idea and asking whether there's need for such a feature. Remember the script's core philosophy is to stay simple and minimal, doing one thing and doing it right. 19 | 20 | ## :pencil: Before you open a Pull Request 21 | 22 | - Follow the same conding style. 23 | - Run eslint to verify your code, run the following command "eslint src/js" and fix any error you find in your code 24 | - **DO NOT** commit changes in the dist directory, this files are created automatically. 25 | - Follow Git best practices (especially use meaningful commit messages). 26 | - Describe thoroughly you work in a PR comment. 27 | - Be patient and understanding. It's a side project, done in free time. 28 | 29 | Thank you to everyone who has contributed to GLightbox.js! 30 | -------------------------------------------------------------------------------- /development/postcss.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | const cssnext = require('postcss-preset-env'); 3 | const cssnested = require('postcss-nested'); 4 | const cssmqpacker = require('css-mqpacker'); 5 | const cssprettify = require('postcss-prettify'); 6 | const cssclean = require('clean-css'); 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | 10 | function postcssCompiler(config) { 11 | const { file, dest, minify = true } = config; 12 | const fileName = path.basename(file); 13 | const from = path.join(__dirname, '../', file); 14 | const to = path.join(__dirname, '../', dest, fileName); 15 | const fileNameMin = path.extname(fileName); 16 | const min = path.join(__dirname, '../', dest, fileName.replace(fileNameMin, `.min${fileNameMin}`)); 17 | const css = fs.readFileSync(from, 'utf8'); 18 | 19 | return new Promise((resolve, reject) => { 20 | return postcss([ 21 | cssnested(), 22 | cssnext({ 23 | stage: 0, 24 | browsers: ['last 2 version'], 25 | features: { 26 | calc: false 27 | } 28 | }), 29 | cssmqpacker({ 30 | sort: true 31 | }), 32 | cssprettify() 33 | ]) 34 | .process(css, { 35 | from, 36 | to 37 | }) 38 | .then((result) => { 39 | if (result && result.css) { 40 | fs.writeFile(to, result.css, 'utf8', (err) => reject(err)); 41 | 42 | if (minify) { 43 | const minified = new cssclean({}).minify(result.css); 44 | fs.writeFile(min, minified.styles, 'utf8', (err) => reject(err)); 45 | 46 | if (result.map) { 47 | fs.writeFile(to + '.map', result.map, 'utf8', (err) => reject(err)); 48 | } 49 | } 50 | 51 | resolve(to); 52 | } else { 53 | reject(result); 54 | } 55 | }) 56 | .catch((err) => { 57 | console.log(err); 58 | reject(err); 59 | }); 60 | }); 61 | } 62 | 63 | module.exports = postcssCompiler; 64 | -------------------------------------------------------------------------------- /src/js/slides/inline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set slide inline content 3 | * we'll extend this to make http 4 | * requests using the fetch api 5 | * but for now we keep it simple 6 | * 7 | * @param {node} slide 8 | * @param {object} data 9 | * @param {int} index 10 | * @param {function} callback 11 | */ 12 | 13 | import { has, addClass, addEvent, createHTML, isString, isNode, isFunction } from '../utils/helpers.js'; 14 | 15 | export default function slideInline(slide, data, index, callback) { 16 | const slideMedia = slide.querySelector('.gslide-media'); 17 | const hash = has(data, 'href') && data.href ? data.href.split('#').pop().trim() : false; 18 | const content = has(data, 'content') && data.content ? data.content : false; 19 | let innerContent; 20 | 21 | if (content) { 22 | if (isString(content)) { 23 | innerContent = createHTML(`
${content}
`); 24 | } 25 | if (isNode(content)) { 26 | if (content.style.display == 'none') { 27 | content.style.display = 'block'; 28 | } 29 | 30 | const container = document.createElement('div'); 31 | container.className = 'ginlined-content'; 32 | container.appendChild(content); 33 | innerContent = container; 34 | } 35 | } 36 | 37 | if (hash) { 38 | let div = document.getElementById(hash); 39 | if (!div) { 40 | return false; 41 | } 42 | const cloned = div.cloneNode(true); 43 | 44 | cloned.style.height = data.height; 45 | cloned.style.maxWidth = data.width; 46 | addClass(cloned, 'ginlined-content'); 47 | innerContent = cloned; 48 | } 49 | 50 | if (!innerContent) { 51 | console.error('Unable to append inline slide content', data); 52 | return false; 53 | } 54 | 55 | slideMedia.style.height = data.height; 56 | slideMedia.style.width = data.width; 57 | slideMedia.appendChild(innerContent); 58 | 59 | this.events['inlineclose' + hash] = addEvent('click', { 60 | onElement: slideMedia.querySelectorAll('.gtrigger-close'), 61 | withCallback: (e) => { 62 | e.preventDefault(); 63 | this.close(); 64 | } 65 | }); 66 | 67 | if (isFunction(callback)) { 68 | callback(); 69 | } 70 | return; 71 | } 72 | -------------------------------------------------------------------------------- /development/watcher.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const chokidar = require('chokidar'); 3 | const path = require('path'); 4 | const notify = require('./notifications'); 5 | const jscompiler = require('./jscompiler'); 6 | const postcssCompiler = require('./postcss'); 7 | const terser = require('terser'); 8 | 9 | let config = { 10 | js: { 11 | src: 'src/js', 12 | dest: 'dist/js', 13 | }, 14 | css: { 15 | src: 'src/postcss', 16 | dest: 'dist/css', 17 | } 18 | }; 19 | 20 | /** 21 | * Handle Javascript files 22 | * compile the javascript files 23 | * to es2015, minify and sync the files 24 | * 25 | * @param {string} file path 26 | */ 27 | async function handleJavascript(file) { 28 | file = path.join(config.js.src, 'glightbox.js'); 29 | 30 | const name = path.basename(file); 31 | 32 | const res = await jscompiler({ 33 | file, 34 | dest: config.js.dest, 35 | format: 'umd', 36 | sourcemap: false, 37 | moduleID: 'GLightbox' 38 | }).catch(error => console.log(error)); 39 | 40 | if (!res) { 41 | notify('Build Error', `View logs for more info`); 42 | console.log(res) 43 | return false; 44 | } 45 | 46 | const minName = name.replace('.js', '.min.js'); 47 | const processed = path.join(config.js.dest, name); 48 | const code = fs.readFileSync(processed, 'utf8'); 49 | const minified = terser.minify(code); 50 | const minifyPath = path.join(config.js.dest, minName); 51 | fs.writeFileSync(minifyPath, minified.code); 52 | 53 | notify('Javascript Build', `Compiled and Minified ${name}`); 54 | } 55 | 56 | 57 | /** 58 | * Handle Postcss files 59 | * compile the css files 60 | * 61 | * @param {string} file path 62 | */ 63 | async function handlePostCSS(file) { 64 | const name = path.basename(file); 65 | const dest = config.css.dest; 66 | 67 | let res = await postcssCompiler({ 68 | file, 69 | dest, 70 | minify: true 71 | }).catch(error => console.log(error)); 72 | if (!res) { 73 | return false; 74 | } 75 | notify('PostCSS Build', `Compiled and Minified ${name}`); 76 | } 77 | 78 | 79 | 80 | /** 81 | * Watcher 82 | * what the files for the backedn 83 | * this includes js and css files 84 | */ 85 | function filesWatcher() { 86 | const watcher = chokidar.watch(['src'], { 87 | ignored: ['.DS_Store', 'src/js/.jshintrc', 'src/js/.babelrc'], 88 | persistent: true, 89 | depth: 3, 90 | awaitWriteFinish: { 91 | stabilityThreshold: 500, 92 | pollInterval: 500 93 | }, 94 | }); 95 | 96 | watcher.on('change', path => { 97 | if (path.endsWith('.js')) { 98 | return handleJavascript(path); 99 | } 100 | if (path.endsWith('.css')) { 101 | return handlePostCSS(path); 102 | } 103 | }) 104 | watcher.on('ready', () => notify('Watching files', 'Initial scan complete. Ready for changes')) 105 | } 106 | filesWatcher(); 107 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 12, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true 7 | } 8 | }, 9 | "rules": { 10 | "semi": ["warn", "always"], 11 | "no-extra-semi": "error", 12 | "brace-style": "error", 13 | "curly": "error", 14 | "eqeqeq": "off", 15 | "block-spacing": "error", 16 | "camelcase": "error", 17 | "new-cap": "error", 18 | "no-const-assign": "error", 19 | "no-dupe-args": "error", 20 | "no-dupe-class-members": "error", 21 | "no-dupe-keys": "error", 22 | "no-duplicate-imports": "error", 23 | "no-empty-character-class": "error", 24 | "no-empty": "off", 25 | "no-eval": "error", 26 | "no-ex-assign": "error", 27 | "no-extend-native": "error", 28 | "no-extra-boolean-cast": "error", 29 | "no-extra-parens": ["warn", "functions"], 30 | "no-fallthrough": "error", 31 | "no-floating-decimal": "error", 32 | "no-implied-eval": "error", 33 | "no-inner-declarations": "error", 34 | "no-lone-blocks": "error", 35 | "no-mixed-spaces-and-tabs": "error", 36 | "no-multi-spaces": "error", 37 | "no-new-func": "error", 38 | "no-new-object": "error", 39 | "no-new-require": "error", 40 | "no-new-symbol": "error", 41 | "no-new-wrappers": "error", 42 | "no-redeclare": "error", 43 | "no-self-assign": "error", 44 | "no-self-compare": "error", 45 | "no-sequences": "error", 46 | "no-shadow-restricted-names": "error", 47 | "no-sparse-arrays": "error", 48 | "no-template-curly-in-string": "error", 49 | "no-this-before-super": "error", 50 | "no-throw-literal": "error", 51 | "no-undef-init": "error", 52 | "no-unsafe-finally": "error", 53 | "no-useless-constructor": "error", 54 | "no-useless-rename": "error", 55 | "no-unexpected-multiline": "error", 56 | "no-whitespace-before-property": "error", 57 | "space-before-blocks": "warn", 58 | "new-parens": "error", 59 | "space-in-parens": "error", 60 | "space-unary-ops": "error", 61 | "keyword-spacing": "warn", 62 | "template-curly-spacing": "warn", 63 | "use-isnan": "warn", 64 | "constructor-super": "error", 65 | "no-array-constructor": "error", 66 | "quotes": ["error", "single"], 67 | "comma-style": ["error", "last"], 68 | "comma-dangle": [ 69 | "warn", 70 | { 71 | "arrays": "never", 72 | "objects": "never", 73 | "imports": "never", 74 | "exports": "never", 75 | "functions": "never" 76 | } 77 | ], 78 | "space-before-function-paren": [ 79 | "off", 80 | { 81 | "anonymous": "always", 82 | "named": "always", 83 | "asyncArrow": "always" 84 | } 85 | ], 86 | "func-call-spacing": ["error", "never"], 87 | "key-spacing": ["error", { "beforeColon": false }], 88 | "one-var": ["off"], 89 | "operator-linebreak": "off", 90 | "space-infix-ops": ["error"], 91 | "comma-spacing": ["error", { "before": false, "after": true }], 92 | "no-unused-vars": ["warn", { "vars": "local", "args": "none", "ignoreRestSiblings": false }] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /development/jscompiler.js: -------------------------------------------------------------------------------- 1 | const { rollup } = require('rollup'); 2 | const babel = require('rollup-plugin-babel'); 3 | const resolve = require('rollup-plugin-node-resolve'); 4 | const commonjs = require('rollup-plugin-commonjs'); 5 | const path = require('path'); 6 | 7 | global.rollupCache = global.rollupCache || {}; 8 | 9 | 10 | function camelCase(str) { 11 | return str.replace(/(?:^\w|[A-Z]|\b\w|[-_])/g, (letter, index) => { 12 | return index == 0 ? letter.toLowerCase() : letter.toUpperCase(); 13 | }).replace(/\s+/g, '').replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, ''); 14 | } 15 | 16 | function jscompiler(config) { 17 | const { 18 | file, 19 | dest 20 | } = config; 21 | 22 | const fileName = path.basename(file); 23 | const extension = path.extname(fileName); 24 | const singleFileName = fileName.replace(extension, ''); 25 | const cache = global.rollupCache[fileName] ? global.rollupCache[fileName] : null; 26 | const format = (config.hasOwnProperty('format') ? config.format : 'iife'); 27 | const strict = (config.hasOwnProperty('strict') ? config.strict : true); 28 | const sourcemap = (config.hasOwnProperty('sourcemap') ? config.sourcemap : false); 29 | const moduleID = (config.hasOwnProperty('moduleID') ? config.moduleID : false); 30 | 31 | let name = (config.hasOwnProperty('name') ? config.name : camelCase(singleFileName)); 32 | let outPutFile = path.join(__dirname, '../', dest, fileName); 33 | let customFileName = (config.hasOwnProperty('fileName') ? config.fileName : false); 34 | 35 | if (customFileName) { 36 | customFileName = customFileName.replace('{name}', singleFileName); 37 | outPutFile = outPutFile.replace(fileName, customFileName); 38 | } 39 | 40 | return new Promise((res, rej) => { 41 | rollup({ 42 | input: file, 43 | cache: cache, 44 | plugins: [ 45 | resolve({ 46 | mainFields: ['module', 'main'], 47 | browser: true, 48 | }), 49 | commonjs(), 50 | babel({ 51 | comments: false, 52 | exclude: 'node_modules/**', 53 | presets: [ 54 | ['@babel/preset-env', { 55 | modules: false 56 | }] 57 | ] 58 | }), 59 | ] 60 | }).then((bundle) => { 61 | global.rollupCache[fileName] = bundle.cache; 62 | bundle.write({ 63 | file: outPutFile, 64 | format: format, // amd, cjs, esm, iife, umd 65 | strict: strict, 66 | sourcemap: sourcemap, 67 | name: (moduleID ? moduleID : name) 68 | }).then(() => { 69 | res(true); 70 | }).catch(error => { 71 | console.error(error) 72 | rej(error); 73 | }); 74 | 75 | return outPutFile; 76 | 77 | }).catch(error => { 78 | console.log(error) 79 | throw new Error(error); 80 | }) 81 | }) 82 | } 83 | 84 | module.exports = jscompiler; 85 | -------------------------------------------------------------------------------- /src/js/core/keyboard-navigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Keyboard Navigation 3 | * Allow navigation using the keyboard 4 | * 5 | * @param {object} instance 6 | */ 7 | 8 | import { addEvent, addClass, removeClass, each } from '../utils/helpers.js'; 9 | 10 | function getNextFocusElement(current = -1) { 11 | const btns = document.querySelectorAll('.gbtn[data-taborder]:not(.disabled)'); 12 | if (!btns.length) { 13 | return false; 14 | } 15 | 16 | if (btns.length == 1) { 17 | return btns[0]; 18 | } 19 | 20 | if (typeof current == 'string') { 21 | current = parseInt(current); 22 | } 23 | 24 | let newIndex = current < 0 ? 1 : current + 1; 25 | if (newIndex > btns.length) { 26 | newIndex = '1'; 27 | } 28 | 29 | const orders = []; 30 | each(btns, (btn) => { 31 | orders.push(btn.getAttribute('data-taborder')); 32 | }); 33 | const nextOrders = orders.filter((el) => el >= parseInt(newIndex)); 34 | const nextFocus = nextOrders.sort()[0]; 35 | 36 | return document.querySelector(`.gbtn[data-taborder="${nextFocus}"]`); 37 | } 38 | 39 | export default function keyboardNavigation(instance) { 40 | if (instance.events.hasOwnProperty('keyboard')) { 41 | return false; 42 | } 43 | 44 | instance.events['keyboard'] = addEvent('keydown', { 45 | onElement: window, 46 | withCallback: (event, target) => { 47 | event = event || window.event; 48 | const key = event.keyCode; 49 | if (key == 9) { 50 | //prettier-ignore 51 | const focusedButton = document.querySelector('.gbtn.focused'); 52 | 53 | if (!focusedButton) { 54 | const activeElement = document.activeElement && document.activeElement.nodeName ? document.activeElement.nodeName.toLocaleLowerCase() : false; 55 | if (activeElement == 'input' || activeElement == 'textarea' || activeElement == 'button') { 56 | return; 57 | } 58 | } 59 | 60 | event.preventDefault(); 61 | const btns = document.querySelectorAll('.gbtn[data-taborder]'); 62 | if (!btns || btns.length <= 0) { 63 | return; 64 | } 65 | 66 | if (!focusedButton) { 67 | const first = getNextFocusElement(); 68 | if (first) { 69 | first.focus(); 70 | addClass(first, 'focused'); 71 | } 72 | return; 73 | } 74 | 75 | let currentFocusOrder = focusedButton.getAttribute('data-taborder'); 76 | let nextFocus = getNextFocusElement(currentFocusOrder); 77 | 78 | removeClass(focusedButton, 'focused'); 79 | 80 | if (nextFocus) { 81 | nextFocus.focus(); 82 | addClass(nextFocus, 'focused'); 83 | } 84 | } 85 | if (key == 39) { 86 | instance.nextSlide(); 87 | } 88 | if (key == 37) { 89 | instance.prevSlide(); 90 | } 91 | if (key == 27) { 92 | instance.close(); 93 | } 94 | } 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /demo/js/site.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var _ = v; 3 | 4 | var scrollerDesc = mctracker(); 5 | scrollerDesc.setup({ 6 | element: _('.box-container').toArray(), 7 | offsetBottom: '20%', 8 | once: true, 9 | }).onStepEnter(function(response) { 10 | var list = _(response.element).find('li'); 11 | list.forEach(function(item, i) { 12 | var delay = i * 150 / 1000; 13 | item = _(item); 14 | item.attr('style', 'transition-delay: ' + delay + 's;'); 15 | }) 16 | 17 | list.addClass('show') 18 | }); 19 | 20 | 21 | var header = function() { 22 | var lastKnownScrollY = 0; 23 | var currentScrollY = 0; 24 | var eleHeader = null; 25 | const classes = { 26 | pinned: 'header-pin', 27 | unpinned: 'header-unpin', 28 | }; 29 | 30 | function onScroll() { 31 | currentScrollY = window.pageYOffset; 32 | 33 | if (currentScrollY <= 0) { 34 | restore(); 35 | return; 36 | } 37 | if (currentScrollY < lastKnownScrollY) { 38 | pin(); 39 | } else if (currentScrollY > lastKnownScrollY) { 40 | unpin(); 41 | } 42 | lastKnownScrollY = currentScrollY; 43 | } 44 | 45 | function pin() { 46 | eleHeader.removeClass(classes.unpinned); 47 | eleHeader.addClass(classes.pinned); 48 | } 49 | function unpin() { 50 | eleHeader.removeClass(classes.pinned); 51 | eleHeader.addClass(classes.unpinned); 52 | } 53 | function restore() { 54 | eleHeader.removeClass(classes.pinned); 55 | eleHeader.removeClass(classes.unpinned); 56 | } 57 | eleHeader = _('.main-header'); 58 | headerHeaight = eleHeader.height(); 59 | onScroll(); 60 | window.onload = function() { 61 | document.addEventListener('scroll', onScroll, false); 62 | } 63 | } 64 | header(); 65 | 66 | 67 | 68 | 69 | var specifics = function(params) { 70 | var scrollerDesc = mctracker(); 71 | scrollerDesc.setup({ 72 | element: _('.especifications ul').toArray(), 73 | bottom: '300', 74 | once: true, 75 | }).onStepEnter(function(response) { 76 | var list = _(response.element).find('li'); 77 | list.forEach(function(item, i) { 78 | var delay = i * 100 / 1000; 79 | item = _(item); 80 | item.attr('style', 'transition-delay: ' + delay + 's;'); 81 | }) 82 | list.addClass('show') 83 | }); 84 | } 85 | specifics(); 86 | 87 | 88 | const letters = _('svg').children('g'); 89 | function animateLetter(index = 0) { 90 | if (index > letters.length - 1) { 91 | return false; 92 | } 93 | 94 | let nextIndex = index + 1; 95 | let paths = _(letters[index]).find('path'); 96 | let duration = (index > 0 ? 390 : 1000); 97 | 98 | anime({ 99 | targets: paths.toArray(), 100 | strokeDashoffset: [anime.setDashoffset, 0], 101 | easing: 'easeInOutSine', 102 | duration: duration, 103 | begin: () => { 104 | paths.addClass('hw'); 105 | }, 106 | complete: () => { 107 | animateLetter(nextIndex); 108 | } 109 | }); 110 | } 111 | animateLetter(0); 112 | 113 | 114 | 115 | }()); 116 | -------------------------------------------------------------------------------- /development/package.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const os = require('os'); 3 | const jetpack = require('fs-jetpack'); 4 | const path = require('path'); 5 | const archiver = require('archiver'); 6 | const args = process.argv.slice(2); 7 | const notify = require('./notifications'); 8 | const folder = path.join(__dirname, '/..'); 9 | 10 | /** 11 | * Realease new version 12 | * calling 13 | * node development/package.js versionhere 14 | * then npm publish 15 | */ 16 | 17 | async function createFolder() { 18 | jetpack.remove(path.join(folder, 'glightbox-master.zip')); 19 | 20 | const tmpfolder = path.join(os.tmpdir(), 'glightbox-master'); 21 | const newVersion = args[0]; 22 | 23 | await updateFileVersion({ 24 | file: path.join(folder, 'index.html'), 25 | search: /download\/(.*)\/glightbox/g, 26 | replace: newVersion 27 | }); 28 | 29 | await updateFileVersion({ 30 | file: path.join(folder, 'package.json'), 31 | search: /"version":\s?"(.*)",/g, 32 | replace: newVersion 33 | }); 34 | 35 | await updateFileVersion({ 36 | file: path.join(folder, 'README.md'), 37 | search: /v([0-9-.]+)/g, 38 | replace: newVersion 39 | }); 40 | 41 | await updateFileVersion({ 42 | file: path.join(folder, 'src/js/glightbox.js'), 43 | search: /version\s?=\s?'(.*)';/g, 44 | replace: newVersion 45 | }); 46 | 47 | jetpack.copy(folder, tmpfolder, { 48 | matching: [ 49 | '!node_modules', 50 | '!node_modules/**/*', 51 | '!.git', 52 | '!.git/**/*', 53 | '!.github', 54 | '!.github/**/*', 55 | '!.vscode', 56 | '!icons.zip', 57 | '!.vscode/**/*', 58 | '!*.psd', 59 | '!.DS_Store' 60 | ] 61 | }); 62 | notify('Created folder', `Created folder correctly`); 63 | 64 | const zip = await createZip(tmpfolder).catch(error => { 65 | jetpack.remove(tmpfolder); 66 | }); 67 | 68 | const folderName = path.basename(folder); 69 | jetpack.remove(tmpfolder); 70 | jetpack.move(zip, path.join(folder, folderName +'-master.zip')); 71 | 72 | notify('Done', `Packaging process ended correctly`); 73 | } 74 | createFolder(); 75 | 76 | 77 | 78 | async function createZip(folder) { 79 | return new Promise((resolve, reject) => { 80 | const name = folder + '.zip'; 81 | const output = fs.createWriteStream(name); 82 | const archive = archiver('zip', { zlib: { level: 9 } }); 83 | 84 | output.on('close', () => { 85 | notify('Zipped', `zip archive was created correctly`); 86 | resolve(name); 87 | }); 88 | archive.on('error', (err) => { 89 | notify('Package Error', `The was an error creating the zip.`) 90 | reject(err); 91 | }); 92 | 93 | archive.pipe(output); 94 | archive.directory(folder, false); 95 | archive.finalize(); 96 | }) 97 | } 98 | 99 | 100 | async function updateFileVersion(data) { 101 | return new Promise((resolve, reject) => { 102 | jetpack.readAsync(data.file).then((str) => { 103 | let regexp = new RegExp(data.search); 104 | 105 | while ((matches = regexp.exec(str)) !== null) { 106 | let foundLine = matches[0]; 107 | let newLine = foundLine.replace(matches[1], data.replace) 108 | str = str.replace(foundLine, newLine); 109 | } 110 | 111 | jetpack.writeAsync(data.file, str).then(() => { 112 | resolve(data.file); 113 | }); 114 | }); 115 | }) 116 | } 117 | -------------------------------------------------------------------------------- /src/js/core/zoom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ZoomImages 3 | * Allow imaes to zoom and drag 4 | * for desktops 5 | * 6 | * @param {node} img node 7 | * @param {node} slide container 8 | * @param {function} function to trigger on close 9 | */ 10 | export default class ZoomImages { 11 | constructor(el, slide, onclose = null) { 12 | this.img = el; 13 | this.slide = slide; 14 | this.onclose = onclose; 15 | 16 | if (this.img.setZoomEvents) { 17 | return false; 18 | } 19 | 20 | this.active = false; 21 | this.zoomedIn = false; 22 | this.dragging = false; 23 | this.currentX = null; 24 | this.currentY = null; 25 | this.initialX = null; 26 | this.initialY = null; 27 | this.xOffset = 0; 28 | this.yOffset = 0; 29 | 30 | this.img.addEventListener('mousedown', (e) => this.dragStart(e), false); 31 | this.img.addEventListener('mouseup', (e) => this.dragEnd(e), false); 32 | this.img.addEventListener('mousemove', (e) => this.drag(e), false); 33 | 34 | this.img.addEventListener( 35 | 'click', 36 | (e) => { 37 | if (this.slide.classList.contains('dragging-nav')) { 38 | this.zoomOut(); 39 | return false; 40 | } 41 | 42 | if (!this.zoomedIn) { 43 | return this.zoomIn(); 44 | } 45 | if (this.zoomedIn && !this.dragging) { 46 | this.zoomOut(); 47 | } 48 | }, 49 | false 50 | ); 51 | 52 | this.img.setZoomEvents = true; 53 | } 54 | zoomIn() { 55 | let winWidth = this.widowWidth(); 56 | 57 | if (this.zoomedIn || winWidth <= 768) { 58 | return; 59 | } 60 | 61 | const img = this.img; 62 | img.setAttribute('data-style', img.getAttribute('style')); 63 | img.style.maxWidth = img.naturalWidth + 'px'; 64 | img.style.maxHeight = img.naturalHeight + 'px'; 65 | 66 | if (img.naturalWidth > winWidth) { 67 | let centerX = winWidth / 2 - img.naturalWidth / 2; 68 | this.setTranslate(this.img.parentNode, centerX, 0); 69 | } 70 | this.slide.classList.add('zoomed'); 71 | this.zoomedIn = true; 72 | } 73 | zoomOut() { 74 | this.img.parentNode.setAttribute('style', ''); 75 | this.img.setAttribute('style', this.img.getAttribute('data-style')); 76 | this.slide.classList.remove('zoomed'); 77 | this.zoomedIn = false; 78 | this.currentX = null; 79 | this.currentY = null; 80 | this.initialX = null; 81 | this.initialY = null; 82 | this.xOffset = 0; 83 | this.yOffset = 0; 84 | 85 | if (this.onclose && typeof this.onclose == 'function') { 86 | this.onclose(); 87 | } 88 | } 89 | dragStart(e) { 90 | e.preventDefault(); 91 | if (!this.zoomedIn) { 92 | this.active = false; 93 | return; 94 | } 95 | if (e.type === 'touchstart') { 96 | this.initialX = e.touches[0].clientX - this.xOffset; 97 | this.initialY = e.touches[0].clientY - this.yOffset; 98 | } else { 99 | this.initialX = e.clientX - this.xOffset; 100 | this.initialY = e.clientY - this.yOffset; 101 | } 102 | 103 | if (e.target === this.img) { 104 | this.active = true; 105 | this.img.classList.add('dragging'); 106 | } 107 | } 108 | dragEnd(e) { 109 | e.preventDefault(); 110 | this.initialX = this.currentX; 111 | this.initialY = this.currentY; 112 | this.active = false; 113 | 114 | setTimeout(() => { 115 | this.dragging = false; 116 | this.img.isDragging = false; 117 | this.img.classList.remove('dragging'); 118 | }, 100); 119 | } 120 | drag(e) { 121 | if (this.active) { 122 | e.preventDefault(); 123 | 124 | if (e.type === 'touchmove') { 125 | this.currentX = e.touches[0].clientX - this.initialX; 126 | this.currentY = e.touches[0].clientY - this.initialY; 127 | } else { 128 | this.currentX = e.clientX - this.initialX; 129 | this.currentY = e.clientY - this.initialY; 130 | } 131 | 132 | this.xOffset = this.currentX; 133 | this.yOffset = this.currentY; 134 | 135 | this.img.isDragging = true; 136 | this.dragging = true; 137 | 138 | this.setTranslate(this.img, this.currentX, this.currentY); 139 | } 140 | } 141 | onMove(e) { 142 | if (!this.zoomedIn) { 143 | return; 144 | } 145 | let xOffset = e.clientX - this.img.naturalWidth / 2; 146 | let yOffset = e.clientY - this.img.naturalHeight / 2; 147 | 148 | this.setTranslate(this.img, xOffset, yOffset); 149 | } 150 | setTranslate(node, xPos, yPos) { 151 | node.style.transform = 'translate3d(' + xPos + 'px, ' + yPos + 'px, 0)'; 152 | } 153 | widowWidth() { 154 | return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/js/slides/video.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set slide video 3 | * 4 | * @param {node} slide 5 | * @param {object} data 6 | * @param {int} index 7 | * @param {function} callback 8 | */ 9 | import { has, closest, injectAssets, addClass, removeClass, createHTML, isFunction, waitUntil } from '../utils/helpers.js'; 10 | 11 | export default function slideVideo(slide, data, index, callback) { 12 | const slideContainer = slide.querySelector('.ginner-container'); 13 | const videoID = 'gvideo' + index; 14 | const slideMedia = slide.querySelector('.gslide-media'); 15 | const videoPlayers = this.getAllPlayers(); 16 | 17 | addClass(slideContainer, 'gvideo-container'); 18 | 19 | slideMedia.insertBefore(createHTML('
'), slideMedia.firstChild); 20 | 21 | const videoWrapper = slide.querySelector('.gvideo-wrapper'); 22 | 23 | injectAssets(this.settings.plyr.css, 'Plyr'); 24 | 25 | let url = data.href; 26 | let protocol = location.protocol.replace(':', ''); 27 | let videoSource = ''; 28 | let embedID = ''; 29 | let customPlaceholder = false; 30 | 31 | if (protocol == 'file') { 32 | protocol = 'http'; 33 | } 34 | slideMedia.style.maxWidth = data.width; 35 | 36 | injectAssets(this.settings.plyr.js, 'Plyr', () => { 37 | // Set vimeo videos 38 | if (url.match(/vimeo\.com\/([0-9]*)/)) { 39 | const vimeoID = /vimeo.*\/(\d+)/i.exec(url); 40 | videoSource = 'vimeo'; 41 | embedID = vimeoID[1]; 42 | } 43 | 44 | // Set youtube videos 45 | if (url.match(/(youtube\.com|youtube-nocookie\.com)\/watch\?v=([a-zA-Z0-9\-_]+)/) || url.match(/youtu\.be\/([a-zA-Z0-9\-_]+)/) || url.match(/(youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9\-_]+)/)) { 46 | const youtubeID = getYoutubeID(url); 47 | videoSource = 'youtube'; 48 | embedID = youtubeID; 49 | } 50 | 51 | // Set local videos 52 | if (url.match(/\.(mp4|ogg|webm|mov)$/) !== null) { 53 | videoSource = 'local'; 54 | let html = ''; 79 | customPlaceholder = createHTML(html); 80 | } 81 | 82 | // prettier-ignore 83 | const placeholder = customPlaceholder ? customPlaceholder : createHTML(`
`); 84 | 85 | addClass(videoWrapper, `${videoSource}-video gvideo`); 86 | videoWrapper.appendChild(placeholder); 87 | videoWrapper.setAttribute('data-id', videoID); 88 | videoWrapper.setAttribute('data-index', index); 89 | 90 | const playerConfig = has(this.settings.plyr, 'config') ? this.settings.plyr.config : {}; 91 | const player = new Plyr('#' + videoID, playerConfig); 92 | 93 | player.on('ready', (event) => { 94 | const instance = event.detail.plyr; 95 | videoPlayers[videoID] = instance; 96 | if (isFunction(callback)) { 97 | callback(); 98 | } 99 | }); 100 | waitUntil( 101 | () => { 102 | return slide.querySelector('iframe') && slide.querySelector('iframe').dataset.ready == 'true'; 103 | }, 104 | () => { 105 | this.resize(slide); 106 | } 107 | ); 108 | player.on('enterfullscreen', handleMediaFullScreen); 109 | player.on('exitfullscreen', handleMediaFullScreen); 110 | }); 111 | } 112 | 113 | /** 114 | * Get youtube ID 115 | * 116 | * @param {string} url 117 | * @returns {string} video id 118 | */ 119 | function getYoutubeID(url) { 120 | let videoID = ''; 121 | url = url.replace(/(>|<)/gi, '').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/); 122 | if (url[2] !== undefined) { 123 | videoID = url[2].split(/[^0-9a-z_\-]/i); 124 | videoID = videoID[0]; 125 | } else { 126 | videoID = url; 127 | } 128 | return videoID; 129 | } 130 | 131 | /** 132 | * Handle fullscreen 133 | * 134 | * @param {object} event 135 | */ 136 | function handleMediaFullScreen(event) { 137 | const media = closest(event.target, '.gslide-media'); 138 | 139 | if (event.type == 'enterfullscreen') { 140 | addClass(media, 'fullscreen'); 141 | } 142 | if (event.type == 'exitfullscreen') { 143 | removeClass(media, 'fullscreen'); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/js/core/slide-parser.js: -------------------------------------------------------------------------------- 1 | import { extend, has, each, isNil, isNode, isObject, isNumber } from '../utils/helpers.js'; 2 | 3 | export default class SlideConfigParser { 4 | constructor(slideParamas = {}) { 5 | this.defaults = { 6 | href: '', 7 | title: '', 8 | type: '', 9 | description: '', 10 | descPosition: 'bottom', 11 | effect: '', 12 | width: '', 13 | height: '', 14 | content: false, 15 | zoomable: true, 16 | draggable: true 17 | }; 18 | 19 | if (isObject(slideParamas)) { 20 | this.defaults = extend(this.defaults, slideParamas); 21 | } 22 | } 23 | 24 | /** 25 | * Get source type 26 | * gte the source type of a url 27 | * 28 | * @param {string} url 29 | */ 30 | sourceType(url) { 31 | let origin = url; 32 | url = url.toLowerCase(); 33 | 34 | if (url.match(/\.(jpeg|jpg|jpe|gif|png|apn|webp|svg)$/) !== null) { 35 | return 'image'; 36 | } 37 | if (url.match(/(youtube\.com|youtube-nocookie\.com)\/watch\?v=([a-zA-Z0-9\-_]+)/) || url.match(/youtu\.be\/([a-zA-Z0-9\-_]+)/) || url.match(/(youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9\-_]+)/)) { 38 | return 'video'; 39 | } 40 | if (url.match(/vimeo\.com\/([0-9]*)/)) { 41 | return 'video'; 42 | } 43 | if (url.match(/\.(mp4|ogg|webm|mov)$/) !== null) { 44 | return 'video'; 45 | } 46 | if (url.match(/\.(mp3|wav|wma|aac|ogg)$/) !== null) { 47 | return 'audio'; 48 | } 49 | 50 | // Check if inline content 51 | if (url.indexOf('#') > -1) { 52 | let hash = origin.split('#').pop(); 53 | if (hash.trim() !== '') { 54 | return 'inline'; 55 | } 56 | } 57 | // Ajax 58 | if (url.indexOf('goajax=true') > -1) { 59 | return 'ajax'; 60 | } 61 | 62 | return 'external'; 63 | } 64 | 65 | parseConfig(element, settings) { 66 | let data = extend({ descPosition: settings.descPosition }, this.defaults); 67 | 68 | if (isObject(element) && !isNode(element)) { 69 | if (!has(element, 'type')) { 70 | if (has(element, 'content') && element.content) { 71 | element.type = 'inline'; 72 | } else if (has(element, 'href')) { 73 | element.type = this.sourceType(element.href); 74 | } 75 | } 76 | let objectData = extend(data, element); 77 | this.setSize(objectData, settings); 78 | 79 | return objectData; 80 | } 81 | 82 | let url = ''; 83 | let config = element.getAttribute('data-glightbox'); 84 | let nodeType = element.nodeName.toLowerCase(); 85 | if (nodeType === 'a') { 86 | url = element.href; 87 | } 88 | if (nodeType === 'img') { 89 | url = element.src; 90 | } 91 | 92 | data.href = url; 93 | 94 | each(data, (val, key) => { 95 | if (has(settings, key) && key !== 'width') { 96 | data[key] = settings[key]; 97 | } 98 | const nodeData = element.dataset[key]; 99 | if (!isNil(nodeData)) { 100 | data[key] = this.sanitizeValue(nodeData); 101 | } 102 | }); 103 | 104 | if (data.content) { 105 | data.type = 'inline'; 106 | } 107 | 108 | if (!data.type && url) { 109 | data.type = this.sourceType(url); 110 | } 111 | 112 | if (!isNil(config)) { 113 | let cleanKeys = []; 114 | each(data, (v, k) => { 115 | cleanKeys.push(';\\s?' + k); 116 | }); 117 | cleanKeys = cleanKeys.join('\\s?:|'); 118 | if (config.trim() !== '') { 119 | each(data, (val, key) => { 120 | const str = config; 121 | const match = 's?' + key + 's?:s?(.*?)(' + cleanKeys + 's?:|$)'; 122 | const regex = new RegExp(match); 123 | const matches = str.match(regex); 124 | 125 | if (matches && matches.length && matches[1]) { 126 | const value = matches[1].trim().replace(/;\s*$/, ''); 127 | data[key] = this.sanitizeValue(value); 128 | } 129 | }); 130 | } 131 | } else { 132 | if (!data.title && nodeType == 'a') { 133 | let title = element.title; 134 | if (!isNil(title) && title !== '') { 135 | data.title = title; 136 | } 137 | } 138 | if (!data.title && nodeType == 'img') { 139 | let alt = element.alt; 140 | if (!isNil(alt) && alt !== '') { 141 | data.title = alt; 142 | } 143 | } 144 | } 145 | 146 | // Try to get the description from a referenced element 147 | if (data.description && data.description.substring(0, 1) === '.') { 148 | let description; 149 | 150 | try { 151 | description = document.querySelector(data.description).innerHTML; 152 | } catch (error) { 153 | if (!(error instanceof DOMException)) { 154 | throw error; 155 | } 156 | } 157 | 158 | if (description) { 159 | data.description = description; 160 | } 161 | } 162 | 163 | // Try to get the description from a .glightbox-desc element 164 | if (!data.description) { 165 | let nodeDesc = element.querySelector('.glightbox-desc'); 166 | if (nodeDesc) { 167 | data.description = nodeDesc.innerHTML; 168 | } 169 | } 170 | 171 | this.setSize(data, settings, element); 172 | this.slideConfig = data; 173 | 174 | return data; 175 | } 176 | 177 | /** 178 | * Set slide data size 179 | * set the correct size dependin 180 | * on the slide type 181 | * 182 | * @param { object } data 183 | * @param { object } settings 184 | * @return { object } 185 | */ 186 | setSize(data, settings, element = null) { 187 | const defaultWith = data.type == 'video' ? this.checkSize(settings.videosWidth) : this.checkSize(settings.width); 188 | const defaultHeight = this.checkSize(settings.height); 189 | 190 | data.width = has(data, 'width') && data.width !== '' ? this.checkSize(data.width) : defaultWith; 191 | data.height = has(data, 'height') && data.height !== '' ? this.checkSize(data.height) : defaultHeight; 192 | 193 | if (element && data.type == 'image') { 194 | data._hasCustomWidth = element.dataset.width ? true : false; 195 | data._hasCustomHeight = element.dataset.height ? true : false; 196 | } 197 | 198 | return data; 199 | } 200 | 201 | /** 202 | * [checkSize size 203 | * check if the passed size has a correct unit 204 | * 205 | * @param {string} size 206 | * @return {string} 207 | */ 208 | checkSize(size) { 209 | return isNumber(size) ? `${size}px` : size; 210 | } 211 | 212 | /** 213 | * Sanitize data attributes value 214 | * 215 | * @param string val 216 | * @return mixed 217 | */ 218 | sanitizeValue(val) { 219 | if (val !== 'true' && val !== 'false') { 220 | return val; 221 | } 222 | return val === 'true'; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/js/core/drag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DragSlides 3 | * Allow imaes to be dragged for prev and next 4 | * in desktops 5 | * 6 | * @param { object } config 7 | */ 8 | 9 | import { closest } from '../utils/helpers.js'; 10 | 11 | export default class DragSlides { 12 | constructor(config = {}) { 13 | let { dragEl, toleranceX = 40, toleranceY = 65, slide = null, instance = null } = config; 14 | 15 | this.el = dragEl; 16 | this.active = false; 17 | this.dragging = false; 18 | this.currentX = null; 19 | this.currentY = null; 20 | this.initialX = null; 21 | this.initialY = null; 22 | this.xOffset = 0; 23 | this.yOffset = 0; 24 | this.direction = null; 25 | this.lastDirection = null; 26 | this.toleranceX = toleranceX; 27 | this.toleranceY = toleranceY; 28 | this.toleranceReached = false; 29 | this.dragContainer = this.el; 30 | this.slide = slide; 31 | this.instance = instance; 32 | 33 | this.el.addEventListener('mousedown', (e) => this.dragStart(e), false); 34 | this.el.addEventListener('mouseup', (e) => this.dragEnd(e), false); 35 | this.el.addEventListener('mousemove', (e) => this.drag(e), false); 36 | } 37 | dragStart(e) { 38 | if (this.slide.classList.contains('zoomed')) { 39 | this.active = false; 40 | return; 41 | } 42 | 43 | if (e.type === 'touchstart') { 44 | this.initialX = e.touches[0].clientX - this.xOffset; 45 | this.initialY = e.touches[0].clientY - this.yOffset; 46 | } else { 47 | this.initialX = e.clientX - this.xOffset; 48 | this.initialY = e.clientY - this.yOffset; 49 | } 50 | 51 | let clicked = e.target.nodeName.toLowerCase(); 52 | let exludeClicks = ['input', 'select', 'textarea', 'button', 'a']; 53 | if ( 54 | e.target.classList.contains('nodrag') || 55 | closest(e.target, '.nodrag') || 56 | exludeClicks.indexOf(clicked) !== -1 57 | ) { 58 | this.active = false; 59 | return; 60 | } 61 | 62 | e.preventDefault(); 63 | 64 | if (e.target === this.el || (clicked !== 'img' && closest(e.target, '.gslide-inline'))) { 65 | this.active = true; 66 | this.el.classList.add('dragging'); 67 | this.dragContainer = closest(e.target, '.ginner-container'); 68 | } 69 | } 70 | dragEnd(e) { 71 | e && e.preventDefault(); 72 | this.initialX = 0; 73 | this.initialY = 0; 74 | this.currentX = null; 75 | this.currentY = null; 76 | this.initialX = null; 77 | this.initialY = null; 78 | this.xOffset = 0; 79 | this.yOffset = 0; 80 | this.active = false; 81 | 82 | if (this.doSlideChange) { 83 | this.instance.preventOutsideClick = true; 84 | this.doSlideChange == 'right' && this.instance.prevSlide(); 85 | this.doSlideChange == 'left' && this.instance.nextSlide(); 86 | } 87 | 88 | if (this.doSlideClose) { 89 | this.instance.close(); 90 | } 91 | 92 | if (!this.toleranceReached) { 93 | this.setTranslate(this.dragContainer, 0, 0, true); 94 | } 95 | 96 | setTimeout(() => { 97 | this.instance.preventOutsideClick = false; 98 | this.toleranceReached = false; 99 | this.lastDirection = null; 100 | this.dragging = false; 101 | this.el.isDragging = false; 102 | this.el.classList.remove('dragging'); 103 | this.slide.classList.remove('dragging-nav'); 104 | this.dragContainer.style.transform = ''; 105 | this.dragContainer.style.transition = ''; 106 | }, 100); 107 | } 108 | drag(e) { 109 | if (this.active) { 110 | e.preventDefault(); 111 | 112 | this.slide.classList.add('dragging-nav'); 113 | 114 | if (e.type === 'touchmove') { 115 | this.currentX = e.touches[0].clientX - this.initialX; 116 | this.currentY = e.touches[0].clientY - this.initialY; 117 | } else { 118 | this.currentX = e.clientX - this.initialX; 119 | this.currentY = e.clientY - this.initialY; 120 | } 121 | 122 | this.xOffset = this.currentX; 123 | this.yOffset = this.currentY; 124 | 125 | this.el.isDragging = true; 126 | this.dragging = true; 127 | this.doSlideChange = false; 128 | this.doSlideClose = false; 129 | 130 | let currentXInt = Math.abs(this.currentX); 131 | let currentYInt = Math.abs(this.currentY); 132 | 133 | // Horizontal drag 134 | if ( 135 | currentXInt > 0 && 136 | currentXInt >= Math.abs(this.currentY) && 137 | (!this.lastDirection || this.lastDirection == 'x') 138 | ) { 139 | this.yOffset = 0; 140 | this.lastDirection = 'x'; 141 | this.setTranslate(this.dragContainer, this.currentX, 0); 142 | 143 | let doChange = this.shouldChange(); 144 | if (!this.instance.settings.dragAutoSnap && doChange) { 145 | this.doSlideChange = doChange; 146 | } 147 | 148 | if (this.instance.settings.dragAutoSnap && doChange) { 149 | this.instance.preventOutsideClick = true; 150 | this.toleranceReached = true; 151 | this.active = false; 152 | this.instance.preventOutsideClick = true; 153 | this.dragEnd(null); 154 | doChange == 'right' && this.instance.prevSlide(); 155 | doChange == 'left' && this.instance.nextSlide(); 156 | return; 157 | } 158 | } 159 | 160 | // Vertical drag 161 | if ( 162 | this.toleranceY > 0 && 163 | currentYInt > 0 && 164 | currentYInt >= currentXInt && 165 | (!this.lastDirection || this.lastDirection == 'y') 166 | ) { 167 | this.xOffset = 0; 168 | this.lastDirection = 'y'; 169 | this.setTranslate(this.dragContainer, 0, this.currentY); 170 | 171 | let doClose = this.shouldClose(); 172 | 173 | if (!this.instance.settings.dragAutoSnap && doClose) { 174 | this.doSlideClose = true; 175 | } 176 | if (this.instance.settings.dragAutoSnap && doClose) { 177 | this.instance.close(); 178 | } 179 | return; 180 | } 181 | } 182 | } 183 | 184 | shouldChange() { 185 | let doChange = false; 186 | let currentXInt = Math.abs(this.currentX); 187 | 188 | if (currentXInt >= this.toleranceX) { 189 | let dragDir = this.currentX > 0 ? 'right' : 'left'; 190 | 191 | if ( 192 | (dragDir == 'left' && this.slide !== this.slide.parentNode.lastChild) || 193 | (dragDir == 'right' && this.slide !== this.slide.parentNode.firstChild) 194 | ) { 195 | doChange = dragDir; 196 | } 197 | } 198 | return doChange; 199 | } 200 | 201 | shouldClose() { 202 | let doClose = false; 203 | let currentYInt = Math.abs(this.currentY); 204 | 205 | if (currentYInt >= this.toleranceY) { 206 | doClose = true; 207 | } 208 | return doClose; 209 | } 210 | 211 | setTranslate(node, xPos, yPos, animated = false) { 212 | if (animated) { 213 | node.style.transition = 'all .2s ease'; 214 | } else { 215 | node.style.transition = ''; 216 | } 217 | node.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/js/core/slide.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Slide 3 | * class to hablde slide creation 4 | * and config parser 5 | */ 6 | 7 | import ZoomImages from './zoom.js'; 8 | import DragSlides from './drag.js'; 9 | import slideImage from '../slides/image.js'; 10 | import slideVideo from '../slides/video.js'; 11 | import slideInline from '../slides/inline.js'; 12 | import slideIframe from '../slides/iframe.js'; 13 | import SlideConfigParser from './slide-parser.js'; 14 | import { addEvent, addClass, removeClass, hasClass, closest, isMobile, isFunction, createHTML } from '../utils/helpers.js'; 15 | 16 | export default class Slide { 17 | constructor(el, instance, index) { 18 | this.element = el; 19 | this.instance = instance; 20 | this.index = index; 21 | } 22 | 23 | /** 24 | * Set slide content 25 | * 26 | * @param {node} slide 27 | * @param {object} data 28 | * @param {function} callback 29 | */ 30 | setContent(slide = null, callback = false) { 31 | if (hasClass(slide, 'loaded')) { 32 | return false; 33 | } 34 | 35 | const settings = this.instance.settings; 36 | const slideConfig = this.slideConfig; 37 | const isMobileDevice = isMobile(); 38 | 39 | if (isFunction(settings.beforeSlideLoad)) { 40 | settings.beforeSlideLoad({ 41 | index: this.index, 42 | slide: slide, 43 | player: false 44 | }); 45 | } 46 | 47 | let type = slideConfig.type; 48 | let position = slideConfig.descPosition; 49 | let slideMedia = slide.querySelector('.gslide-media'); 50 | let slideTitle = slide.querySelector('.gslide-title'); 51 | let slideText = slide.querySelector('.gslide-desc'); 52 | let slideDesc = slide.querySelector('.gdesc-inner'); 53 | let finalCallback = callback; 54 | 55 | // used for image accessiblity 56 | let titleID = 'gSlideTitle_' + this.index; 57 | let textID = 'gSlideDesc_' + this.index; 58 | 59 | if (isFunction(settings.afterSlideLoad)) { 60 | finalCallback = () => { 61 | if (isFunction(callback)) { 62 | callback(); 63 | } 64 | settings.afterSlideLoad({ 65 | index: this.index, 66 | slide: slide, 67 | player: this.instance.getSlidePlayerInstance(this.index) 68 | }); 69 | }; 70 | } 71 | 72 | if (slideConfig.title == '' && slideConfig.description == '') { 73 | if (slideDesc) { 74 | slideDesc.parentNode.parentNode.removeChild(slideDesc.parentNode); 75 | } 76 | } else { 77 | if (slideTitle && slideConfig.title !== '') { 78 | slideTitle.id = titleID; 79 | slideTitle.innerHTML = slideConfig.title; 80 | } else { 81 | slideTitle.parentNode.removeChild(slideTitle); 82 | } 83 | if (slideText && slideConfig.description !== '') { 84 | slideText.id = textID; 85 | if (isMobileDevice && settings.moreLength > 0) { 86 | slideConfig.smallDescription = this.slideShortDesc(slideConfig.description, settings.moreLength, settings.moreText); 87 | slideText.innerHTML = slideConfig.smallDescription; 88 | this.descriptionEvents(slideText, slideConfig); 89 | } else { 90 | slideText.innerHTML = slideConfig.description; 91 | } 92 | } else { 93 | slideText.parentNode.removeChild(slideText); 94 | } 95 | addClass(slideMedia.parentNode, `desc-${position}`); 96 | addClass(slideDesc.parentNode, `description-${position}`); 97 | } 98 | 99 | addClass(slideMedia, `gslide-${type}`); 100 | addClass(slide, 'loaded'); 101 | 102 | if (type === 'video') { 103 | slideVideo.apply(this.instance, [slide, slideConfig, this.index, finalCallback]); 104 | return; 105 | } 106 | 107 | if (type === 'external') { 108 | slideIframe.apply(this, [slide, slideConfig, this.index, finalCallback]); 109 | return; 110 | } 111 | 112 | if (type === 'inline') { 113 | slideInline.apply(this.instance, [slide, slideConfig, this.index, finalCallback]); 114 | if (settings.draggable) { 115 | new DragSlides({ 116 | dragEl: slide.querySelector('.gslide-inline'), 117 | toleranceX: settings.dragToleranceX, 118 | toleranceY: settings.dragToleranceY, 119 | slide: slide, 120 | instance: this.instance 121 | }); 122 | } 123 | return; 124 | } 125 | 126 | if (type === 'image') { 127 | slideImage(slide, slideConfig, this.index, () => { 128 | const img = slide.querySelector('img'); 129 | 130 | if (settings.draggable) { 131 | new DragSlides({ 132 | dragEl: img, 133 | toleranceX: settings.dragToleranceX, 134 | toleranceY: settings.dragToleranceY, 135 | slide: slide, 136 | instance: this.instance 137 | }); 138 | } 139 | if (slideConfig.zoomable && img.naturalWidth > img.offsetWidth) { 140 | addClass(img, 'zoomable'); 141 | new ZoomImages(img, slide, () => { 142 | this.instance.resize(); 143 | }); 144 | } 145 | 146 | if (isFunction(finalCallback)) { 147 | finalCallback(); 148 | } 149 | }); 150 | return; 151 | } 152 | 153 | if (isFunction(finalCallback)) { 154 | finalCallback(); 155 | } 156 | } 157 | 158 | slideShortDesc(string, n = 50, wordBoundary = false) { 159 | let div = document.createElement('div'); 160 | div.innerHTML = string; 161 | let cleanedString = div.innerText; 162 | 163 | let useWordBoundary = wordBoundary; 164 | string = cleanedString.trim(); 165 | if (string.length <= n) { 166 | return string; 167 | } 168 | let subString = string.substr(0, n - 1); 169 | if (!useWordBoundary) { 170 | return subString; 171 | } 172 | 173 | div = null; 174 | return subString + '... ' + wordBoundary + ''; 175 | } 176 | 177 | descriptionEvents(desc, data) { 178 | let moreLink = desc.querySelector('.desc-more'); 179 | if (!moreLink) { 180 | return false; 181 | } 182 | 183 | addEvent('click', { 184 | onElement: moreLink, 185 | withCallback: (event, target) => { 186 | event.preventDefault(); 187 | const body = document.body; 188 | 189 | let desc = closest(target, '.gslide-desc'); 190 | if (!desc) { 191 | return false; 192 | } 193 | 194 | desc.innerHTML = data.description; 195 | addClass(body, 'gdesc-open'); 196 | 197 | let shortEvent = addEvent('click', { 198 | onElement: [body, closest(desc, '.gslide-description')], 199 | withCallback: (event, target) => { 200 | if (event.target.nodeName.toLowerCase() !== 'a') { 201 | removeClass(body, 'gdesc-open'); 202 | addClass(body, 'gdesc-closed'); 203 | desc.innerHTML = data.smallDescription; 204 | this.descriptionEvents(desc, data); 205 | 206 | setTimeout(() => { 207 | removeClass(body, 'gdesc-closed'); 208 | }, 400); 209 | shortEvent.destroy(); 210 | } 211 | } 212 | }); 213 | } 214 | }); 215 | } 216 | 217 | /** 218 | * Create Slide Node 219 | * 220 | * @return { node } 221 | */ 222 | create() { 223 | return createHTML(this.instance.settings.slideHTML); 224 | } 225 | 226 | /** 227 | * Get slide config 228 | * returns each individual slide config 229 | * it uses SlideConfigParser 230 | * each slide can overwrite a global setting 231 | * read more in the SlideConfigParser class 232 | * 233 | * @return { object } 234 | */ 235 | getConfig() { 236 | const parser = new SlideConfigParser(this.instance.settings.slideExtraAttributes); 237 | this.slideConfig = parser.parseConfig(this.element, this.instance.settings); 238 | 239 | return this.slideConfig; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/js/core/touch-navigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Touch Navigation 3 | * Allow navigation using touch events 4 | * 5 | * @param {object} instance 6 | */ 7 | 8 | import TouchEvents from './touch-events.js'; 9 | import { addEvent, addClass, removeClass, hasClass, closest, whichTransitionEvent, cssTransform, windowSize } from '../utils/helpers.js'; 10 | 11 | function resetSlideMove(slide) { 12 | const transitionEnd = whichTransitionEvent(); 13 | const windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 14 | 15 | let media = hasClass(slide, 'gslide-media') ? slide : slide.querySelector('.gslide-media'); 16 | let container = closest(media, '.ginner-container'); 17 | let desc = slide.querySelector('.gslide-description'); 18 | 19 | if (windowWidth > 769) { 20 | media = container; 21 | } 22 | 23 | addClass(media, 'greset'); 24 | cssTransform(media, 'translate3d(0, 0, 0)'); 25 | addEvent(transitionEnd, { 26 | onElement: media, 27 | once: true, 28 | withCallback: (event, target) => { 29 | removeClass(media, 'greset'); 30 | } 31 | }); 32 | 33 | media.style.opacity = ''; 34 | if (desc) { 35 | desc.style.opacity = ''; 36 | } 37 | } 38 | 39 | export default function touchNavigation(instance) { 40 | if (instance.events.hasOwnProperty('touch')) { 41 | return false; 42 | } 43 | 44 | let winSize = windowSize(); 45 | let winWidth = winSize.width; 46 | let winHeight = winSize.height; 47 | let process = false; 48 | let currentSlide = null; 49 | let media = null; 50 | let mediaImage = null; 51 | let doingMove = false; 52 | let initScale = 1; 53 | let maxScale = 4.5; 54 | let currentScale = 1; 55 | let doingZoom = false; 56 | let imageZoomed = false; 57 | let zoomedPosX = null; 58 | let zoomedPosY = null; 59 | let lastZoomedPosX = null; 60 | let lastZoomedPosY = null; 61 | let hDistance; 62 | let vDistance; 63 | let hDistancePercent = 0; 64 | let vDistancePercent = 0; 65 | let vSwipe = false; 66 | let hSwipe = false; 67 | let startCoords = {}; 68 | let endCoords = {}; 69 | let xDown = 0; 70 | let yDown = 0; 71 | let isInlined; 72 | 73 | const sliderWrapper = document.getElementById('glightbox-slider'); 74 | const overlay = document.querySelector('.goverlay'); 75 | 76 | const touchInstance = new TouchEvents(sliderWrapper, { 77 | touchStart: (e) => { 78 | process = true; 79 | 80 | // TODO: More tests for inline content slides 81 | if (hasClass(e.targetTouches[0].target, 'ginner-container') || closest(e.targetTouches[0].target, '.gslide-desc') || e.targetTouches[0].target.nodeName.toLowerCase() == 'a') { 82 | process = false; 83 | } 84 | 85 | if (closest(e.targetTouches[0].target, '.gslide-inline') && !hasClass(e.targetTouches[0].target.parentNode, 'gslide-inline')) { 86 | process = false; 87 | } 88 | 89 | if (process) { 90 | endCoords = e.targetTouches[0]; 91 | startCoords.pageX = e.targetTouches[0].pageX; 92 | startCoords.pageY = e.targetTouches[0].pageY; 93 | xDown = e.targetTouches[0].clientX; 94 | yDown = e.targetTouches[0].clientY; 95 | 96 | currentSlide = instance.activeSlide; 97 | media = currentSlide.querySelector('.gslide-media'); 98 | isInlined = currentSlide.querySelector('.gslide-inline'); 99 | 100 | mediaImage = null; 101 | if (hasClass(media, 'gslide-image')) { 102 | mediaImage = media.querySelector('img'); 103 | } 104 | 105 | const windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 106 | 107 | if (windowWidth > 769) { 108 | media = currentSlide.querySelector('.ginner-container'); 109 | } 110 | 111 | removeClass(overlay, 'greset'); 112 | 113 | if (e.pageX > 20 && e.pageX < window.innerWidth - 20) { 114 | return; 115 | } 116 | e.preventDefault(); 117 | } 118 | }, 119 | touchMove: (e) => { 120 | if (!process) { 121 | return; 122 | } 123 | endCoords = e.targetTouches[0]; 124 | 125 | if (doingZoom || imageZoomed) { 126 | return; 127 | } 128 | if (isInlined && isInlined.offsetHeight > winHeight) { 129 | // Allow scroll without moving the slide 130 | const moved = startCoords.pageX - endCoords.pageX; 131 | if (Math.abs(moved) <= 13) { 132 | return false; 133 | } 134 | } 135 | 136 | doingMove = true; 137 | let xUp = e.targetTouches[0].clientX; 138 | let yUp = e.targetTouches[0].clientY; 139 | let xDiff = xDown - xUp; 140 | let yDiff = yDown - yUp; 141 | 142 | if (Math.abs(xDiff) > Math.abs(yDiff)) { 143 | vSwipe = false; 144 | hSwipe = true; 145 | } else { 146 | hSwipe = false; 147 | vSwipe = true; 148 | } 149 | 150 | hDistance = endCoords.pageX - startCoords.pageX; 151 | hDistancePercent = (hDistance * 100) / winWidth; 152 | 153 | vDistance = endCoords.pageY - startCoords.pageY; 154 | vDistancePercent = (vDistance * 100) / winHeight; 155 | 156 | let opacity; 157 | if (vSwipe && mediaImage) { 158 | opacity = 1 - Math.abs(vDistance) / winHeight; 159 | overlay.style.opacity = opacity; 160 | 161 | if (instance.settings.touchFollowAxis) { 162 | hDistancePercent = 0; 163 | } 164 | } 165 | if (hSwipe) { 166 | opacity = 1 - Math.abs(hDistance) / winWidth; 167 | media.style.opacity = opacity; 168 | 169 | if (instance.settings.touchFollowAxis) { 170 | vDistancePercent = 0; 171 | } 172 | } 173 | 174 | if (!mediaImage) { 175 | return cssTransform(media, `translate3d(${hDistancePercent}%, 0, 0)`); 176 | } 177 | 178 | cssTransform(media, `translate3d(${hDistancePercent}%, ${vDistancePercent}%, 0)`); 179 | }, 180 | touchEnd: () => { 181 | if (!process) { 182 | return; 183 | } 184 | doingMove = false; 185 | if (imageZoomed || doingZoom) { 186 | lastZoomedPosX = zoomedPosX; 187 | lastZoomedPosY = zoomedPosY; 188 | return; 189 | } 190 | const v = Math.abs(parseInt(vDistancePercent)); 191 | const h = Math.abs(parseInt(hDistancePercent)); 192 | 193 | if (v > 29 && mediaImage) { 194 | instance.close(); 195 | return; 196 | } 197 | if (v < 29 && h < 25) { 198 | addClass(overlay, 'greset'); 199 | overlay.style.opacity = 1; 200 | return resetSlideMove(media); 201 | } 202 | }, 203 | multipointEnd: () => { 204 | setTimeout(() => { 205 | doingZoom = false; 206 | }, 50); 207 | }, 208 | multipointStart: () => { 209 | doingZoom = true; 210 | initScale = currentScale ? currentScale : 1; 211 | }, 212 | pinch: (evt) => { 213 | if (!mediaImage || doingMove) { 214 | return false; 215 | } 216 | 217 | doingZoom = true; 218 | mediaImage.scaleX = mediaImage.scaleY = initScale * evt.zoom; 219 | 220 | let scale = initScale * evt.zoom; 221 | imageZoomed = true; 222 | 223 | if (scale <= 1) { 224 | imageZoomed = false; 225 | scale = 1; 226 | lastZoomedPosY = null; 227 | lastZoomedPosX = null; 228 | zoomedPosX = null; 229 | zoomedPosY = null; 230 | mediaImage.setAttribute('style', ''); 231 | return; 232 | } 233 | if (scale > maxScale) { 234 | // max scale zoom 235 | scale = maxScale; 236 | } 237 | 238 | mediaImage.style.transform = `scale3d(${scale}, ${scale}, 1)`; 239 | currentScale = scale; 240 | }, 241 | pressMove: (e) => { 242 | if (imageZoomed && !doingZoom) { 243 | var mhDistance = endCoords.pageX - startCoords.pageX; 244 | var mvDistance = endCoords.pageY - startCoords.pageY; 245 | 246 | if (lastZoomedPosX) { 247 | mhDistance = mhDistance + lastZoomedPosX; 248 | } 249 | if (lastZoomedPosY) { 250 | mvDistance = mvDistance + lastZoomedPosY; 251 | } 252 | 253 | zoomedPosX = mhDistance; 254 | zoomedPosY = mvDistance; 255 | 256 | let style = `translate3d(${mhDistance}px, ${mvDistance}px, 0)`; 257 | if (currentScale) { 258 | style += ` scale3d(${currentScale}, ${currentScale}, 1)`; 259 | } 260 | cssTransform(mediaImage, style); 261 | } 262 | }, 263 | swipe: (evt) => { 264 | if (imageZoomed) { 265 | return; 266 | } 267 | if (doingZoom) { 268 | doingZoom = false; 269 | return; 270 | } 271 | if (evt.direction == 'Left') { 272 | if (instance.index == instance.elements.length - 1) { 273 | return resetSlideMove(media); 274 | } 275 | instance.nextSlide(); 276 | } 277 | if (evt.direction == 'Right') { 278 | if (instance.index == 0) { 279 | return resetSlideMove(media); 280 | } 281 | instance.prevSlide(); 282 | } 283 | } 284 | }); 285 | 286 | instance.events['touch'] = touchInstance; 287 | } 288 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## 3.0.9 6 | 7 | - Updated: Plyr to 3.6.8 8 | - Improved: JS error when the description attribute is an invalid selector 9 | - Fixed: Media Buttons Not responding on Android #233 10 | - Fixed: When touch is activated images swipe independently of their descriptions #238 11 | - Fixed: Width / Height data attributes do not work for image types #234 12 | - Fixed: Adds missing size unit of video description on resize #229 13 | 14 | ## 3.0.8 15 | 16 | - New: Added Aria-hidden on all root elements except the glightbox-container 17 | - Fixed: Video in portrait mode is cropped 18 | - Fixed: Video always has maxWidth 900px because of hardcoded setting 19 | - Fixed: Removed explicit tabindex from navigation buttons 20 | 21 | ## 3.0.7 22 | 23 | - Fixed: Lightbox playing incorrect video with multiple videos in gallery #187 24 | - Fixed: Draggable: false option not working #192 25 | - Fixed: Links not working inside inline content in mobile devices 26 | - Fixed: moreLength not working correctly on mobile if description has HTML 27 | - Added: Added plugins option for a future release that will allow extending GLightbox with plugins 28 | - Changed: Plyr fullscreen set to iosNative 29 | - Changed: Renamed skin option to theme for a future release that will allow creating themes like plugins, skin still works but will be replaced in a future relase 30 | 31 | ## 3.0.6 32 | 33 | - Fixed: Events cleanup on `destroy()` method causes exception #175 34 | - Fixed: "data-title" Getting Overwritten when "title" is present #178 35 | - Fixed: IE 11 "Multiple definitions of a property not allowed in strict mode when bundling and minifying glightbox #155 36 | - Fixed: Cannot add new slides #166 37 | - Fixed: Video not playing on mobile #160 38 | - Improved: Plyr will only be loaded if it's already used in the site 39 | - New: Updated PLYR version to 3.6.3 40 | - New: Added new option "autofocusVideos" to enable all Plyr shortcuts for the video player 41 | - New: Improved events to use a generic way to access data 42 | - New: Improved code 43 | 44 | ## 3.0.5 45 | 46 | - Fixed: IE11 does not support ".includes()" 47 | - Fixed: Clicking outside the content to close only works in specific areas 48 | - Fixed: openEffect / closeEffect no longer accepting "none" 49 | 50 | ## 3.0.4 51 | 52 | - New: New way to listen for events, the old events will still work but will be removed in a future update. See the Events section in the README 53 | - New: Added new methods "slidePlayerPause" and "slidePlayerPlay" so in the future they will replace "playSlideVideo" and "stopSlideVideo" to provide support for audio slides in a future update 54 | - New: Add preventDefault via touchstart on lightbox to prevent navigation swipe 55 | - Fixed: e.getAttribute is not a function when there are no nodes in the gallery 56 | 57 | ## 3.0.3 58 | 59 | - New: Option "dragAutoSnap" to control the mouse drag auto close or change slide 60 | - Fixed: Multiple galleries not working 61 | - Fixed: Setting the elements directly using an object triggered error 62 | 63 | ## 3.0.2 64 | 65 | - New: Option "zoomable" to enable or disable zoomable images 66 | - New: Option "preload" to enable or disable preloading 67 | - New: Option "draggable" to go to prev/next slide by mouse dragging. Thanks to @Hirbod for the donation to make this happen 68 | - New: Option "dragToleranceX" Used with draggable. Number of pixels the user has to drag to go to prev or next slide 69 | - New: Option "dragToleranceY" Used with draggable. Number of pixels the user has to drag up or down to close the lightbox (Set 0 to disable vertical drag) 70 | - New: The code was refactored to make it easier to maintain 71 | - Fixed: data gallery stopped working 72 | - Fixed: iOS bug with Vimeo iframe when fullscreen button pressed 73 | - Fixed: "See more" link in the description throws an JS error when clicked 74 | - Fixed: Videos not resized vertically when window height was smaller than the window width 75 | 76 | ## 3.0.1 77 | 78 | - Fixed: vertical scrolling of descriptions [@zothynine](https://github.com/biati-digital/glightbox/pull/134) 79 | - Fixed: CSS properties bug 80 | - Fixed: Passing null as selector throws an exception 81 | 82 | ## 3.0.0 83 | 84 | - New: New methods to access player instances "getSlidePlayerInstance(index or node) and getAllPlayers" 85 | - New: Access player instance from afterSlideChange and beforeSlideChange" 86 | - New: New Method removeSlide(1) remove slide at the specified index, it works even when the lightbox is open 87 | - New: insertSlide now works even when the lightbox is open 88 | - New: Added Accesibility features to slides 89 | - New: Enabled touchNavigation for all devices that support touch events and not only mobile devices 90 | - Changed: afterSlideLoad and beforeSlideLoad methods to follow the same variables as afterSlideChange, beforeSlideChange 91 | - Fixed: Calling `destroy()` throws an error when modal is not open 92 | - Fixed: Navigation not disabled correctly when only one slide 93 | 94 | ## 2.0.6 95 | 96 | - New: Now you can define width and height as 900px, 95%, 100vw, 100vh so you can have full screen content 97 | - New: Now you can define custom html or a node in the slide data to append it to the slide (view the API section) 98 | - New: Now you can use any attribute as selector for example '.glightbox' or 'data-glightbox' or '\*[data-glightbox]' 99 | - New: Method "openAt" you can open the lightbox at specific index eg: lightbox.openAt(2); 100 | - New: Method "insertSlide" that allows you to append a slide at specified index 101 | - Fixed Tab Key Doesn't Work on Form Within GLightbox Inline Content 102 | - Fixed Scrolling Description triggers closing the lightbox on touch devices 103 | - Fixed Page jumps depending on page scrollbar 104 | - Fixed Overriding default plyr settings does not merge correctly 105 | - Fixed fullscreen video button on ios 106 | - Moved plyr.ratio to plyr.config.ratio 107 | 108 | ## 2.0.5 109 | 110 | - New: Loop, renamed loopAtEnd to loop and now works in both directions 111 | - New: added touchFollowAxis, for mobile when dragging the media will follow the same axis, set to false to move media as you wish 112 | - New: added jpe format 113 | - Fixed .mov videos not recognized as videos 114 | 115 | ## 2.0.4 116 | 117 | - Fixed some errors when zooming and dragging images 118 | - Fixed description position not respected when configured globally 119 | - Fixed local videos not resized correctly when entered fullscreen 120 | 121 | ## 2.0.3 122 | 123 | - New: Zooming images. Now you can zoom images on desktop if image is too large 124 | - New: Now you can also define the slide description using the content of any div you want. 125 | - New: Replaced png icons with svg and added options to customize them. 126 | - Fixed responsive videos not resizing correctly when resizing the window vertically 127 | - Fixed responsive images not resizing correctly if they have description and the window height is lower that the slide height 128 | - Fixed youtube video not detected correctly for urls like youtube(-nocookie).com/embed/... 129 | 130 | ## 2.0.2 131 | 132 | - New: [Plyr player](https://plyr.io/), we have changed to this player so that way only one api is managed instead of 3 133 | - New: Added tabindex accesibility to loop the controls with the tab key 134 | - New: Inside inline content you can close the lightbox by adding the class **gtrigger-close** to any element 135 | - Fixed StartAt not taking specified index 136 | - Removed JWPlayer because that player implemented some restrictions unless you pay for a license 137 | - Improved mobile touch events, swipe, move, zoom, etc. 138 | - Changed: Youtube now by default uses youtube-nocookie.com, you can enable cookies in the config with youtube.nocookie to false 139 | - Removed option videosHeight. The height is automatic depending the video width and ratio. 140 | - Removed Gulp and replaced for pure nodejs scripts 141 | - Improved documentation 142 | 143 | ## 2.0.1 144 | 145 | - Fixed Mobile navigation 146 | - Fixed slide width for external sources 147 | 148 | ## 2.0.0 149 | 150 | - New: Delegated permissions to cross-origin iframes (for the new browsers autoplay restrictions) 151 | - Fixed youtube, vimeo autoplay when changing slides 152 | - Fixed lightbox won't fit screen with description 153 | - Fixed Removed global body variable that was causing some problems 154 | 155 | ## 1.0.9 156 | 157 | - Added svg to source types so it can be displayed as an image [@tuomassalo](https://github.com/mcstudios/glightbox/pull/40) 158 | - Added contributing file 159 | - Updated dependencies [@tuomassalo](https://github.com/mcstudios/glightbox/pull/40). 160 | - Removed demo folder from npm 161 | 162 | ## 1.0.8 163 | 164 | - New: You can define each slide option in a different data attribute (data-title="example" data-description="...") 165 | - Fixed youtube and vimeo autoplay when opened for the first time 166 | - Fixed global slide params not working 167 | - Fixed some issues on IE11 168 | - Fixed using characters : or ; in slide description 169 | 170 | ## 1.0.7 171 | 172 | - New: Added reload method, useful when injecting content with ajax, cloning nodes, etc. 173 | - Fixed closeButton setting not removing the element 174 | - Fixed video not displayed on mobile devices 175 | 176 | ## 1.0.6 177 | 178 | - New: Now you can set individual width and height for each slide with inlines or iframes. 179 | - New: Now you can set individual source types for each slide ('type': 'image' | 'iframe' | 'video' | 'inline' | 'external). 180 | - New: Published on npm 181 | - New: Published on bower 182 | - Fixed afterSlideLoad only triggered one time for all the slides 183 | - Fixed a small space between the image and description when is set to top or bottom 184 | 185 | ## 1.0.5 186 | 187 | - New: Added none as a new option to disable open, close and slide animations 188 | - New: Added new options (touchNavigation, keyboardNavigation, closeOnOutsideClick) to enable or disable user interaction 189 | - Fixed open and close effect not taking a custom animation 190 | - Fixed an error when calling the destroy method and no videos were present in the slider 191 | 192 | ## 1.0.4 193 | 194 | - Improved the open method so it can be called without duplicating the structure and events 195 | - New: The original node is passed to events like beforeSlideChange, afterSlideChange, etc. 196 | 197 | ## 1.0.3 198 | 199 | - New: Added option moreLength to control the number of characters in the description for mobile devices 200 | 201 | ## 1.0.2 202 | 203 | - Fixed instance not returned and unable to call public methods 204 | 205 | ## 1.0.1 206 | 207 | - Fixed large images not displayed correctly 208 | 209 | ## 1.0.0 210 | 211 | - Initial release 212 | -------------------------------------------------------------------------------- /dist/css/glightbox.min.css: -------------------------------------------------------------------------------- 1 | .glightbox-container{width:100%;height:100%;position:fixed;top:0;left:0;z-index:999999!important;overflow:hidden;-ms-touch-action:none;touch-action:none;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;outline:0;overflow:hidden}.glightbox-container.inactive{display:none}.glightbox-container .gcontainer{position:relative;width:100%;height:100%;z-index:9999;overflow:hidden}.glightbox-container .gslider{-webkit-transition:-webkit-transform .4s ease;transition:-webkit-transform .4s ease;transition:transform .4s ease;transition:transform .4s ease,-webkit-transform .4s ease;height:100%;left:0;top:0;width:100%;position:relative;overflow:hidden;display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.glightbox-container .gslide{width:100%;position:absolute;opacity:1;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;opacity:0}.glightbox-container .gslide.current{opacity:1;z-index:99999;position:relative}.glightbox-container .gslide.prev{opacity:1;z-index:9999}.glightbox-container .gslide-inner-content{width:100%}.glightbox-container .ginner-container{position:relative;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;max-width:100%;margin:auto;height:100vh}.glightbox-container .ginner-container.gvideo-container{width:100%}.glightbox-container .ginner-container.desc-bottom,.glightbox-container .ginner-container.desc-top{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.glightbox-container .ginner-container.desc-left,.glightbox-container .ginner-container.desc-right{max-width:100%!important}.gslide iframe,.gslide video{outline:0!important;border:none;min-height:165px;-webkit-overflow-scrolling:touch;-ms-touch-action:auto;touch-action:auto}.gslide-image{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.gslide-image img{max-height:100vh;display:block;padding:0;float:none;outline:0;border:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;max-width:100vw;width:auto;height:auto;-o-object-fit:cover;object-fit:cover;-ms-touch-action:none;touch-action:none;margin:auto;min-width:200px}.desc-bottom .gslide-image img,.desc-top .gslide-image img{width:auto}.desc-left .gslide-image img,.desc-right .gslide-image img{width:auto;max-width:100%}.gslide-image img.zoomable{position:relative}.gslide-image img.dragging{cursor:-webkit-grabbing!important;cursor:grabbing!important;-webkit-transition:none;transition:none}.gslide-video{position:relative;max-width:100vh;width:100%!important}.gslide-video .gvideo-wrapper{width:100%;margin:auto}.gslide-video::before{content:'';display:block;position:absolute;width:100%;height:100%;background:rgba(255,0,0,.34);display:none}.gslide-video.playing::before{display:none}.gslide-video.fullscreen{max-width:100%!important;min-width:100%;height:75vh}.gslide-video.fullscreen video{max-width:100%!important;width:100%!important}.gslide-inline{background:#fff;text-align:left;max-height:calc(100vh - 40px);overflow:auto;max-width:100%}.gslide-inline .ginlined-content{padding:20px;width:100%}.gslide-inline .dragging{cursor:-webkit-grabbing!important;cursor:grabbing!important;-webkit-transition:none;transition:none}.ginlined-content{overflow:auto;display:block!important;opacity:1}.gslide-external{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;min-width:100%;background:#fff;padding:0;overflow:auto;max-height:75vh;height:100%}.gslide-media{display:-webkit-box;display:-ms-flexbox;display:flex;width:auto}.zoomed .gslide-media{-webkit-box-shadow:none!important;box-shadow:none!important}.desc-bottom .gslide-media,.desc-top .gslide-media{margin:0 auto;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.gslide-description{position:relative;-webkit-box-flex:1;-ms-flex:1 0 100%;flex:1 0 100%}.gslide-description.description-left,.gslide-description.description-right{max-width:100%}.gslide-description.description-bottom,.gslide-description.description-top{margin:0 auto;width:100%}.gslide-description p{margin-bottom:12px}.gslide-description p:last-child{margin-bottom:0}.zoomed .gslide-description{display:none}.glightbox-button-hidden{display:none}.glightbox-mobile .glightbox-container .gslide-description{height:auto!important;width:100%;background:0 0;position:absolute;bottom:15px;padding:19px 11px;max-width:100vw!important;-webkit-box-ordinal-group:3!important;-ms-flex-order:2!important;order:2!important;max-height:78vh;overflow:auto!important;background:-webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,.75)));background:linear-gradient(to bottom,rgba(0,0,0,0) 0,rgba(0,0,0,.75) 100%);-webkit-transition:opacity .3s linear;transition:opacity .3s linear;padding-bottom:50px}.glightbox-mobile .glightbox-container .gslide-title{color:#fff;font-size:1em}.glightbox-mobile .glightbox-container .gslide-desc{color:#a1a1a1}.glightbox-mobile .glightbox-container .gslide-desc a{color:#fff;font-weight:700}.glightbox-mobile .glightbox-container .gslide-desc *{color:inherit}.glightbox-mobile .glightbox-container .gslide-desc string{color:#fff}.glightbox-mobile .glightbox-container .gslide-desc .desc-more{color:#fff;opacity:.4}.gdesc-open .gslide-media{-webkit-transition:opacity .5s ease;transition:opacity .5s ease;opacity:.4}.gdesc-open .gdesc-inner{padding-bottom:30px}.gdesc-closed .gslide-media{-webkit-transition:opacity .5s ease;transition:opacity .5s ease;opacity:1}.greset{-webkit-transition:all .3s ease;transition:all .3s ease}.gabsolute{position:absolute}.grelative{position:relative}.glightbox-desc{display:none!important}.glightbox-open{overflow:hidden}.gloader{height:25px;width:25px;-webkit-animation:lightboxLoader .8s infinite linear;animation:lightboxLoader .8s infinite linear;border:2px solid #fff;border-right-color:transparent;border-radius:50%;position:absolute;display:block;z-index:9999;left:0;right:0;margin:0 auto;top:47%}.goverlay{width:100%;height:calc(100vh + 1px);position:fixed;top:-1px;left:0;background:#000;will-change:opacity}.glightbox-mobile .goverlay{background:#000}.gclose,.gnext,.gprev{z-index:99999;cursor:pointer;width:26px;height:44px;border:none;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.gclose svg,.gnext svg,.gprev svg{display:block;width:25px;height:auto;margin:0;padding:0}.gclose.disabled,.gnext.disabled,.gprev.disabled{opacity:.1}.gclose .garrow,.gnext .garrow,.gprev .garrow{stroke:#fff}.gbtn.focused{outline:2px solid #0f3d81}iframe.wait-autoplay{opacity:0}.glightbox-closing .gclose,.glightbox-closing .gnext,.glightbox-closing .gprev{opacity:0!important}.glightbox-clean .gslide-description{background:#fff}.glightbox-clean .gdesc-inner{padding:22px 20px}.glightbox-clean .gslide-title{font-size:1em;font-weight:400;font-family:arial;color:#000;margin-bottom:19px;line-height:1.4em}.glightbox-clean .gslide-desc{font-size:.86em;margin-bottom:0;font-family:arial;line-height:1.4em}.glightbox-clean .gslide-video{background:#000}.glightbox-clean .gclose,.glightbox-clean .gnext,.glightbox-clean .gprev{background-color:rgba(0,0,0,.75);border-radius:4px}.glightbox-clean .gclose path,.glightbox-clean .gnext path,.glightbox-clean .gprev path{fill:#fff}.glightbox-clean .gprev{position:absolute;top:-100%;left:30px;width:40px;height:50px}.glightbox-clean .gnext{position:absolute;top:-100%;right:30px;width:40px;height:50px}.glightbox-clean .gclose{width:35px;height:35px;top:15px;right:10px;position:absolute}.glightbox-clean .gclose svg{width:18px;height:auto}.glightbox-clean .gclose:hover{opacity:1}.gfadeIn{-webkit-animation:gfadeIn .5s ease;animation:gfadeIn .5s ease}.gfadeOut{-webkit-animation:gfadeOut .5s ease;animation:gfadeOut .5s ease}.gslideOutLeft{-webkit-animation:gslideOutLeft .3s ease;animation:gslideOutLeft .3s ease}.gslideInLeft{-webkit-animation:gslideInLeft .3s ease;animation:gslideInLeft .3s ease}.gslideOutRight{-webkit-animation:gslideOutRight .3s ease;animation:gslideOutRight .3s ease}.gslideInRight{-webkit-animation:gslideInRight .3s ease;animation:gslideInRight .3s ease}.gzoomIn{-webkit-animation:gzoomIn .5s ease;animation:gzoomIn .5s ease}.gzoomOut{-webkit-animation:gzoomOut .5s ease;animation:gzoomOut .5s ease}@-webkit-keyframes lightboxLoader{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes lightboxLoader{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes gfadeIn{from{opacity:0}to{opacity:1}}@keyframes gfadeIn{from{opacity:0}to{opacity:1}}@-webkit-keyframes gfadeOut{from{opacity:1}to{opacity:0}}@keyframes gfadeOut{from{opacity:1}to{opacity:0}}@-webkit-keyframes gslideInLeft{from{opacity:0;-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0)}to{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes gslideInLeft{from{opacity:0;-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0)}to{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes gslideOutLeft{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0);opacity:0;visibility:hidden}}@keyframes gslideOutLeft{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0);opacity:0;visibility:hidden}}@-webkit-keyframes gslideInRight{from{opacity:0;visibility:visible;-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes gslideInRight{from{opacity:0;visibility:visible;-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes gslideOutRight{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0);opacity:0}}@keyframes gslideOutRight{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0);opacity:0}}@-webkit-keyframes gzoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:1}}@keyframes gzoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:1}}@-webkit-keyframes gzoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}@keyframes gzoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}@media (min-width:769px){.glightbox-container .ginner-container{width:auto;height:auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.glightbox-container .ginner-container.desc-top .gslide-description{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.glightbox-container .ginner-container.desc-top .gslide-image,.glightbox-container .ginner-container.desc-top .gslide-image img{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.glightbox-container .ginner-container.desc-left .gslide-description{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.glightbox-container .ginner-container.desc-left .gslide-image{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.gslide-image img{max-height:97vh;max-width:100%}.gslide-image img.zoomable{cursor:-webkit-zoom-in;cursor:zoom-in}.zoomed .gslide-image img.zoomable{cursor:-webkit-grab;cursor:grab}.gslide-inline{max-height:95vh}.gslide-external{max-height:100vh}.gslide-description.description-left,.gslide-description.description-right{max-width:275px}.glightbox-open{height:auto}.goverlay{background:rgba(0,0,0,.92)}.glightbox-clean .gslide-media{-webkit-box-shadow:1px 2px 9px 0 rgba(0,0,0,.65);box-shadow:1px 2px 9px 0 rgba(0,0,0,.65)}.glightbox-clean .description-left .gdesc-inner,.glightbox-clean .description-right .gdesc-inner{position:absolute;height:100%;overflow-y:auto}.glightbox-clean .gclose,.glightbox-clean .gnext,.glightbox-clean .gprev{background-color:rgba(0,0,0,.32)}.glightbox-clean .gclose:hover,.glightbox-clean .gnext:hover,.glightbox-clean .gprev:hover{background-color:rgba(0,0,0,.7)}.glightbox-clean .gprev{top:45%}.glightbox-clean .gnext{top:45%}}@media (min-width:992px){.glightbox-clean .gclose{opacity:.7;right:20px}}@media screen and (max-height:420px){.goverlay{background:#000}} -------------------------------------------------------------------------------- /src/js/core/touch-events.js: -------------------------------------------------------------------------------- 1 | function getLen(v) { 2 | return Math.sqrt(v.x * v.x + v.y * v.y); 3 | } 4 | 5 | function dot(v1, v2) { 6 | return v1.x * v2.x + v1.y * v2.y; 7 | } 8 | 9 | function getAngle(v1, v2) { 10 | var mr = getLen(v1) * getLen(v2); 11 | if (mr === 0) { 12 | return 0; 13 | } 14 | var r = dot(v1, v2) / mr; 15 | if (r > 1) { 16 | r = 1; 17 | } 18 | return Math.acos(r); 19 | } 20 | 21 | function cross(v1, v2) { 22 | return v1.x * v2.y - v2.x * v1.y; 23 | } 24 | 25 | function getRotateAngle(v1, v2) { 26 | var angle = getAngle(v1, v2); 27 | if (cross(v1, v2) > 0) { 28 | angle *= -1; 29 | } 30 | 31 | return (angle * 180) / Math.PI; 32 | } 33 | 34 | class EventsHandlerAdmin { 35 | constructor(el) { 36 | this.handlers = []; 37 | this.el = el; 38 | } 39 | add(handler) { 40 | this.handlers.push(handler); 41 | } 42 | del(handler) { 43 | if (!handler) { 44 | this.handlers = []; 45 | } 46 | 47 | for (var i = this.handlers.length; i >= 0; i--) { 48 | if (this.handlers[i] === handler) { 49 | this.handlers.splice(i, 1); 50 | } 51 | } 52 | } 53 | dispatch() { 54 | for (var i = 0, len = this.handlers.length; i < len; i++) { 55 | var handler = this.handlers[i]; 56 | if (typeof handler === 'function') { 57 | handler.apply(this.el, arguments); 58 | } 59 | } 60 | } 61 | } 62 | 63 | function wrapFunc(el, handler) { 64 | var EventshandlerAdmin = new EventsHandlerAdmin(el); 65 | EventshandlerAdmin.add(handler); 66 | 67 | return EventshandlerAdmin; 68 | } 69 | 70 | // Modified version of AlloyFinger 71 | export default class TouchEvents { 72 | constructor(el, option) { 73 | this.element = typeof el == 'string' ? document.querySelector(el) : el; 74 | 75 | this.start = this.start.bind(this); 76 | this.move = this.move.bind(this); 77 | this.end = this.end.bind(this); 78 | this.cancel = this.cancel.bind(this); 79 | this.element.addEventListener('touchstart', this.start, false); 80 | this.element.addEventListener('touchmove', this.move, false); 81 | this.element.addEventListener('touchend', this.end, false); 82 | this.element.addEventListener('touchcancel', this.cancel, false); 83 | 84 | this.preV = { x: null, y: null }; 85 | this.pinchStartLen = null; 86 | this.zoom = 1; 87 | this.isDoubleTap = false; 88 | 89 | var noop = function () {}; 90 | 91 | this.rotate = wrapFunc(this.element, option.rotate || noop); 92 | this.touchStart = wrapFunc(this.element, option.touchStart || noop); 93 | this.multipointStart = wrapFunc(this.element, option.multipointStart || noop); 94 | this.multipointEnd = wrapFunc(this.element, option.multipointEnd || noop); 95 | this.pinch = wrapFunc(this.element, option.pinch || noop); 96 | this.swipe = wrapFunc(this.element, option.swipe || noop); 97 | this.tap = wrapFunc(this.element, option.tap || noop); 98 | this.doubleTap = wrapFunc(this.element, option.doubleTap || noop); 99 | this.longTap = wrapFunc(this.element, option.longTap || noop); 100 | this.singleTap = wrapFunc(this.element, option.singleTap || noop); 101 | this.pressMove = wrapFunc(this.element, option.pressMove || noop); 102 | this.twoFingerPressMove = wrapFunc(this.element, option.twoFingerPressMove || noop); 103 | this.touchMove = wrapFunc(this.element, option.touchMove || noop); 104 | this.touchEnd = wrapFunc(this.element, option.touchEnd || noop); 105 | this.touchCancel = wrapFunc(this.element, option.touchCancel || noop); 106 | this.translateContainer = this.element; 107 | 108 | this._cancelAllHandler = this.cancelAll.bind(this); 109 | 110 | window.addEventListener('scroll', this._cancelAllHandler); 111 | 112 | this.delta = null; 113 | this.last = null; 114 | this.now = null; 115 | this.tapTimeout = null; 116 | this.singleTapTimeout = null; 117 | this.longTapTimeout = null; 118 | this.swipeTimeout = null; 119 | this.x1 = this.x2 = this.y1 = this.y2 = null; 120 | this.preTapPosition = { x: null, y: null }; 121 | } 122 | start(evt) { 123 | if (!evt.touches) { 124 | return; 125 | } 126 | 127 | // Fix Media Buttons Not responding on Android #233 128 | const ignoreDragFor = ['a', 'button', 'input']; 129 | if (evt.target && evt.target.nodeName && ignoreDragFor.indexOf(evt.target.nodeName.toLowerCase()) >= 0) { 130 | console.log('ignore drag for this touched element', evt.target.nodeName.toLowerCase()); 131 | return; 132 | } 133 | 134 | this.now = Date.now(); 135 | this.x1 = evt.touches[0].pageX; 136 | this.y1 = evt.touches[0].pageY; 137 | this.delta = this.now - (this.last || this.now); 138 | this.touchStart.dispatch(evt, this.element); 139 | if (this.preTapPosition.x !== null) { 140 | this.isDoubleTap = this.delta > 0 && this.delta <= 250 && Math.abs(this.preTapPosition.x - this.x1) < 30 && Math.abs(this.preTapPosition.y - this.y1) < 30; 141 | if (this.isDoubleTap) { 142 | clearTimeout(this.singleTapTimeout); 143 | } 144 | } 145 | this.preTapPosition.x = this.x1; 146 | this.preTapPosition.y = this.y1; 147 | this.last = this.now; 148 | var preV = this.preV, 149 | len = evt.touches.length; 150 | if (len > 1) { 151 | this._cancelLongTap(); 152 | this._cancelSingleTap(); 153 | var v = { x: evt.touches[1].pageX - this.x1, y: evt.touches[1].pageY - this.y1 }; 154 | preV.x = v.x; 155 | preV.y = v.y; 156 | this.pinchStartLen = getLen(preV); 157 | this.multipointStart.dispatch(evt, this.element); 158 | } 159 | this._preventTap = false; 160 | this.longTapTimeout = setTimeout( 161 | function () { 162 | this.longTap.dispatch(evt, this.element); 163 | this._preventTap = true; 164 | }.bind(this), 165 | 750 166 | ); 167 | } 168 | move(evt) { 169 | if (!evt.touches) { 170 | return; 171 | } 172 | var preV = this.preV, 173 | len = evt.touches.length, 174 | currentX = evt.touches[0].pageX, 175 | currentY = evt.touches[0].pageY; 176 | this.isDoubleTap = false; 177 | if (len > 1) { 178 | var sCurrentX = evt.touches[1].pageX, 179 | sCurrentY = evt.touches[1].pageY; 180 | var v = { x: evt.touches[1].pageX - currentX, y: evt.touches[1].pageY - currentY }; 181 | 182 | if (preV.x !== null) { 183 | if (this.pinchStartLen > 0) { 184 | evt.zoom = getLen(v) / this.pinchStartLen; 185 | this.pinch.dispatch(evt, this.element); 186 | } 187 | 188 | evt.angle = getRotateAngle(v, preV); 189 | this.rotate.dispatch(evt, this.element); 190 | } 191 | preV.x = v.x; 192 | preV.y = v.y; 193 | 194 | if (this.x2 !== null && this.sx2 !== null) { 195 | evt.deltaX = (currentX - this.x2 + sCurrentX - this.sx2) / 2; 196 | evt.deltaY = (currentY - this.y2 + sCurrentY - this.sy2) / 2; 197 | } else { 198 | evt.deltaX = 0; 199 | evt.deltaY = 0; 200 | } 201 | this.twoFingerPressMove.dispatch(evt, this.element); 202 | 203 | this.sx2 = sCurrentX; 204 | this.sy2 = sCurrentY; 205 | } else { 206 | if (this.x2 !== null) { 207 | evt.deltaX = currentX - this.x2; 208 | evt.deltaY = currentY - this.y2; 209 | 210 | var movedX = Math.abs(this.x1 - this.x2), 211 | movedY = Math.abs(this.y1 - this.y2); 212 | 213 | if (movedX > 10 || movedY > 10) { 214 | this._preventTap = true; 215 | } 216 | } else { 217 | evt.deltaX = 0; 218 | evt.deltaY = 0; 219 | } 220 | this.pressMove.dispatch(evt, this.element); 221 | } 222 | 223 | this.touchMove.dispatch(evt, this.element); 224 | 225 | this._cancelLongTap(); 226 | this.x2 = currentX; 227 | this.y2 = currentY; 228 | 229 | if (len > 1) { 230 | evt.preventDefault(); 231 | } 232 | } 233 | end(evt) { 234 | if (!evt.changedTouches) { 235 | return; 236 | } 237 | this._cancelLongTap(); 238 | var self = this; 239 | if (evt.touches.length < 2) { 240 | this.multipointEnd.dispatch(evt, this.element); 241 | this.sx2 = this.sy2 = null; 242 | } 243 | 244 | //swipe 245 | if ((this.x2 && Math.abs(this.x1 - this.x2) > 30) || (this.y2 && Math.abs(this.y1 - this.y2) > 30)) { 246 | evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2); 247 | this.swipeTimeout = setTimeout(function () { 248 | self.swipe.dispatch(evt, self.element); 249 | }, 0); 250 | } else { 251 | this.tapTimeout = setTimeout(function () { 252 | if (!self._preventTap) { 253 | self.tap.dispatch(evt, self.element); 254 | } 255 | // trigger double tap immediately 256 | if (self.isDoubleTap) { 257 | self.doubleTap.dispatch(evt, self.element); 258 | self.isDoubleTap = false; 259 | } 260 | }, 0); 261 | 262 | if (!self.isDoubleTap) { 263 | self.singleTapTimeout = setTimeout(function () { 264 | self.singleTap.dispatch(evt, self.element); 265 | }, 250); 266 | } 267 | } 268 | 269 | this.touchEnd.dispatch(evt, this.element); 270 | 271 | this.preV.x = 0; 272 | this.preV.y = 0; 273 | this.zoom = 1; 274 | this.pinchStartLen = null; 275 | this.x1 = this.x2 = this.y1 = this.y2 = null; 276 | } 277 | cancelAll() { 278 | this._preventTap = true; 279 | clearTimeout(this.singleTapTimeout); 280 | clearTimeout(this.tapTimeout); 281 | clearTimeout(this.longTapTimeout); 282 | clearTimeout(this.swipeTimeout); 283 | } 284 | cancel(evt) { 285 | this.cancelAll(); 286 | this.touchCancel.dispatch(evt, this.element); 287 | } 288 | _cancelLongTap() { 289 | clearTimeout(this.longTapTimeout); 290 | } 291 | _cancelSingleTap() { 292 | clearTimeout(this.singleTapTimeout); 293 | } 294 | _swipeDirection(x1, x2, y1, y2) { 295 | return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : y1 - y2 > 0 ? 'Up' : 'Down'; 296 | } 297 | on(evt, handler) { 298 | if (this[evt]) { 299 | this[evt].add(handler); 300 | } 301 | } 302 | off(evt, handler) { 303 | if (this[evt]) { 304 | this[evt].del(handler); 305 | } 306 | } 307 | destroy() { 308 | if (this.singleTapTimeout) { 309 | clearTimeout(this.singleTapTimeout); 310 | } 311 | if (this.tapTimeout) { 312 | clearTimeout(this.tapTimeout); 313 | } 314 | if (this.longTapTimeout) { 315 | clearTimeout(this.longTapTimeout); 316 | } 317 | if (this.swipeTimeout) { 318 | clearTimeout(this.swipeTimeout); 319 | } 320 | 321 | this.element.removeEventListener('touchstart', this.start); 322 | this.element.removeEventListener('touchmove', this.move); 323 | this.element.removeEventListener('touchend', this.end); 324 | this.element.removeEventListener('touchcancel', this.cancel); 325 | 326 | this.rotate.del(); 327 | this.touchStart.del(); 328 | this.multipointStart.del(); 329 | this.multipointEnd.del(); 330 | this.pinch.del(); 331 | this.swipe.del(); 332 | this.tap.del(); 333 | this.doubleTap.del(); 334 | this.longTap.del(); 335 | this.singleTap.del(); 336 | this.pressMove.del(); 337 | this.twoFingerPressMove.del(); 338 | this.touchMove.del(); 339 | this.touchEnd.del(); 340 | this.touchCancel.del(); 341 | 342 | this.preV = 343 | this.pinchStartLen = 344 | this.zoom = 345 | this.isDoubleTap = 346 | this.delta = 347 | this.last = 348 | this.now = 349 | this.tapTimeout = 350 | this.singleTapTimeout = 351 | this.longTapTimeout = 352 | this.swipeTimeout = 353 | this.x1 = 354 | this.x2 = 355 | this.y1 = 356 | this.y2 = 357 | this.preTapPosition = 358 | this.rotate = 359 | this.touchStart = 360 | this.multipointStart = 361 | this.multipointEnd = 362 | this.pinch = 363 | this.swipe = 364 | this.tap = 365 | this.doubleTap = 366 | this.longTap = 367 | this.singleTap = 368 | this.pressMove = 369 | this.touchMove = 370 | this.touchEnd = 371 | this.touchCancel = 372 | this.twoFingerPressMove = 373 | null; 374 | 375 | window.removeEventListener('scroll', this._cancelAllHandler); 376 | return null; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /demo/css/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Gilroy'; 3 | src: local('Gilroy Regular'), local('Gilroy-Regular'), url('./fonts/Gilroy-Regular.woff2') format('woff2'), url('./fonts/Gilroy-Regular.woff') format('woff'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Gilroy'; 10 | src: local('Gilroy Regular Italic'), local('Gilroy-RegularItalic'), url('./fonts/Gilroy-RegularItalic.woff2') format('woff2'), url('./fonts/Gilroy-RegularItalic.woff') format('woff'); 11 | font-weight: normal; 12 | font-style: italic; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Gilroy'; 17 | src: local('Gilroy SemiBold'), local('Gilroy-SemiBold'), url('./fonts/Gilroy-SemiBold.woff2') format('woff2'), url('./fonts/Gilroy-SemiBold.woff') format('woff'); 18 | font-weight: 600; 19 | font-style: normal; 20 | } 21 | 22 | @font-face { 23 | font-family: 'Gilroy'; 24 | src: local('Gilroy Bold'), local('Gilroy-Bold'), url('./fonts/Gilroy-Bold.woff2') format('woff2'), url('./fonts/Gilroy-Bold.woff') format('woff'); 25 | font-weight: bold; 26 | font-style: normal; 27 | } 28 | 29 | 30 | html, 31 | body, 32 | div, 33 | span, 34 | applet, 35 | object, 36 | iframe, 37 | h1, 38 | h2, 39 | h3, 40 | h4, 41 | h5, 42 | h6, 43 | p, 44 | blockquote, 45 | pre, 46 | a, 47 | abbr, 48 | acronym, 49 | address, 50 | big, 51 | cite, 52 | code, 53 | del, 54 | dfn, 55 | em, 56 | img, 57 | ins, 58 | kbd, 59 | q, 60 | s, 61 | samp, 62 | small, 63 | strike, 64 | strong, 65 | sub, 66 | sup, 67 | tt, 68 | var, 69 | b, 70 | u, 71 | i, 72 | center, 73 | dl, 74 | dt, 75 | dd, 76 | ol, 77 | ul, 78 | li, 79 | fieldset, 80 | form, 81 | label, 82 | legend, 83 | table, 84 | caption, 85 | tbody, 86 | tfoot, 87 | thead, 88 | tr, 89 | th, 90 | td, 91 | article, 92 | aside, 93 | canvas, 94 | details, 95 | embed, 96 | figure, 97 | figcaption, 98 | footer, 99 | header, 100 | hgroup, 101 | menu, 102 | nav, 103 | output, 104 | ruby, 105 | section, 106 | summary, 107 | time, 108 | mark, 109 | audio, 110 | video { 111 | margin: 0; 112 | padding: 0; 113 | border: 0; 114 | font-size: 100%; 115 | font: inherit; 116 | vertical-align: baseline; 117 | } 118 | 119 | /* HTML5 display-role reset for older browsers */ 120 | article, 121 | aside, 122 | details, 123 | figcaption, 124 | figure, 125 | footer, 126 | header, 127 | hgroup, 128 | menu, 129 | nav, 130 | section { 131 | display: block; 132 | } 133 | 134 | body { 135 | line-height: 1; 136 | } 137 | 138 | ol, 139 | ul { 140 | list-style: none; 141 | } 142 | 143 | li { 144 | margin-bottom: 10px; 145 | } 146 | 147 | .section { 148 | padding: 95px 0; 149 | } 150 | 151 | body { 152 | font-family: 'Gilroy', sans-serif; 153 | background: #fff; 154 | color: #666; 155 | font-size: 16px; 156 | font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, '.SFNSText-Regular', sans-serif !important; 157 | } 158 | 159 | a, 160 | a:hover { 161 | text-decoration: none; 162 | color: #000; 163 | } 164 | 165 | .text-center { 166 | text-align: center; 167 | } 168 | 169 | .pair { 170 | background: #f8f8f8; 171 | } 172 | 173 | @media (min-width: 1200px) { 174 | .container { 175 | max-width: 1040px; 176 | } 177 | } 178 | 179 | header { 180 | position: absolute; 181 | width: 100%; 182 | top: 0; 183 | left: 0; 184 | border-bottom: 1px solid transparent; 185 | z-index: 999; 186 | transition: all 0.3s ease-in-out; 187 | } 188 | 189 | header.fixed { 190 | background: #fff; 191 | border-bottom: 1px solid #cecece; 192 | } 193 | 194 | .header-unpin { 195 | position: fixed; 196 | opacity: 0; 197 | transform: translate3d(0, -100%, 0); 198 | transition: all 0.3s ease-in-out; 199 | } 200 | 201 | .header-pin { 202 | position: fixed; 203 | background: #fff; 204 | opacity: 1; 205 | border-bottom: 1px solid #cecece; 206 | transform: translate3d(0, 0%, 0); 207 | transition: all 0.6s ease-in-out; 208 | } 209 | 210 | .header-inner { 211 | padding: 25px 0; 212 | overflow: hidden; 213 | } 214 | 215 | .logo { 216 | font-family: 'Lobster', cursive; 217 | color: #000; 218 | float: left; 219 | font-size: 1.6em; 220 | } 221 | 222 | header ul { 223 | float: right; 224 | position: relative; 225 | top: 4px; 226 | } 227 | 228 | header ul li { 229 | float: left; 230 | list-style: none; 231 | margin-left: 25px; 232 | opacity: 1; 233 | transition: opacity 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275); 234 | } 235 | 236 | header ul li a { 237 | color: #2b2b2b; 238 | font-weight: 600; 239 | font-size: 0.8em; 240 | } 241 | 242 | header ul:hover li { 243 | opacity: 0.7; 244 | transition: opacity 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275); 245 | } 246 | header ul li:hover { 247 | opacity: 1; 248 | transition: opacity 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); 249 | } 250 | header ul li:hover a { 251 | color: #000000; 252 | } 253 | 254 | 255 | h3 { 256 | font-family: 'Gilroy', sans-serif; 257 | font-size: 1.7em; 258 | border-bottom: 1px solid #eee; 259 | padding-bottom: 25px; 260 | margin-bottom: 30px; 261 | font-weight: 600; 262 | color: #000; 263 | display: flex; 264 | } 265 | 266 | h4 { 267 | font-family: 'Gilroy', sans-serif; 268 | margin-bottom: 25px; 269 | font-weight: 600; 270 | color: #000; 271 | font-size: 1.2em; 272 | } 273 | 274 | a { 275 | color: #000; 276 | } 277 | 278 | p { 279 | line-height: 1.7em; 280 | margin-bottom: 2em; 281 | color: #2b2b2b; 282 | } 283 | 284 | .mcbutton { 285 | padding: 11px 45px; 286 | margin-top: 20px; 287 | display: inline-block; 288 | border-radius: 4px; 289 | margin-right: 10px; 290 | color: #000; 291 | border: 2px solid #000; 292 | font-weight: 600; 293 | font-weight: 700; 294 | text-transform: uppercase; 295 | font-size: .7em; 296 | position: relative; 297 | width: 100%; 298 | text-align: center; 299 | } 300 | 301 | .mcbutton::before, 302 | .mcbutton::after { 303 | content: ''; 304 | display: block; 305 | position: absolute; 306 | width: 0; 307 | height: 2px; 308 | background: #000; 309 | transition: all 0.2s ease-in-out; 310 | z-index: 99; 311 | } 312 | 313 | .mcbutton::before { 314 | left: 0; 315 | top: 0; 316 | } 317 | 318 | .mcbutton::after { 319 | right: 0; 320 | bottom: 0; 321 | } 322 | 323 | .mcbutton:hover::before, 324 | .mcbutton:hover::after { 325 | width: 100%; 326 | transition: all 0.4s ease-in-out; 327 | } 328 | 329 | .mcbutton span::before, 330 | .mcbutton span::after { 331 | content: ''; 332 | display: block; 333 | position: absolute; 334 | width: 2px; 335 | height: 0; 336 | background: #000; 337 | transition: all 0.3s ease-in-out; 338 | } 339 | 340 | .mcbutton span::before { 341 | left: 0; 342 | top: 0; 343 | } 344 | 345 | .mcbutton span::after { 346 | right: 0; 347 | bottom: 0; 348 | } 349 | 350 | .mcbutton:hover span::before, 351 | .mcbutton:hover span::after { 352 | height: 100%; 353 | transition: all 0.3s ease-in-out; 354 | } 355 | 356 | .mcbutton.primary { 357 | background: #FFC107; 358 | } 359 | 360 | .mcbutton.black { 361 | background: #1b1b1b; 362 | color: #fff; 363 | } 364 | 365 | .lead .mcbutton.primary { 366 | border-color: #a2a2a2; 367 | } 368 | 369 | .lead .mcbutton { 370 | padding: 16px 45px; 371 | font-size: 0.6em; 372 | } 373 | 374 | .mcbutton i { 375 | color: inherit; 376 | font-size: 1.5em; 377 | margin-right: 10px; 378 | } 379 | 380 | a { 381 | text-decoration: none; 382 | } 383 | 384 | ol { 385 | margin-left: 2em; 386 | margin-bottom: 2em; 387 | } 388 | 389 | ul { 390 | margin-left: 1em; 391 | margin-bottom: 0; 392 | } 393 | 394 | ul ul { 395 | margin-left: 1em; 396 | } 397 | 398 | strong { 399 | font-weight: 600; 400 | color: #1f1f1f; 401 | } 402 | 403 | .intro { 404 | width: 100%; 405 | margin: 0 auto; 406 | background: url(../img/head.jpg); 407 | background-size: cover; 408 | margin-bottom: 0px; 409 | height: 100vh; 410 | width: 100%; 411 | display: flex; 412 | align-items: center; 413 | padding-top: 80px; 414 | min-height: 579px; 415 | max-height: 579px; 416 | } 417 | 418 | .intro h1 { 419 | margin-top: 0; 420 | font-size: 3.3em; 421 | color: #000; 422 | font-weight: 100; 423 | margin-bottom: 35px; 424 | text-align: center; 425 | } 426 | 427 | .tagline { 428 | color: #3a3a3a; 429 | margin-bottom: 30px; 430 | font-weight: 600; 431 | text-align: center; 432 | font-size: 17px; 433 | } 434 | 435 | .lead { 436 | margin-bottom: 2em; 437 | } 438 | 439 | .lead .mcbutton { 440 | border-color: #595959; 441 | cursor: pointer; 442 | } 443 | 444 | .heading-icon { 445 | margin-right: 16px 446 | } 447 | 448 | .box-container { 449 | display: inline-block; 450 | margin: 0; 451 | padding: 0; 452 | margin-top: 1.4em; 453 | width: 100%; 454 | } 455 | 456 | .box { 457 | list-style-type: none; 458 | float: left; 459 | opacity: 0; 460 | transform: translate3d(0, 40px, 0); 461 | transition: opacity 0.2s, transform 0.35s; 462 | } 463 | 464 | .box.show { 465 | opacity: 1; 466 | transform: translate3d(0, 0, 0); 467 | transition: opacity 0.4s, transform 0.35s; 468 | } 469 | 470 | .box .inner { 471 | padding: 10px; 472 | position: relative; 473 | } 474 | 475 | .box a { 476 | display: block; 477 | width: 100%; 478 | height: auto; 479 | position: relative; 480 | overflow: hidden; 481 | } 482 | 483 | .box img { 484 | width: calc(100% + 50px); 485 | max-width: calc(100% + 50px); 486 | transition: opacity 0.35s, transform 0.35s; 487 | transform: translate3d(-40px, 0, 0); 488 | } 489 | 490 | .box a::before { 491 | content: ''; 492 | display: block; 493 | width: 100%; 494 | height: 100%; 495 | position: absolute; 496 | top: 0; 497 | left: 0; 498 | background: #000; 499 | z-index: 99; 500 | opacity: 0; 501 | transition: opacity 0.4s; 502 | } 503 | 504 | .three-cols .box { 505 | width: 100%; 506 | } 507 | 508 | .four-cols .box { 509 | width: 23.1%; 510 | } 511 | 512 | .four-cols .box:nth-child(4n+0) { 513 | margin-right: 0; 514 | } 515 | 516 | .four-cols .box:nth-child(4n+1) { 517 | clear: both; 518 | margin-left: 0; 519 | } 520 | 521 | .options-list { 522 | width: 700px; 523 | margin: 0 auto; 524 | border: 1px solid #ececec; 525 | border-radius: 2px; 526 | } 527 | 528 | .options-list .option { 529 | overflow: hidden; 530 | border-bottom: 1px solid #ececec; 531 | } 532 | 533 | .options-list .option .name { 534 | font-weight: 600; 535 | display: inline-block; 536 | width: 23%; 537 | float: left; 538 | text-align: left; 539 | padding: 14px 17px; 540 | color: #000; 541 | font-size: 0.89em; 542 | font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, '.SFNSText-Regular', sans-serif !important; 543 | } 544 | 545 | .options-list .option .value { 546 | display: inline-block; 547 | width: 65%; 548 | float: left; 549 | padding: 14px 17px; 550 | text-align: left; 551 | line-height: 1.7em; 552 | font-size: 0.89em; 553 | border-left: 1px solid #eee; 554 | font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, '.SFNSText-Regular', sans-serif !important; 555 | } 556 | 557 | .options-list .option .type { 558 | color: #ed7205; 559 | font-weight: 600; 560 | } 561 | 562 | .params-list { 563 | margin-bottom: 11em; 564 | } 565 | 566 | .option-code { 567 | width: 700px; 568 | margin: 0 auto; 569 | border: 1px solid #ececec; 570 | border-radius: 2px; 571 | padding-left: 20px; 572 | padding-top: 0; 573 | margin-bottom: 48px; 574 | } 575 | 576 | .inline-inner { 577 | padding: 40px; 578 | } 579 | 580 | .inline-close-btn { 581 | background: #FFC107; 582 | width: 200px; 583 | display: block; 584 | margin: auto; 585 | height: 53px; 586 | text-align: center; 587 | line-height: 53px; 588 | font-weight: 600; 589 | font-size: 15px; 590 | border-radius: 5px; 591 | border: 2px solid #eee; 592 | } 593 | 594 | .inline-close-btn:hover { 595 | border-color: #000; 596 | } 597 | 598 | .especifications { 599 | width: 100%; 600 | max-width: 543px; 601 | margin: auto; 602 | } 603 | .especifications ul { 604 | margin-top: 40px; 605 | margin-bottom: 40px; 606 | } 607 | .especifications li { 608 | text-align: left; 609 | list-style: initial; 610 | margin-bottom: 15px; 611 | font-weight: 600; 612 | font-size: 16px; 613 | color: #2b2b2b; 614 | opacity: 0; 615 | line-height: 1.7em; 616 | transform: translate3d(-10px, 0, 0); 617 | } 618 | 619 | .especifications li.show { 620 | opacity: 1; 621 | transform: translate3d(0, 0, 0); 622 | transition: all 0.3s ease-in-out; 623 | } 624 | 625 | .especifications li i { 626 | color: #000; 627 | } 628 | 629 | 630 | .footer-btns { 631 | margin-top: 50px; 632 | } 633 | 634 | footer { 635 | text-align: center; 636 | color: #666; 637 | margin: 2rem 0; 638 | } 639 | 640 | footer a { 641 | color: #000; 642 | } 643 | 644 | .copyright { 645 | font-size: 0.8em; 646 | } 647 | 648 | 649 | @media (min-width: 390px) { 650 | .three-cols .box { 651 | width: 50%; 652 | } 653 | } 654 | 655 | 656 | @media (min-width: 576px) { 657 | .intro h1 { 658 | font-size: 4.3em; 659 | } 660 | 661 | .intro { 662 | max-height: 70vh; 663 | min-height: 640px; 664 | padding-top: 0px; 665 | } 666 | 667 | .intro h1, 668 | .tagline { 669 | text-align: left; 670 | } 671 | 672 | .tagline { 673 | font-size: 100%; 674 | } 675 | 676 | .three-cols .box { 677 | width: 33.3%; 678 | } 679 | 680 | .three-cols .box:nth-child(3n+0) { 681 | margin-right: 0; 682 | } 683 | 684 | .three-cols .box:nth-child(3n+1) { 685 | margin-left: 0; 686 | } 687 | 688 | .mcbutton { 689 | width: auto; 690 | } 691 | } 692 | 693 | 694 | @media (min-width: 992px) { 695 | .box:hover img { 696 | transform: translate3d(0, 0, 0); 697 | transition: opacity 0.35s, transform 0.35s; 698 | } 699 | 700 | .box:hover a::before { 701 | opacity: 0.3; 702 | transition: opacity 0.5s; 703 | } 704 | } 705 | 706 | 707 | /* path { 708 | stroke-opacity: 0; 709 | } 710 | 711 | path.hw { 712 | stroke-opacity: 1; 713 | } 714 | */ -------------------------------------------------------------------------------- /src/js/utils/helpers.js: -------------------------------------------------------------------------------- 1 | const uid = Date.now(); 2 | 3 | /** 4 | * Merge two or more objects 5 | */ 6 | export function extend() { 7 | let extended = {}; 8 | let deep = true; 9 | let i = 0; 10 | let length = arguments.length; 11 | if (Object.prototype.toString.call(arguments[0]) === '[object Boolean]') { 12 | deep = arguments[0]; 13 | i++; 14 | } 15 | let merge = (obj) => { 16 | for (let prop in obj) { 17 | if (Object.prototype.hasOwnProperty.call(obj, prop)) { 18 | if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') { 19 | extended[prop] = extend(true, extended[prop], obj[prop]); 20 | } else { 21 | extended[prop] = obj[prop]; 22 | } 23 | } 24 | } 25 | }; 26 | for (; i < length; i++) { 27 | let obj = arguments[i]; 28 | merge(obj); 29 | } 30 | return extended; 31 | } 32 | 33 | 34 | /** 35 | * Each 36 | * 37 | * @param {mixed} node list, array, object 38 | * @param {function} callback 39 | */ 40 | export function each(collection, callback) { 41 | if (isNode(collection) || collection === window || collection === document) { 42 | collection = [collection]; 43 | } 44 | if (!isArrayLike(collection) && !isObject(collection)) { 45 | collection = [collection]; 46 | } 47 | if (size(collection) == 0) { 48 | return; 49 | } 50 | 51 | if (isArrayLike(collection) && !isObject(collection)) { 52 | let l = collection.length, 53 | i = 0; 54 | for (; i < l; i++) { 55 | if (callback.call(collection[i], collection[i], i, collection) === false) { 56 | break; 57 | } 58 | } 59 | } else if (isObject(collection)) { 60 | for (let key in collection) { 61 | if (has(collection, key)) { 62 | if (callback.call(collection[key], collection[key], key, collection) === false) { 63 | break; 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | 71 | /** 72 | * Get nde events 73 | * return node events and optionally 74 | * check if the node has already a specific event 75 | * to avoid duplicated callbacks 76 | * 77 | * @param {node} node 78 | * @param {string} name event name 79 | * @param {object} fn callback 80 | * @returns {object} 81 | */ 82 | export function getNodeEvents(node, name = null, fn = null) { 83 | const cache = (node[uid] = node[uid] || []); 84 | const data = { all: cache, evt: null, found: null }; 85 | if (name && fn && size(cache) > 0) { 86 | each(cache, (cl, i) => { 87 | if (cl.eventName == name && cl.fn.toString() == fn.toString()) { 88 | data.found = true; 89 | data.evt = i; 90 | return false; 91 | } 92 | }); 93 | } 94 | return data; 95 | } 96 | 97 | 98 | /** 99 | * Add Event 100 | * Add an event listener 101 | * 102 | * @param {string} eventName 103 | * @param {object} detials 104 | */ 105 | export function addEvent(eventName, { 106 | onElement, 107 | withCallback, 108 | avoidDuplicate = true, 109 | once = false, 110 | useCapture = false 111 | } = {}, thisArg) { 112 | let element = onElement || []; 113 | if (isString(element)) { 114 | element = document.querySelectorAll(element); 115 | } 116 | 117 | function handler(event) { 118 | if (isFunction(withCallback)) { 119 | withCallback.call(thisArg, event, this); 120 | } 121 | if (once) { 122 | handler.destroy(); 123 | } 124 | } 125 | handler.destroy = function() { 126 | each(element, (el) => { 127 | const events = getNodeEvents(el, eventName, handler); 128 | if (events.found) { 129 | events.all.splice(events.evt, 1); 130 | } 131 | if (el.removeEventListener) { 132 | el.removeEventListener(eventName, handler, useCapture); 133 | } 134 | }); 135 | }; 136 | each(element, (el) => { 137 | const events = getNodeEvents(el, eventName, handler); 138 | if (el.addEventListener && (avoidDuplicate && !events.found) || !avoidDuplicate) { 139 | el.addEventListener(eventName, handler, useCapture); 140 | events.all.push({ eventName: eventName, fn: handler }); 141 | } 142 | }); 143 | return handler; 144 | } 145 | 146 | /** 147 | * Add element class 148 | * 149 | * @param {node} element 150 | * @param {string} class name 151 | */ 152 | export function addClass(node, name) { 153 | each(name.split(' '), cl => node.classList.add(cl)); 154 | } 155 | 156 | /** 157 | * Remove element class 158 | * 159 | * @param {node} element 160 | * @param {string} class name 161 | */ 162 | export function removeClass(node, name) { 163 | each(name.split(' '), cl => node.classList.remove(cl)); 164 | } 165 | 166 | /** 167 | * Has class 168 | * 169 | * @param {node} element 170 | * @param {string} class name 171 | */ 172 | export function hasClass(node, name) { 173 | return node.classList.contains(name); 174 | } 175 | 176 | /** 177 | * Get the closestElement 178 | * 179 | * @param {node} element 180 | * @param {string} class name 181 | */ 182 | export function closest(elem, selector) { 183 | while (elem !== document.body) { 184 | elem = elem.parentElement; 185 | if (!elem) { 186 | return false; 187 | } 188 | const matches = typeof elem.matches == 'function' ? elem.matches(selector) : elem.msMatchesSelector(selector); 189 | 190 | if (matches) { 191 | return elem; 192 | } 193 | } 194 | } 195 | 196 | /** 197 | * CSS Animations 198 | * 199 | * @param {node} element 200 | * @param {string} animation name 201 | * @param {function} callback 202 | */ 203 | export function animateElement(element, animation = '', callback = false) { 204 | if (!element || animation === '') { 205 | return false; 206 | } 207 | if (animation == 'none') { 208 | if (isFunction(callback)) { 209 | callback(); 210 | } 211 | return false; 212 | } 213 | const animationEnd = whichAnimationEvent(); 214 | const animationNames = animation.split(' '); 215 | each(animationNames, (name) => { 216 | addClass(element, 'g' + name); 217 | }); 218 | addEvent(animationEnd, { 219 | onElement: element, 220 | avoidDuplicate: false, 221 | once: true, 222 | withCallback: (event, target) => { 223 | each(animationNames, (name) => { 224 | removeClass(target, 'g' + name); 225 | }); 226 | if (isFunction(callback)) { 227 | callback(); 228 | } 229 | } 230 | }); 231 | } 232 | 233 | export function cssTransform(node, translate = '') { 234 | if (translate == '') { 235 | node.style.webkitTransform = ''; 236 | node.style.MozTransform = ''; 237 | node.style.msTransform = ''; 238 | node.style.OTransform = ''; 239 | node.style.transform = ''; 240 | return false; 241 | } 242 | node.style.webkitTransform = translate; 243 | node.style.MozTransform = translate; 244 | node.style.msTransform = translate; 245 | node.style.OTransform = translate; 246 | node.style.transform = translate; 247 | } 248 | 249 | /** 250 | * Show element 251 | * 252 | * @param {node} element 253 | */ 254 | export function show(element) { 255 | element.style.display = 'block'; 256 | } 257 | 258 | /** 259 | * Hide element 260 | */ 261 | export function hide(element) { 262 | element.style.display = 'none'; 263 | } 264 | 265 | /** 266 | * Create a document fragment 267 | * 268 | * @param {string} html code 269 | */ 270 | export function createHTML(htmlStr) { 271 | let frag = document.createDocumentFragment(), 272 | temp = document.createElement('div'); 273 | temp.innerHTML = htmlStr; 274 | while (temp.firstChild) { 275 | frag.appendChild(temp.firstChild); 276 | } 277 | return frag; 278 | } 279 | 280 | /** 281 | * Return screen size 282 | * return the current screen dimensions 283 | * 284 | * @returns {object} 285 | */ 286 | export function windowSize() { 287 | return { 288 | width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, 289 | height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 290 | }; 291 | } 292 | 293 | /** 294 | * Determine animation events 295 | */ 296 | export function whichAnimationEvent() { 297 | let t, el = document.createElement('fakeelement'); 298 | let animations = { 299 | animation: 'animationend', 300 | OAnimation: 'oAnimationEnd', 301 | MozAnimation: 'animationend', 302 | WebkitAnimation: 'webkitAnimationEnd' 303 | }; 304 | for (t in animations) { 305 | if (el.style[t] !== undefined) { 306 | return animations[t]; 307 | } 308 | } 309 | } 310 | 311 | /** 312 | * Determine transition events 313 | */ 314 | export function whichTransitionEvent() { 315 | let t, 316 | el = document.createElement('fakeelement'); 317 | 318 | const transitions = { 319 | transition: 'transitionend', 320 | OTransition: 'oTransitionEnd', 321 | MozTransition: 'transitionend', 322 | WebkitTransition: 'webkitTransitionEnd' 323 | }; 324 | 325 | for (t in transitions) { 326 | if (el.style[t] !== undefined) { 327 | return transitions[t]; 328 | } 329 | } 330 | } 331 | 332 | 333 | /** 334 | * Create an iframe element 335 | * 336 | * @param {string} url 337 | * @param {numeric} width 338 | * @param {numeric} height 339 | * @param {function} callback 340 | */ 341 | export function createIframe(config) { 342 | let { url, allow, callback, appendTo } = config; 343 | let iframe = document.createElement('iframe'); 344 | iframe.className = 'vimeo-video gvideo'; 345 | iframe.src = url; 346 | iframe.style.width = '100%'; 347 | iframe.style.height = '100%'; 348 | 349 | if (allow) { 350 | iframe.setAttribute('allow', allow); 351 | } 352 | iframe.onload = function() { 353 | addClass(iframe, 'node-ready'); 354 | if (isFunction(callback)) { 355 | callback(); 356 | } 357 | }; 358 | 359 | if (appendTo) { 360 | appendTo.appendChild(iframe); 361 | } 362 | return iframe; 363 | } 364 | 365 | 366 | /** 367 | * Wait until 368 | * wait until all the validations 369 | * are passed 370 | * 371 | * @param {function} check 372 | * @param {function} onComplete 373 | * @param {numeric} delay 374 | * @param {numeric} timeout 375 | */ 376 | export function waitUntil(check, onComplete, delay, timeout) { 377 | if (check()) { 378 | onComplete(); 379 | return; 380 | } 381 | 382 | if (!delay) { 383 | delay = 100; 384 | } 385 | let timeoutPointer; 386 | let intervalPointer = setInterval(() => { 387 | if (!check()) { 388 | return; 389 | } 390 | clearInterval(intervalPointer); 391 | if (timeoutPointer) { 392 | clearTimeout(timeoutPointer); 393 | } 394 | onComplete(); 395 | }, delay); 396 | if (timeout) { 397 | timeoutPointer = setTimeout(() => { 398 | clearInterval(intervalPointer); 399 | }, timeout); 400 | } 401 | } 402 | 403 | /** 404 | * Inject videos api 405 | * used for video player 406 | * 407 | * @param {string} url 408 | * @param {function} callback 409 | */ 410 | export function injectAssets(url, waitFor, callback) { 411 | if (isNil(url)) { 412 | console.error('Inject assets error'); 413 | return; 414 | } 415 | if (isFunction(waitFor)) { 416 | callback = waitFor; 417 | waitFor = false; 418 | } 419 | 420 | if (isString(waitFor) && (waitFor in window)) { 421 | if (isFunction(callback)) { 422 | callback(); 423 | } 424 | return; 425 | } 426 | 427 | let found; 428 | 429 | if (url.indexOf('.css') !== -1) { 430 | found = document.querySelectorAll('link[href="' + url + '"]'); 431 | if (found && found.length > 0) { 432 | if (isFunction(callback)) { 433 | callback(); 434 | } 435 | return; 436 | } 437 | 438 | const head = document.getElementsByTagName('head')[0]; 439 | const headStyles = head.querySelectorAll('link[rel="stylesheet"]'); 440 | const link = document.createElement('link'); 441 | 442 | link.rel = 'stylesheet'; 443 | link.type = 'text/css'; 444 | link.href = url; 445 | link.media = 'all'; 446 | 447 | if (headStyles) { 448 | head.insertBefore(link, headStyles[0]); 449 | } else { 450 | head.appendChild(link); 451 | } 452 | if (isFunction(callback)) { 453 | callback(); 454 | } 455 | return; 456 | } 457 | 458 | found = document.querySelectorAll('script[src="' + url + '"]'); 459 | if (found && found.length > 0) { 460 | if (isFunction(callback)) { 461 | if (isString(waitFor)) { 462 | waitUntil(() => { 463 | return typeof window[waitFor] !== 'undefined'; 464 | }, () => { 465 | callback(); 466 | }); 467 | return false; 468 | } 469 | callback(); 470 | } 471 | return; 472 | } 473 | 474 | let script = document.createElement('script'); 475 | script.type = 'text/javascript'; 476 | script.src = url; 477 | script.onload = () => { 478 | if (isFunction(callback)) { 479 | if (isString(waitFor)) { 480 | waitUntil(() => { 481 | return typeof window[waitFor] !== 'undefined'; 482 | }, () => { 483 | callback(); 484 | }); 485 | return false; 486 | } 487 | callback(); 488 | } 489 | }; 490 | document.body.appendChild(script); 491 | return; 492 | } 493 | 494 | export function isMobile() { 495 | return ('navigator' in window && window.navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(Android)|(PlayBook)|(BB10)|(BlackBerry)|(Opera Mini)|(IEMobile)|(webOS)|(MeeGo)/i)); 496 | } 497 | 498 | export function isTouch() { 499 | return isMobile() !== null || document.createTouch !== undefined || ('ontouchstart' in window) || ('onmsgesturechange' in window) || navigator.msMaxTouchPoints; 500 | } 501 | 502 | export function isFunction(f) { 503 | return typeof f === 'function'; 504 | } 505 | export function isString(s) { 506 | return typeof s === 'string'; 507 | } 508 | export function isNode(el) { 509 | return !!(el && el.nodeType && el.nodeType == 1); 510 | } 511 | export function isArray(ar) { 512 | return Array.isArray(ar); 513 | } 514 | export function isArrayLike(ar) { 515 | return (ar && ar.length && isFinite(ar.length)); 516 | } 517 | export function isObject(o) { 518 | let type = typeof o; 519 | return type === 'object' && (o != null && !isFunction(o) && !isArray(o)); 520 | } 521 | export function isNil(o) { 522 | return o == null; 523 | } 524 | export function has(obj, key) { 525 | return obj !== null && hasOwnProperty.call(obj, key); 526 | } 527 | export function size(o) { 528 | if (isObject(o)) { 529 | if (o.keys) { 530 | return o.keys().length; 531 | } 532 | let l = 0; 533 | for (let k in o) { 534 | if (has(o, k)) { 535 | l++; 536 | } 537 | } 538 | return l; 539 | } else { 540 | return o.length; 541 | } 542 | } 543 | export function isNumber(n) { 544 | return !isNaN(parseFloat(n)) && isFinite(n); 545 | } 546 | -------------------------------------------------------------------------------- /src/postcss/glightbox.css: -------------------------------------------------------------------------------- 1 | @custom-media --small-viewport (width >=576px); 2 | @custom-media --medium-small-viewport (width > 768px); 3 | @custom-media --medium-viewport (width >=992px); 4 | @custom-media --large-viewport (width >=1200px); 5 | 6 | .glightbox-container { 7 | width: 100%; 8 | height: 100%; 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | z-index: 999999 !important; 13 | overflow: hidden; 14 | touch-action: none; 15 | text-size-adjust: 100%; 16 | backface-visibility: hidden; 17 | outline: none; 18 | overflow: hidden; 19 | 20 | &.inactive { 21 | display: none; 22 | } 23 | 24 | & .gcontainer { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | z-index: 9999; 29 | overflow: hidden; 30 | } 31 | 32 | .gslider { 33 | transition: transform 0.4s ease; 34 | height: 100%; 35 | left: 0; 36 | top: 0; 37 | width: 100%; 38 | position: relative; 39 | overflow: hidden; 40 | display: flex !important; 41 | justify-content: center; 42 | align-items: center; 43 | transform: translate3d(0, 0, 0); 44 | } 45 | 46 | .gslide { 47 | width: 100%; 48 | position: absolute; 49 | opacity: 1; 50 | user-select: none; 51 | display: flex; 52 | align-items: center; 53 | justify-content: center; 54 | opacity: 0; 55 | 56 | &.current { 57 | opacity: 1; 58 | z-index: 99999; 59 | position: relative; 60 | } 61 | 62 | &.prev { 63 | opacity: 1; 64 | z-index: 9999; 65 | } 66 | } 67 | 68 | .gslide-inner-content { 69 | width: 100%; 70 | } 71 | 72 | .ginner-container { 73 | position: relative; 74 | width: 100%; 75 | display: flex; 76 | justify-content: center; 77 | flex-direction: column; 78 | max-width: 100%; 79 | margin: auto; 80 | height: 100vh; 81 | 82 | &.gvideo-container { 83 | width: 100%; 84 | } 85 | 86 | @media (--medium-small-viewport) { 87 | width: auto; 88 | height: auto; 89 | flex-direction: row; 90 | } 91 | 92 | &.desc-bottom, 93 | &.desc-top { 94 | flex-direction: column; 95 | } 96 | 97 | &.desc-left, 98 | &.desc-right { 99 | max-width: 100% !important; 100 | } 101 | 102 | &.desc-top { 103 | @media (--medium-small-viewport) { 104 | .gslide-description { 105 | order: 0; 106 | } 107 | .gslide-image, 108 | .gslide-image img { 109 | order: 1; 110 | } 111 | } 112 | } 113 | 114 | &.desc-left { 115 | @media (--medium-small-viewport) { 116 | .gslide-description { 117 | order: 0; 118 | } 119 | .gslide-image { 120 | order: 1; 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | .gslide { 128 | iframe, 129 | video { 130 | outline: none !important; 131 | border: none; 132 | min-height: 165px; 133 | -webkit-overflow-scrolling: touch; 134 | touch-action: auto; 135 | } 136 | } 137 | 138 | .gslide-image { 139 | align-items: center; 140 | 141 | img { 142 | max-height: 100vh; 143 | display: block; 144 | padding: 0; 145 | float: none; 146 | outline: none; 147 | border: none; 148 | user-select: none; 149 | max-width: 100vw; 150 | width: auto; 151 | height: auto; 152 | object-fit: cover; 153 | touch-action: none; 154 | margin: auto; 155 | min-width: 200px; 156 | 157 | @media (--medium-small-viewport) { 158 | max-height: 97vh; 159 | max-width: 100%; 160 | } 161 | 162 | .desc-top &, 163 | .desc-bottom & { 164 | width: auto; 165 | } 166 | 167 | .desc-left &, 168 | .desc-right & { 169 | width: auto; 170 | max-width: 100%; 171 | } 172 | 173 | &.zoomable { 174 | position: relative; 175 | 176 | @media (--medium-small-viewport) { 177 | cursor: zoom-in; 178 | 179 | .zoomed & { 180 | cursor: grab; 181 | } 182 | } 183 | } 184 | 185 | &.dragging { 186 | cursor: grabbing !important; 187 | transition: none; 188 | } 189 | } 190 | } 191 | 192 | .gslide-video { 193 | position: relative; 194 | max-width: 100vh; 195 | width: 100% !important; 196 | 197 | .gvideo-wrapper { 198 | width: 100%; 199 | /* max-width: 160vmin; */ 200 | margin: auto; 201 | } 202 | 203 | &::before { 204 | content: ''; 205 | display: block; 206 | position: absolute; 207 | width: 100%; 208 | height: 100%; 209 | background: rgba(255, 0, 0, 0.34); 210 | display: none; 211 | } 212 | &.playing::before { 213 | display: none; 214 | } 215 | 216 | &.fullscreen { 217 | max-width: 100% !important; 218 | min-width: 100%; 219 | height: 75vh; 220 | 221 | video { 222 | max-width: 100% !important; 223 | width: 100% !important; 224 | } 225 | } 226 | } 227 | 228 | .gslide-inline { 229 | background: #fff; 230 | text-align: left; 231 | max-height: calc(100vh - 40px); 232 | overflow: auto; 233 | max-width: 100%; 234 | 235 | @media (--medium-small-viewport) { 236 | max-height: 95vh; 237 | } 238 | 239 | .ginlined-content { 240 | padding: 20px; 241 | width: 100%; 242 | } 243 | 244 | .dragging { 245 | cursor: grabbing !important; 246 | transition: none; 247 | } 248 | } 249 | 250 | .ginlined-content { 251 | overflow: auto; 252 | display: block !important; 253 | opacity: 1; 254 | } 255 | 256 | .gslide-external { 257 | display: flex; 258 | width: 100%; 259 | min-width: 100%; 260 | background: #fff; 261 | padding: 0; 262 | overflow: auto; 263 | max-height: 75vh; 264 | height: 100%; 265 | 266 | @media (--medium-small-viewport) { 267 | max-height: 100vh; 268 | } 269 | } 270 | 271 | .gslide-media { 272 | display: flex; 273 | width: auto; 274 | 275 | .zoomed & { 276 | box-shadow: none !important; 277 | } 278 | 279 | .desc-top &, 280 | .desc-bottom & { 281 | margin: 0 auto; 282 | flex-direction: column; 283 | } 284 | } 285 | 286 | .gslide-description { 287 | position: relative; 288 | flex: 1 0 100%; 289 | 290 | &.description-left, 291 | &.description-right { 292 | max-width: 100%; 293 | 294 | @media (--medium-small-viewport) { 295 | max-width: 275px; 296 | } 297 | } 298 | 299 | &.description-bottom, 300 | &.description-top { 301 | margin: 0 auto; 302 | width: 100%; 303 | } 304 | 305 | p { 306 | margin-bottom: 12px; 307 | 308 | &:last-child { 309 | margin-bottom: 0; 310 | } 311 | } 312 | 313 | .zoomed & { 314 | display: none; 315 | } 316 | } 317 | 318 | .glightbox-button-hidden { 319 | display: none; 320 | } 321 | 322 | /* 323 | * Description for mobiles 324 | * something like facebook does the description 325 | * for the photos 326 | */ 327 | .glightbox-mobile .glightbox-container { 328 | .gslide-description { 329 | height: auto !important; 330 | width: 100%; 331 | background: transparent; 332 | position: absolute; 333 | bottom: 15px; 334 | padding: 19px 11px; 335 | max-width: 100vw !important; 336 | order: 2 !important; 337 | max-height: 78vh; 338 | overflow: auto !important; 339 | background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.75) 100%); 340 | transition: opacity 0.3s linear; 341 | padding-bottom: 50px; 342 | } 343 | 344 | .gslide-title { 345 | color: #fff; 346 | font-size: 1em; 347 | } 348 | .gslide-desc { 349 | color: #a1a1a1; 350 | 351 | a { 352 | color: #fff; 353 | font-weight: bold; 354 | } 355 | 356 | * { 357 | color: inherit; 358 | } 359 | string { 360 | color: #fff; 361 | } 362 | .desc-more { 363 | color: #fff; 364 | opacity: 0.4; 365 | } 366 | } 367 | } 368 | 369 | .gdesc-open { 370 | .gslide-media { 371 | transition: opacity 0.5s ease; 372 | opacity: 0.4; 373 | } 374 | .gdesc-inner { 375 | padding-bottom: 30px; 376 | } 377 | } 378 | .gdesc-closed { 379 | .gslide-media { 380 | transition: opacity 0.5s ease; 381 | opacity: 1; 382 | } 383 | } 384 | 385 | .greset { 386 | transition: all 0.3s ease; 387 | } 388 | 389 | .gabsolute { 390 | position: absolute; 391 | } 392 | 393 | .grelative { 394 | position: relative; 395 | } 396 | 397 | .glightbox-desc { 398 | display: none !important; 399 | } 400 | 401 | .glightbox-open { 402 | overflow: hidden; 403 | 404 | @media (--medium-small-viewport) { 405 | height: auto; 406 | } 407 | } 408 | 409 | .gloader { 410 | height: 25px; 411 | width: 25px; 412 | animation: lightboxLoader 0.8s infinite linear; 413 | border: 2px solid #fff; 414 | border-right-color: transparent; 415 | border-radius: 50%; 416 | position: absolute; 417 | display: block; 418 | z-index: 9999; 419 | left: 0; 420 | right: 0; 421 | margin: 0 auto; 422 | top: 47%; 423 | } 424 | 425 | .goverlay { 426 | width: 100%; 427 | height: calc(100vh + 1px); 428 | position: fixed; 429 | top: -1px; 430 | left: 0; 431 | background: #000; 432 | will-change: opacity; 433 | 434 | .glightbox-mobile & { 435 | background: #000; 436 | } 437 | 438 | @media (--medium-small-viewport) { 439 | background: rgba(0, 0, 0, 0.92); 440 | } 441 | 442 | @media screen and (max-height: 420px) { 443 | background: #000; 444 | } 445 | } 446 | 447 | .gprev, 448 | .gnext, 449 | .gclose { 450 | z-index: 99999; 451 | cursor: pointer; 452 | width: 26px; 453 | height: 44px; 454 | border: none; 455 | display: flex; 456 | justify-content: center; 457 | align-items: center; 458 | flex-direction: column; 459 | 460 | svg { 461 | display: block; 462 | width: 25px; 463 | height: auto; 464 | margin: 0; 465 | padding: 0; 466 | } 467 | 468 | &.disabled { 469 | opacity: 0.1; 470 | } 471 | 472 | .garrow { 473 | stroke: #fff; 474 | } 475 | } 476 | 477 | .gbtn.focused { 478 | outline: 2px solid #0f3d81; 479 | } 480 | 481 | iframe.wait-autoplay { 482 | opacity: 0; 483 | } 484 | 485 | .glightbox-closing { 486 | .gnext, 487 | .gprev, 488 | .gclose { 489 | opacity: 0 !important; 490 | } 491 | } 492 | 493 | /*Skin */ 494 | .glightbox-clean { 495 | .gslide-media { 496 | @media (--medium-small-viewport) { 497 | box-shadow: 1px 2px 9px 0px rgba(0, 0, 0, 0.65); 498 | } 499 | } 500 | 501 | .gslide-description { 502 | background: #fff; 503 | } 504 | 505 | .gdesc-inner { 506 | padding: 22px 20px; 507 | } 508 | 509 | @media (--medium-small-viewport) { 510 | .description-left, 511 | .description-right { 512 | .gdesc-inner { 513 | position: absolute; 514 | height: 100%; 515 | overflow-y: auto; 516 | } 517 | } 518 | } 519 | 520 | .gslide-title { 521 | font-size: 1em; 522 | font-weight: normal; 523 | font-family: arial; 524 | color: #000; 525 | margin-bottom: 19px; 526 | line-height: 1.4em; 527 | } 528 | 529 | .gslide-desc { 530 | font-size: 0.86em; 531 | margin-bottom: 0; 532 | font-family: arial; 533 | line-height: 1.4em; 534 | } 535 | 536 | .gslide-video { 537 | background: #000; 538 | } 539 | 540 | .gprev, 541 | .gnext, 542 | .gclose { 543 | background-color: rgba(0, 0, 0, 0.75); 544 | border-radius: 4px; 545 | 546 | @media (--medium-small-viewport) { 547 | background-color: rgba(0, 0, 0, 0.32); 548 | 549 | &:hover { 550 | background-color: rgba(0, 0, 0, 0.7); 551 | } 552 | } 553 | 554 | path { 555 | fill: #fff; 556 | } 557 | } 558 | 559 | .gprev { 560 | position: absolute; 561 | top: -100%; 562 | left: 30px; 563 | width: 40px; 564 | height: 50px; 565 | 566 | @media (--medium-small-viewport) { 567 | top: 45%; 568 | } 569 | } 570 | 571 | .gnext { 572 | position: absolute; 573 | top: -100%; 574 | right: 30px; 575 | width: 40px; 576 | height: 50px; 577 | 578 | @media (--medium-small-viewport) { 579 | top: 45%; 580 | } 581 | } 582 | 583 | .gclose { 584 | width: 35px; 585 | height: 35px; 586 | top: 15px; 587 | right: 10px; 588 | position: absolute; 589 | 590 | svg { 591 | width: 18px; 592 | height: auto; 593 | } 594 | 595 | @media (--medium-viewport) { 596 | opacity: 0.7; 597 | right: 20px; 598 | } 599 | 600 | &:hover { 601 | opacity: 1; 602 | } 603 | } 604 | } 605 | 606 | /*CSS Animations*/ 607 | .gfadeIn { 608 | animation: gfadeIn 0.5s ease; 609 | } 610 | .gfadeOut { 611 | animation: gfadeOut 0.5s ease; 612 | } 613 | .gslideOutLeft { 614 | animation: gslideOutLeft 0.3s ease; 615 | } 616 | .gslideInLeft { 617 | animation: gslideInLeft 0.3s ease; 618 | } 619 | .gslideOutRight { 620 | animation: gslideOutRight 0.3s ease; 621 | } 622 | .gslideInRight { 623 | animation: gslideInRight 0.3s ease; 624 | } 625 | .gzoomIn { 626 | animation: gzoomIn 0.5s ease; 627 | } 628 | .gzoomOut { 629 | animation: gzoomOut 0.5s ease; 630 | } 631 | 632 | @keyframes lightboxLoader { 633 | 0% { 634 | transform: rotate(0deg); 635 | } 636 | 100% { 637 | transform: rotate(360deg); 638 | } 639 | } 640 | @keyframes gfadeIn { 641 | from { 642 | opacity: 0; 643 | } 644 | to { 645 | opacity: 1; 646 | } 647 | } 648 | @keyframes gfadeOut { 649 | from { 650 | opacity: 1; 651 | } 652 | to { 653 | opacity: 0; 654 | } 655 | } 656 | @keyframes gslideInLeft { 657 | from { 658 | opacity: 0; 659 | transform: translate3d(-60%, 0, 0); 660 | } 661 | to { 662 | visibility: visible; 663 | transform: translate3d(0, 0, 0); 664 | opacity: 1; 665 | } 666 | } 667 | @keyframes gslideOutLeft { 668 | from { 669 | opacity: 1; 670 | visibility: visible; 671 | transform: translate3d(0, 0, 0); 672 | } 673 | to { 674 | transform: translate3d(-60%, 0, 0); 675 | opacity: 0; 676 | visibility: hidden; 677 | } 678 | } 679 | @keyframes gslideInRight { 680 | from { 681 | opacity: 0; 682 | visibility: visible; 683 | transform: translate3d(60%, 0, 0); 684 | } 685 | to { 686 | transform: translate3d(0, 0, 0); 687 | opacity: 1; 688 | } 689 | } 690 | @keyframes gslideOutRight { 691 | from { 692 | opacity: 1; 693 | visibility: visible; 694 | transform: translate3d(0, 0, 0); 695 | } 696 | to { 697 | transform: translate3d(60%, 0, 0); 698 | opacity: 0; 699 | } 700 | } 701 | @keyframes gzoomIn { 702 | from { 703 | opacity: 0; 704 | transform: scale3d(0.3, 0.3, 0.3); 705 | } 706 | to { 707 | opacity: 1; 708 | } 709 | } 710 | @keyframes gzoomOut { 711 | from { 712 | opacity: 1; 713 | } 714 | 50% { 715 | opacity: 0; 716 | transform: scale3d(0.3, 0.3, 0.3); 717 | } 718 | to { 719 | opacity: 0; 720 | } 721 | } 722 | -------------------------------------------------------------------------------- /dist/css/glightbox.css: -------------------------------------------------------------------------------- 1 | .glightbox-container { 2 | width: 100%; 3 | height: 100%; 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | z-index: 999999 !important; 8 | overflow: hidden; 9 | -ms-touch-action: none; 10 | touch-action: none; 11 | -webkit-text-size-adjust: 100%; 12 | -moz-text-size-adjust: 100%; 13 | -ms-text-size-adjust: 100%; 14 | text-size-adjust: 100%; 15 | -webkit-backface-visibility: hidden; 16 | backface-visibility: hidden; 17 | outline: none; 18 | overflow: hidden; 19 | } 20 | 21 | .glightbox-container.inactive { 22 | display: none; 23 | } 24 | 25 | .glightbox-container .gcontainer { 26 | position: relative; 27 | width: 100%; 28 | height: 100%; 29 | z-index: 9999; 30 | overflow: hidden; 31 | } 32 | 33 | .glightbox-container .gslider { 34 | -webkit-transition: -webkit-transform 0.4s ease; 35 | transition: -webkit-transform 0.4s ease; 36 | transition: transform 0.4s ease; 37 | transition: transform 0.4s ease, -webkit-transform 0.4s ease; 38 | height: 100%; 39 | left: 0; 40 | top: 0; 41 | width: 100%; 42 | position: relative; 43 | overflow: hidden; 44 | display: -webkit-box !important; 45 | display: -ms-flexbox !important; 46 | display: flex !important; 47 | -webkit-box-pack: center; 48 | -ms-flex-pack: center; 49 | justify-content: center; 50 | -webkit-box-align: center; 51 | -ms-flex-align: center; 52 | align-items: center; 53 | -webkit-transform: translate3d(0, 0, 0); 54 | transform: translate3d(0, 0, 0); 55 | } 56 | 57 | .glightbox-container .gslide { 58 | width: 100%; 59 | position: absolute; 60 | opacity: 1; 61 | -webkit-user-select: none; 62 | -moz-user-select: none; 63 | -ms-user-select: none; 64 | user-select: none; 65 | display: -webkit-box; 66 | display: -ms-flexbox; 67 | display: flex; 68 | -webkit-box-align: center; 69 | -ms-flex-align: center; 70 | align-items: center; 71 | -webkit-box-pack: center; 72 | -ms-flex-pack: center; 73 | justify-content: center; 74 | opacity: 0; 75 | } 76 | 77 | .glightbox-container .gslide.current { 78 | opacity: 1; 79 | z-index: 99999; 80 | position: relative; 81 | } 82 | 83 | .glightbox-container .gslide.prev { 84 | opacity: 1; 85 | z-index: 9999; 86 | } 87 | 88 | .glightbox-container .gslide-inner-content { 89 | width: 100%; 90 | } 91 | 92 | .glightbox-container .ginner-container { 93 | position: relative; 94 | width: 100%; 95 | display: -webkit-box; 96 | display: -ms-flexbox; 97 | display: flex; 98 | -webkit-box-pack: center; 99 | -ms-flex-pack: center; 100 | justify-content: center; 101 | -webkit-box-orient: vertical; 102 | -webkit-box-direction: normal; 103 | -ms-flex-direction: column; 104 | flex-direction: column; 105 | max-width: 100%; 106 | margin: auto; 107 | height: 100vh; 108 | } 109 | 110 | .glightbox-container .ginner-container.gvideo-container { 111 | width: 100%; 112 | } 113 | 114 | .glightbox-container .ginner-container.desc-bottom, 115 | .glightbox-container .ginner-container.desc-top { 116 | -webkit-box-orient: vertical; 117 | -webkit-box-direction: normal; 118 | -ms-flex-direction: column; 119 | flex-direction: column; 120 | } 121 | 122 | .glightbox-container .ginner-container.desc-left, 123 | .glightbox-container .ginner-container.desc-right { 124 | max-width: 100% !important; 125 | } 126 | 127 | .gslide iframe, 128 | .gslide video { 129 | outline: none !important; 130 | border: none; 131 | min-height: 165px; 132 | -webkit-overflow-scrolling: touch; 133 | -ms-touch-action: auto; 134 | touch-action: auto; 135 | } 136 | 137 | .gslide-image { 138 | -webkit-box-align: center; 139 | -ms-flex-align: center; 140 | align-items: center; 141 | } 142 | 143 | .gslide-image img { 144 | max-height: 100vh; 145 | display: block; 146 | padding: 0; 147 | float: none; 148 | outline: none; 149 | border: none; 150 | -webkit-user-select: none; 151 | -moz-user-select: none; 152 | -ms-user-select: none; 153 | user-select: none; 154 | max-width: 100vw; 155 | width: auto; 156 | height: auto; 157 | -o-object-fit: cover; 158 | object-fit: cover; 159 | -ms-touch-action: none; 160 | touch-action: none; 161 | margin: auto; 162 | min-width: 200px; 163 | } 164 | 165 | .desc-top .gslide-image img, 166 | .desc-bottom .gslide-image img { 167 | width: auto; 168 | } 169 | 170 | .desc-left .gslide-image img, 171 | .desc-right .gslide-image img { 172 | width: auto; 173 | max-width: 100%; 174 | } 175 | 176 | .gslide-image img.zoomable { 177 | position: relative; 178 | } 179 | 180 | .gslide-image img.dragging { 181 | cursor: -webkit-grabbing !important; 182 | cursor: grabbing !important; 183 | -webkit-transition: none; 184 | transition: none; 185 | } 186 | 187 | .gslide-video { 188 | position: relative; 189 | max-width: 100vh; 190 | width: 100% !important; 191 | } 192 | 193 | .gslide-video .gvideo-wrapper { 194 | width: 100%; 195 | /* max-width: 160vmin; */ 196 | margin: auto; 197 | } 198 | 199 | .gslide-video::before { 200 | content: ''; 201 | display: block; 202 | position: absolute; 203 | width: 100%; 204 | height: 100%; 205 | background: rgba(255, 0, 0, 0.34); 206 | display: none; 207 | } 208 | 209 | .gslide-video.playing::before { 210 | display: none; 211 | } 212 | 213 | .gslide-video.fullscreen { 214 | max-width: 100% !important; 215 | min-width: 100%; 216 | height: 75vh; 217 | } 218 | 219 | .gslide-video.fullscreen video { 220 | max-width: 100% !important; 221 | width: 100% !important; 222 | } 223 | 224 | .gslide-inline { 225 | background: #fff; 226 | text-align: left; 227 | max-height: calc(100vh - 40px); 228 | overflow: auto; 229 | max-width: 100%; 230 | } 231 | 232 | .gslide-inline .ginlined-content { 233 | padding: 20px; 234 | width: 100%; 235 | } 236 | 237 | .gslide-inline .dragging { 238 | cursor: -webkit-grabbing !important; 239 | cursor: grabbing !important; 240 | -webkit-transition: none; 241 | transition: none; 242 | } 243 | 244 | .ginlined-content { 245 | overflow: auto; 246 | display: block !important; 247 | opacity: 1; 248 | } 249 | 250 | .gslide-external { 251 | display: -webkit-box; 252 | display: -ms-flexbox; 253 | display: flex; 254 | width: 100%; 255 | min-width: 100%; 256 | background: #fff; 257 | padding: 0; 258 | overflow: auto; 259 | max-height: 75vh; 260 | height: 100%; 261 | } 262 | 263 | .gslide-media { 264 | display: -webkit-box; 265 | display: -ms-flexbox; 266 | display: flex; 267 | width: auto; 268 | } 269 | 270 | .zoomed .gslide-media { 271 | -webkit-box-shadow: none !important; 272 | box-shadow: none !important; 273 | } 274 | 275 | .desc-top .gslide-media, 276 | .desc-bottom .gslide-media { 277 | margin: 0 auto; 278 | -webkit-box-orient: vertical; 279 | -webkit-box-direction: normal; 280 | -ms-flex-direction: column; 281 | flex-direction: column; 282 | } 283 | 284 | .gslide-description { 285 | position: relative; 286 | -webkit-box-flex: 1; 287 | -ms-flex: 1 0 100%; 288 | flex: 1 0 100%; 289 | } 290 | 291 | .gslide-description.description-left, 292 | .gslide-description.description-right { 293 | max-width: 100%; 294 | } 295 | 296 | .gslide-description.description-bottom, 297 | .gslide-description.description-top { 298 | margin: 0 auto; 299 | width: 100%; 300 | } 301 | 302 | .gslide-description p { 303 | margin-bottom: 12px; 304 | } 305 | 306 | .gslide-description p:last-child { 307 | margin-bottom: 0; 308 | } 309 | 310 | .zoomed .gslide-description { 311 | display: none; 312 | } 313 | 314 | .glightbox-button-hidden { 315 | display: none; 316 | } 317 | 318 | 319 | /* 320 | * Description for mobiles 321 | * something like facebook does the description 322 | * for the photos 323 | */ 324 | 325 | .glightbox-mobile .glightbox-container .gslide-description { 326 | height: auto !important; 327 | width: 100%; 328 | background: transparent; 329 | position: absolute; 330 | bottom: 15px; 331 | padding: 19px 11px; 332 | max-width: 100vw !important; 333 | -webkit-box-ordinal-group: 3 !important; 334 | -ms-flex-order: 2 !important; 335 | order: 2 !important; 336 | max-height: 78vh; 337 | overflow: auto !important; 338 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.75))); 339 | background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.75) 100%); 340 | -webkit-transition: opacity 0.3s linear; 341 | transition: opacity 0.3s linear; 342 | padding-bottom: 50px; 343 | } 344 | 345 | .glightbox-mobile .glightbox-container .gslide-title { 346 | color: #fff; 347 | font-size: 1em; 348 | } 349 | 350 | .glightbox-mobile .glightbox-container .gslide-desc { 351 | color: #a1a1a1; 352 | } 353 | 354 | .glightbox-mobile .glightbox-container .gslide-desc a { 355 | color: #fff; 356 | font-weight: bold; 357 | } 358 | 359 | .glightbox-mobile .glightbox-container .gslide-desc * { 360 | color: inherit; 361 | } 362 | 363 | .glightbox-mobile .glightbox-container .gslide-desc string { 364 | color: #fff; 365 | } 366 | 367 | .glightbox-mobile .glightbox-container .gslide-desc .desc-more { 368 | color: #fff; 369 | opacity: 0.4; 370 | } 371 | 372 | .gdesc-open .gslide-media { 373 | -webkit-transition: opacity 0.5s ease; 374 | transition: opacity 0.5s ease; 375 | opacity: 0.4; 376 | } 377 | 378 | .gdesc-open .gdesc-inner { 379 | padding-bottom: 30px; 380 | } 381 | 382 | .gdesc-closed .gslide-media { 383 | -webkit-transition: opacity 0.5s ease; 384 | transition: opacity 0.5s ease; 385 | opacity: 1; 386 | } 387 | 388 | .greset { 389 | -webkit-transition: all 0.3s ease; 390 | transition: all 0.3s ease; 391 | } 392 | 393 | .gabsolute { 394 | position: absolute; 395 | } 396 | 397 | .grelative { 398 | position: relative; 399 | } 400 | 401 | .glightbox-desc { 402 | display: none !important; 403 | } 404 | 405 | .glightbox-open { 406 | overflow: hidden; 407 | } 408 | 409 | .gloader { 410 | height: 25px; 411 | width: 25px; 412 | -webkit-animation: lightboxLoader 0.8s infinite linear; 413 | animation: lightboxLoader 0.8s infinite linear; 414 | border: 2px solid #fff; 415 | border-right-color: transparent; 416 | border-radius: 50%; 417 | position: absolute; 418 | display: block; 419 | z-index: 9999; 420 | left: 0; 421 | right: 0; 422 | margin: 0 auto; 423 | top: 47%; 424 | } 425 | 426 | .goverlay { 427 | width: 100%; 428 | height: calc(100vh + 1px); 429 | position: fixed; 430 | top: -1px; 431 | left: 0; 432 | background: #000; 433 | will-change: opacity; 434 | } 435 | 436 | .glightbox-mobile .goverlay { 437 | background: #000; 438 | } 439 | 440 | .gprev, 441 | .gnext, 442 | .gclose { 443 | z-index: 99999; 444 | cursor: pointer; 445 | width: 26px; 446 | height: 44px; 447 | border: none; 448 | display: -webkit-box; 449 | display: -ms-flexbox; 450 | display: flex; 451 | -webkit-box-pack: center; 452 | -ms-flex-pack: center; 453 | justify-content: center; 454 | -webkit-box-align: center; 455 | -ms-flex-align: center; 456 | align-items: center; 457 | -webkit-box-orient: vertical; 458 | -webkit-box-direction: normal; 459 | -ms-flex-direction: column; 460 | flex-direction: column; 461 | } 462 | 463 | .gprev svg, 464 | .gnext svg, 465 | .gclose svg { 466 | display: block; 467 | width: 25px; 468 | height: auto; 469 | margin: 0; 470 | padding: 0; 471 | } 472 | 473 | .gprev.disabled, 474 | .gnext.disabled, 475 | .gclose.disabled { 476 | opacity: 0.1; 477 | } 478 | 479 | .gprev .garrow, 480 | .gnext .garrow, 481 | .gclose .garrow { 482 | stroke: #fff; 483 | } 484 | 485 | .gbtn.focused { 486 | outline: 2px solid #0f3d81; 487 | } 488 | 489 | iframe.wait-autoplay { 490 | opacity: 0; 491 | } 492 | 493 | .glightbox-closing .gnext, 494 | .glightbox-closing .gprev, 495 | .glightbox-closing .gclose { 496 | opacity: 0 !important; 497 | } 498 | 499 | 500 | /*Skin */ 501 | 502 | .glightbox-clean .gslide-description { 503 | background: #fff; 504 | } 505 | 506 | .glightbox-clean .gdesc-inner { 507 | padding: 22px 20px; 508 | } 509 | 510 | .glightbox-clean .gslide-title { 511 | font-size: 1em; 512 | font-weight: normal; 513 | font-family: arial; 514 | color: #000; 515 | margin-bottom: 19px; 516 | line-height: 1.4em; 517 | } 518 | 519 | .glightbox-clean .gslide-desc { 520 | font-size: 0.86em; 521 | margin-bottom: 0; 522 | font-family: arial; 523 | line-height: 1.4em; 524 | } 525 | 526 | .glightbox-clean .gslide-video { 527 | background: #000; 528 | } 529 | 530 | .glightbox-clean .gprev, 531 | .glightbox-clean .gnext, 532 | .glightbox-clean .gclose { 533 | background-color: rgba(0, 0, 0, 0.75); 534 | border-radius: 4px; 535 | } 536 | 537 | .glightbox-clean .gprev path, 538 | .glightbox-clean .gnext path, 539 | .glightbox-clean .gclose path { 540 | fill: #fff; 541 | } 542 | 543 | .glightbox-clean .gprev { 544 | position: absolute; 545 | top: -100%; 546 | left: 30px; 547 | width: 40px; 548 | height: 50px; 549 | } 550 | 551 | .glightbox-clean .gnext { 552 | position: absolute; 553 | top: -100%; 554 | right: 30px; 555 | width: 40px; 556 | height: 50px; 557 | } 558 | 559 | .glightbox-clean .gclose { 560 | width: 35px; 561 | height: 35px; 562 | top: 15px; 563 | right: 10px; 564 | position: absolute; 565 | } 566 | 567 | .glightbox-clean .gclose svg { 568 | width: 18px; 569 | height: auto; 570 | } 571 | 572 | .glightbox-clean .gclose:hover { 573 | opacity: 1; 574 | } 575 | 576 | 577 | /*CSS Animations*/ 578 | 579 | .gfadeIn { 580 | -webkit-animation: gfadeIn 0.5s ease; 581 | animation: gfadeIn 0.5s ease; 582 | } 583 | 584 | .gfadeOut { 585 | -webkit-animation: gfadeOut 0.5s ease; 586 | animation: gfadeOut 0.5s ease; 587 | } 588 | 589 | .gslideOutLeft { 590 | -webkit-animation: gslideOutLeft 0.3s ease; 591 | animation: gslideOutLeft 0.3s ease; 592 | } 593 | 594 | .gslideInLeft { 595 | -webkit-animation: gslideInLeft 0.3s ease; 596 | animation: gslideInLeft 0.3s ease; 597 | } 598 | 599 | .gslideOutRight { 600 | -webkit-animation: gslideOutRight 0.3s ease; 601 | animation: gslideOutRight 0.3s ease; 602 | } 603 | 604 | .gslideInRight { 605 | -webkit-animation: gslideInRight 0.3s ease; 606 | animation: gslideInRight 0.3s ease; 607 | } 608 | 609 | .gzoomIn { 610 | -webkit-animation: gzoomIn 0.5s ease; 611 | animation: gzoomIn 0.5s ease; 612 | } 613 | 614 | .gzoomOut { 615 | -webkit-animation: gzoomOut 0.5s ease; 616 | animation: gzoomOut 0.5s ease; 617 | } 618 | 619 | @-webkit-keyframes lightboxLoader { 620 | 0% { 621 | -webkit-transform: rotate(0deg); 622 | transform: rotate(0deg); 623 | } 624 | 100% { 625 | -webkit-transform: rotate(360deg); 626 | transform: rotate(360deg); 627 | } 628 | } 629 | 630 | @keyframes lightboxLoader { 631 | 0% { 632 | -webkit-transform: rotate(0deg); 633 | transform: rotate(0deg); 634 | } 635 | 100% { 636 | -webkit-transform: rotate(360deg); 637 | transform: rotate(360deg); 638 | } 639 | } 640 | 641 | @-webkit-keyframes gfadeIn { 642 | from { 643 | opacity: 0; 644 | } 645 | to { 646 | opacity: 1; 647 | } 648 | } 649 | 650 | @keyframes gfadeIn { 651 | from { 652 | opacity: 0; 653 | } 654 | to { 655 | opacity: 1; 656 | } 657 | } 658 | 659 | @-webkit-keyframes gfadeOut { 660 | from { 661 | opacity: 1; 662 | } 663 | to { 664 | opacity: 0; 665 | } 666 | } 667 | 668 | @keyframes gfadeOut { 669 | from { 670 | opacity: 1; 671 | } 672 | to { 673 | opacity: 0; 674 | } 675 | } 676 | 677 | @-webkit-keyframes gslideInLeft { 678 | from { 679 | opacity: 0; 680 | -webkit-transform: translate3d(-60%, 0, 0); 681 | transform: translate3d(-60%, 0, 0); 682 | } 683 | to { 684 | visibility: visible; 685 | -webkit-transform: translate3d(0, 0, 0); 686 | transform: translate3d(0, 0, 0); 687 | opacity: 1; 688 | } 689 | } 690 | 691 | @keyframes gslideInLeft { 692 | from { 693 | opacity: 0; 694 | -webkit-transform: translate3d(-60%, 0, 0); 695 | transform: translate3d(-60%, 0, 0); 696 | } 697 | to { 698 | visibility: visible; 699 | -webkit-transform: translate3d(0, 0, 0); 700 | transform: translate3d(0, 0, 0); 701 | opacity: 1; 702 | } 703 | } 704 | 705 | @-webkit-keyframes gslideOutLeft { 706 | from { 707 | opacity: 1; 708 | visibility: visible; 709 | -webkit-transform: translate3d(0, 0, 0); 710 | transform: translate3d(0, 0, 0); 711 | } 712 | to { 713 | -webkit-transform: translate3d(-60%, 0, 0); 714 | transform: translate3d(-60%, 0, 0); 715 | opacity: 0; 716 | visibility: hidden; 717 | } 718 | } 719 | 720 | @keyframes gslideOutLeft { 721 | from { 722 | opacity: 1; 723 | visibility: visible; 724 | -webkit-transform: translate3d(0, 0, 0); 725 | transform: translate3d(0, 0, 0); 726 | } 727 | to { 728 | -webkit-transform: translate3d(-60%, 0, 0); 729 | transform: translate3d(-60%, 0, 0); 730 | opacity: 0; 731 | visibility: hidden; 732 | } 733 | } 734 | 735 | @-webkit-keyframes gslideInRight { 736 | from { 737 | opacity: 0; 738 | visibility: visible; 739 | -webkit-transform: translate3d(60%, 0, 0); 740 | transform: translate3d(60%, 0, 0); 741 | } 742 | to { 743 | -webkit-transform: translate3d(0, 0, 0); 744 | transform: translate3d(0, 0, 0); 745 | opacity: 1; 746 | } 747 | } 748 | 749 | @keyframes gslideInRight { 750 | from { 751 | opacity: 0; 752 | visibility: visible; 753 | -webkit-transform: translate3d(60%, 0, 0); 754 | transform: translate3d(60%, 0, 0); 755 | } 756 | to { 757 | -webkit-transform: translate3d(0, 0, 0); 758 | transform: translate3d(0, 0, 0); 759 | opacity: 1; 760 | } 761 | } 762 | 763 | @-webkit-keyframes gslideOutRight { 764 | from { 765 | opacity: 1; 766 | visibility: visible; 767 | -webkit-transform: translate3d(0, 0, 0); 768 | transform: translate3d(0, 0, 0); 769 | } 770 | to { 771 | -webkit-transform: translate3d(60%, 0, 0); 772 | transform: translate3d(60%, 0, 0); 773 | opacity: 0; 774 | } 775 | } 776 | 777 | @keyframes gslideOutRight { 778 | from { 779 | opacity: 1; 780 | visibility: visible; 781 | -webkit-transform: translate3d(0, 0, 0); 782 | transform: translate3d(0, 0, 0); 783 | } 784 | to { 785 | -webkit-transform: translate3d(60%, 0, 0); 786 | transform: translate3d(60%, 0, 0); 787 | opacity: 0; 788 | } 789 | } 790 | 791 | @-webkit-keyframes gzoomIn { 792 | from { 793 | opacity: 0; 794 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 795 | transform: scale3d(0.3, 0.3, 0.3); 796 | } 797 | to { 798 | opacity: 1; 799 | } 800 | } 801 | 802 | @keyframes gzoomIn { 803 | from { 804 | opacity: 0; 805 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 806 | transform: scale3d(0.3, 0.3, 0.3); 807 | } 808 | to { 809 | opacity: 1; 810 | } 811 | } 812 | 813 | @-webkit-keyframes gzoomOut { 814 | from { 815 | opacity: 1; 816 | } 817 | 50% { 818 | opacity: 0; 819 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 820 | transform: scale3d(0.3, 0.3, 0.3); 821 | } 822 | to { 823 | opacity: 0; 824 | } 825 | } 826 | 827 | @keyframes gzoomOut { 828 | from { 829 | opacity: 1; 830 | } 831 | 50% { 832 | opacity: 0; 833 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 834 | transform: scale3d(0.3, 0.3, 0.3); 835 | } 836 | to { 837 | opacity: 0; 838 | } 839 | } 840 | 841 | @media (min-width: 769px) { 842 | .glightbox-container .ginner-container { 843 | width: auto; 844 | height: auto; 845 | -webkit-box-orient: horizontal; 846 | -webkit-box-direction: normal; 847 | -ms-flex-direction: row; 848 | flex-direction: row; 849 | } 850 | .glightbox-container .ginner-container.desc-top .gslide-description { 851 | -webkit-box-ordinal-group: 1; 852 | -ms-flex-order: 0; 853 | order: 0; 854 | } 855 | .glightbox-container .ginner-container.desc-top .gslide-image, 856 | .glightbox-container .ginner-container.desc-top .gslide-image img { 857 | -webkit-box-ordinal-group: 2; 858 | -ms-flex-order: 1; 859 | order: 1; 860 | } 861 | .glightbox-container .ginner-container.desc-left .gslide-description { 862 | -webkit-box-ordinal-group: 1; 863 | -ms-flex-order: 0; 864 | order: 0; 865 | } 866 | .glightbox-container .ginner-container.desc-left .gslide-image { 867 | -webkit-box-ordinal-group: 2; 868 | -ms-flex-order: 1; 869 | order: 1; 870 | } 871 | .gslide-image img { 872 | max-height: 97vh; 873 | max-width: 100%; 874 | } 875 | .gslide-image img.zoomable { 876 | cursor: -webkit-zoom-in; 877 | cursor: zoom-in; 878 | } 879 | .zoomed .gslide-image img.zoomable { 880 | cursor: -webkit-grab; 881 | cursor: grab; 882 | } 883 | .gslide-inline { 884 | max-height: 95vh; 885 | } 886 | .gslide-external { 887 | max-height: 100vh; 888 | } 889 | .gslide-description.description-left, 890 | .gslide-description.description-right { 891 | max-width: 275px; 892 | } 893 | .glightbox-open { 894 | height: auto; 895 | } 896 | .goverlay { 897 | background: rgba(0, 0, 0, 0.92); 898 | } 899 | .glightbox-clean .gslide-media { 900 | -webkit-box-shadow: 1px 2px 9px 0px rgba(0, 0, 0, 0.65); 901 | box-shadow: 1px 2px 9px 0px rgba(0, 0, 0, 0.65); 902 | } 903 | .glightbox-clean .description-left .gdesc-inner, 904 | .glightbox-clean .description-right .gdesc-inner { 905 | position: absolute; 906 | height: 100%; 907 | overflow-y: auto; 908 | } 909 | .glightbox-clean .gprev, 910 | .glightbox-clean .gnext, 911 | .glightbox-clean .gclose { 912 | background-color: rgba(0, 0, 0, 0.32); 913 | } 914 | .glightbox-clean .gprev:hover, 915 | .glightbox-clean .gnext:hover, 916 | .glightbox-clean .gclose:hover { 917 | background-color: rgba(0, 0, 0, 0.7); 918 | } 919 | .glightbox-clean .gprev { 920 | top: 45%; 921 | } 922 | .glightbox-clean .gnext { 923 | top: 45%; 924 | } 925 | } 926 | 927 | @media (min-width: 992px) { 928 | .glightbox-clean .gclose { 929 | opacity: 0.7; 930 | right: 20px; 931 | } 932 | } 933 | 934 | @media screen and (max-height: 420px) { 935 | .goverlay { 936 | background: #000; 937 | } 938 | } 939 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | GLightbox | A touchable pure Javascript lightbox 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 |
28 | 29 | 35 |
36 |
37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 |

GLightbox.

47 |

48 | Code name 49 | "Gie" 50 | A touchable Pure Javascript 51 |
52 | lightbox with mobile and video support. 53 |

54 | 55 | 65 |
66 |
67 |
68 |
69 |
70 | 71 | 72 |
73 |
74 |
75 |
76 |

77 | 78 | Simple Image Gallery 79 |

80 | 81 | 125 |
126 |
127 |
128 |
129 | 130 | 131 |
132 |
133 |
134 |
135 |

136 | 137 | Images with Description 138 |

139 |

140 | You can add descriptions to your slides, the description position can be set globally for all slides or you can set a different position for each slide. options are: top, bottom, left or 141 | right. You can adjust the style the way you want with basic CSS. 142 | The description can display html code 143 | . 144 |

145 | 146 |
    147 |
  • 148 |
    149 | 154 | image 155 | 156 |
    157 |
  • 158 |
  • 159 |
    160 | 161 | image 162 | 163 | 164 |
    165 |

    166 | You can set the position of the description in different ways for example 167 | top, bottom, left or right 168 |

    169 |

    170 | Example Google link 171 | ipsum vehicula eros ultrices lacinia Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae Duis quis ipsum vehicula eros ultrices lacinia. 172 | Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere 173 |

    174 |

    175 | Primis pharetra facilisis lorem quis penatibus ad nulla inceptos, dui per tempor taciti aliquet consequat sodales, curae tristique gravida auctor interdum malesuada sagittis. 176 | Felis pretium eros ligula natoque ad ante rutrum himenaeos, adipiscing urna mauris porta quam efficitur odio, sagittis morbi tellus nisi molestie mus faucibus. 177 |

    178 |

    179 | Primis pharetra facilisis lorem quis penatibus ad nulla inceptos, dui per tempor taciti aliquet consequat sodales, curae tristique gravida auctor interdum malesuada sagittis. 180 | Felis pretium eros ligula natoque ad ante rutrum himenaeos, adipiscing urna mauris porta quam efficitur odio, sagittis morbi tellus nisi molestie mus faucibus. 181 |

    182 |
    183 |
    184 |
  • 185 |
  • 186 |
    187 | 188 | image 189 | 190 |
    191 |

    You can set the position of the description in different ways for example top, bottom, left or right

    192 |

    Duis quis ipsum vehicula eros ultrices lacinia. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae

    193 |
    194 |
    195 |
  • 196 |
197 |
198 |
199 |
200 |
201 | 202 | 203 |
204 |
205 |
206 |
207 |

208 | 209 | Videos Gallery 210 |

211 |

212 | You can add videos with optional autoplay for 213 | Vimeo 214 | , 215 | Youtube 216 | and 217 | self hosted videos 218 | . You can specify a default width for the videos or set different widths to each video in your gallery. The videos are 100% responsive and will play correctly on mobile devices. 219 |

220 | 221 | 244 |
245 |
246 |
247 |
248 | 249 | 250 |
251 |
252 |
253 |
254 |

255 | 256 | Iframes and Inline Elements 257 |

258 |

You can easily add iframes by simply entering the url, it could be a web page, a video, google maps, etc. also you can display any div of your page by entering the ID in the href attribute.

259 | 260 | 286 |
287 |
288 |
289 |
290 | 291 |
292 |
293 |
294 |
295 |
296 |

Technical specifications

297 |

GLightbox is built using es6 and transpiled with babel for older browsers and can be used with nodejs.

298 | 299 |

Animations

300 |

All the animations are created with CSS and only the transform and opacity properties are animated. You can overwrite the CSS of the animations or create your own.

301 | 302 |

Features

303 |
    304 |
  • Small - only 11KB Gzipped
  • 305 |
  • Responsive - works with any screen size
  • 306 |
  • Gallery Support - Create multiple galleries
  • 307 |
  • Video Support - Youtube, Vimeo and self hosted videos with autoplay
  • 308 |
  • Inline content support - display any inline content
  • 309 |
  • Iframe support - need to embed an iframe? no problem
  • 310 |
  • Keyboard Navigation - esc, arrows keys, tab and enter is all you need
  • 311 |
  • Touch Navigation - mobile touch events like swipe, move, pinch, etc.
  • 312 |
  • Zoomable images - zoom and drag images on mobile and desktop
  • 313 |
  • Retina ready - svg icons for controls.
  • 314 |
  • Api - control the lightbox with the provided methods
  • 315 |
316 | 317 |

Supported browsers and devices

318 |
    319 |
  • 320 | 321 | Safari 322 |
  • 323 |
  • 324 | 325 | Chrome 326 |
  • 327 |
  • 328 | 329 | Opera 330 |
  • 331 |
  • 332 | 333 | Firefox 334 |
  • 335 |
  • 336 | 337 | Edge 338 |
  • 339 |
  • 340 | 341 | Internet Explorer 11 342 |
  • 343 |
  • 344 | 345 | IOS (browser: safari, chrome, opera and firefox) 346 |
  • 347 |
  • 348 | 349 | Android (browser: chrome, opera and firefox) 350 |
  • 351 |
  • 352 | 353 | And any browser that supports flexbox and classList 354 |
  • 355 |
356 | 357 |

Documentation

358 |

359 | The documentation can be found in the github page. 360 | View Documentation 361 |

362 | 363 |
364 |

License

365 |

366 | GLightbox is free to use for personal and commercial projects. 367 | MIT license 368 |

369 |
370 |
371 | 372 | 382 |
383 |
384 |
385 |
386 | 387 | 400 | 401 | 411 | 412 | 413 | 414 | 415 | 416 | 476 | 477 | 478 | --------------------------------------------------------------------------------