├── src ├── index.css └── index.js ├── .gitignore ├── .gitattributes ├── docs ├── images │ ├── tibet-1.jpg │ ├── tibet-2.jpg │ ├── tibet-3.jpg │ ├── tibet-4.jpg │ ├── tibet-5.jpg │ ├── tibet-6.jpg │ ├── tibet-7.jpg │ ├── tibet-8.jpg │ ├── tibet-9.jpg │ └── thumbnails │ │ ├── tibet-1.jpg │ │ ├── tibet-2.jpg │ │ ├── tibet-3.jpg │ │ ├── tibet-4.jpg │ │ ├── tibet-5.jpg │ │ ├── tibet-6.jpg │ │ ├── tibet-7.jpg │ │ ├── tibet-8.jpg │ │ └── tibet-9.jpg ├── css │ ├── main.css │ └── viewer.css ├── js │ └── main.js └── index.html ├── .travis.yml ├── .editorconfig ├── .stylelintrc ├── babel.config.js ├── postcss.config.js ├── .eslintrc ├── LICENSE ├── karma.conf.js ├── rollup.config.js ├── test └── index.js ├── CHANGELOG.md ├── README.md ├── package.json └── dist ├── viewer.min.css ├── viewer.css ├── viewer.min.js └── viewer.esm.js /src/index.css: -------------------------------------------------------------------------------- 1 | @import 'viewerjs/src'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.local 2 | *.map 3 | coverage 4 | node_modules 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /docs/images/tibet-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/tibet-1.jpg -------------------------------------------------------------------------------- /docs/images/tibet-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/tibet-2.jpg -------------------------------------------------------------------------------- /docs/images/tibet-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/tibet-3.jpg -------------------------------------------------------------------------------- /docs/images/tibet-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/tibet-4.jpg -------------------------------------------------------------------------------- /docs/images/tibet-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/tibet-5.jpg -------------------------------------------------------------------------------- /docs/images/tibet-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/tibet-6.jpg -------------------------------------------------------------------------------- /docs/images/tibet-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/tibet-7.jpg -------------------------------------------------------------------------------- /docs/images/tibet-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/tibet-8.jpg -------------------------------------------------------------------------------- /docs/images/tibet-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/tibet-9.jpg -------------------------------------------------------------------------------- /docs/images/thumbnails/tibet-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/thumbnails/tibet-1.jpg -------------------------------------------------------------------------------- /docs/images/thumbnails/tibet-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/thumbnails/tibet-2.jpg -------------------------------------------------------------------------------- /docs/images/thumbnails/tibet-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/thumbnails/tibet-3.jpg -------------------------------------------------------------------------------- /docs/images/thumbnails/tibet-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/thumbnails/tibet-4.jpg -------------------------------------------------------------------------------- /docs/images/thumbnails/tibet-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/thumbnails/tibet-5.jpg -------------------------------------------------------------------------------- /docs/images/thumbnails/tibet-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/thumbnails/tibet-6.jpg -------------------------------------------------------------------------------- /docs/images/thumbnails/tibet-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/thumbnails/tibet-7.jpg -------------------------------------------------------------------------------- /docs/images/thumbnails/tibet-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/thumbnails/tibet-8.jpg -------------------------------------------------------------------------------- /docs/images/thumbnails/tibet-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengyuanchen/viewer/HEAD/docs/images/thumbnails/tibet-9.jpg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: node 3 | cache: npm 4 | script: 5 | - npm run lint 6 | - npm run build 7 | - npm test 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "plugins": [ 4 | "stylelint-order" 5 | ], 6 | "rules": { 7 | "no-descending-specificity": null, 8 | "order/properties-alphabetical-order": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | modules: false, 7 | }, 8 | ], 9 | ], 10 | env: { 11 | test: { 12 | plugins: [ 13 | 'istanbul', 14 | ], 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const rollupConfig = require('./rollup.config'); 2 | 3 | module.exports = { 4 | plugins: { 5 | 'postcss-import': {}, 6 | 'postcss-preset-env': { 7 | stage: 3, 8 | features: { 9 | 'nesting-rules': true, 10 | }, 11 | }, 12 | 'postcss-url': { 13 | url: 'inline', 14 | }, 15 | 'postcss-header': { 16 | header: rollupConfig.output[0].banner, 17 | }, 18 | stylelint: { 19 | fix: true, 20 | }, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "env": { 4 | "browser": true 5 | }, 6 | "root": true, 7 | "rules": { 8 | "import/no-extraneous-dependencies": "off", 9 | "no-param-reassign": "off", 10 | "no-restricted-properties": "off", 11 | "valid-jsdoc": ["error", { 12 | "requireReturn": false 13 | }] 14 | }, 15 | "overrides": [ 16 | { 17 | "files": "test/**/*.js", 18 | "env": { 19 | "jquery": true, 20 | "mocha": true 21 | }, 22 | "globals": { 23 | "expect": true 24 | }, 25 | "rules": { 26 | "no-unused-expressions": "off" 27 | } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2015-present Chen Fengyuan 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const babel = require('rollup-plugin-babel'); 2 | const commonjs = require('rollup-plugin-commonjs'); 3 | const nodeResolve = require('rollup-plugin-node-resolve'); 4 | const puppeteer = require('puppeteer'); 5 | 6 | process.env.CHROME_BIN = puppeteer.executablePath(); 7 | process.env.NODE_ENV = 'test'; 8 | 9 | module.exports = (config) => { 10 | config.set({ 11 | autoWatch: false, 12 | browsers: ['ChromeHeadless'], 13 | client: { 14 | mocha: { 15 | timeout: 10000, 16 | }, 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: ['html', 'lcovonly', 'text-summary'], 20 | }, 21 | files: [ 22 | 'dist/viewer.css', 23 | 'test/index.js', 24 | { 25 | pattern: 'docs/images/*', 26 | included: false, 27 | }, 28 | ], 29 | frameworks: ['mocha', 'chai'], 30 | preprocessors: { 31 | 'test/index.js': ['rollup'], 32 | }, 33 | reporters: ['mocha', 'coverage-istanbul'], 34 | rollupPreprocessor: { 35 | output: { 36 | format: 'iife', 37 | name: 'Anonymous', 38 | sourcemap: 'inline', 39 | }, 40 | plugins: [ 41 | nodeResolve(), 42 | commonjs(), 43 | babel(), 44 | ], 45 | }, 46 | singleRun: true, 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const babel = require('rollup-plugin-babel'); 2 | const commonjs = require('rollup-plugin-commonjs'); 3 | const nodeResolve = require('rollup-plugin-node-resolve'); 4 | const changeCase = require('change-case'); 5 | const createBanner = require('create-banner'); 6 | const pkg = require('./package'); 7 | 8 | pkg.name = pkg.name.replace('image', ''); 9 | 10 | const name = changeCase.pascalCase(pkg.name); 11 | const banner = createBanner({ 12 | data: { 13 | name, 14 | year: '2015-present', 15 | }, 16 | }); 17 | 18 | module.exports = { 19 | input: 'src/index.js', 20 | output: [ 21 | { 22 | banner, 23 | name, 24 | file: `dist/${pkg.name}.js`, 25 | format: 'umd', 26 | globals: { 27 | jquery: 'jQuery', 28 | }, 29 | }, 30 | { 31 | banner, 32 | file: `dist/${pkg.name}.common.js`, 33 | format: 'cjs', 34 | }, 35 | { 36 | banner, 37 | file: `dist/${pkg.name}.esm.js`, 38 | format: 'es', 39 | }, 40 | { 41 | banner, 42 | name, 43 | file: `docs/js/${pkg.name}.js`, 44 | format: 'umd', 45 | globals: { 46 | jquery: 'jQuery', 47 | }, 48 | }, 49 | ], 50 | external: ['jquery'], 51 | plugins: [ 52 | nodeResolve(), 53 | commonjs(), 54 | babel(), 55 | ], 56 | }; 57 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import Viewer from 'viewerjs/src'; 3 | 4 | if ($.fn) { 5 | const AnotherViewer = $.fn.viewer; 6 | const NAMESPACE = 'viewer'; 7 | 8 | $.fn.viewer = function jQueryViewer(option, ...args) { 9 | let result; 10 | 11 | this.each((i, element) => { 12 | const $element = $(element); 13 | const isDestroy = option === 'destroy'; 14 | let viewer = $element.data(NAMESPACE); 15 | 16 | if (!viewer) { 17 | if (isDestroy) { 18 | return; 19 | } 20 | 21 | const options = $.extend({}, $element.data(), $.isPlainObject(option) && option); 22 | 23 | viewer = new Viewer(element, options); 24 | $element.data(NAMESPACE, viewer); 25 | } 26 | 27 | if (typeof option === 'string') { 28 | const fn = viewer[option]; 29 | 30 | if ($.isFunction(fn)) { 31 | result = fn.apply(viewer, args); 32 | 33 | if (result === viewer) { 34 | result = undefined; 35 | } 36 | 37 | if (isDestroy) { 38 | $element.removeData(NAMESPACE); 39 | } 40 | } 41 | } 42 | }); 43 | 44 | return result !== undefined ? result : this; 45 | }; 46 | 47 | $.fn.viewer.Constructor = Viewer; 48 | $.fn.viewer.setDefaults = Viewer.setDefaults; 49 | $.fn.viewer.noConflict = function noConflict() { 50 | $.fn.viewer = AnotherViewer; 51 | return this; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import '../src'; 3 | 4 | describe('viewer', () => { 5 | const createImage = () => { 6 | const container = document.createElement('div'); 7 | const image = document.createElement('img'); 8 | 9 | image.src = '/base/docs/images/tibet-1.jpg'; 10 | container.appendChild(image); 11 | document.body.appendChild(container); 12 | 13 | return image; 14 | }; 15 | 16 | it('should register as a plugin correctly', () => { 17 | expect($.fn.viewer).to.be.a('function'); 18 | expect($.fn.viewer.Constructor).to.be.a('function'); 19 | expect($.fn.viewer.noConflict).to.be.a('function'); 20 | expect($.fn.viewer.setDefaults).to.be.a('function'); 21 | }); 22 | 23 | it('should remove data after destroyed', () => { 24 | const $image = $(createImage()); 25 | 26 | $image.viewer(); 27 | expect($image.data('viewer')).to.be.an.instanceof($.fn.viewer.Constructor); 28 | $image.viewer('destroy'); 29 | expect($image.data('viewer')).to.be.undefined; 30 | }); 31 | 32 | it('should apply the given option', (done) => { 33 | $(createImage()).viewer({ 34 | inline: true, 35 | 36 | ready() { 37 | done(); 38 | }, 39 | }); 40 | }); 41 | 42 | it('should execute the given method', (done) => { 43 | $(createImage()).viewer({ 44 | shown() { 45 | done(); 46 | }, 47 | }).viewer('show'); 48 | }); 49 | 50 | it('should trigger the binding event', (done) => { 51 | $(createImage()).one('ready', (event) => { 52 | expect(event.type).to.equal('ready'); 53 | done(); 54 | }).viewer('show'); 55 | }); 56 | 57 | it('should rollback when call the $.fn.viewer.conflict', () => { 58 | const { viewer } = $.fn; 59 | const noConflictViewer = $.fn.viewer.noConflict(); 60 | 61 | expect(noConflictViewer).to.equal(viewer); 62 | expect($.fn.viewer).to.be.undefined; 63 | 64 | // Reverts it for the rest test suites 65 | $.fn.viewer = noConflictViewer; 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /docs/css/main.css: -------------------------------------------------------------------------------- 1 | .btn { 2 | padding-left: 0.75rem; 3 | padding-right: 0.75rem; 4 | } 5 | 6 | .list-group-item { 7 | padding: 0.0625rem 1rem; 8 | } 9 | 10 | .d-flex > .btn { 11 | flex: 1; 12 | } 13 | 14 | .carbonads { 15 | border: 1px solid #ccc; 16 | border-radius: 0.25rem; 17 | font-size: 0.875rem; 18 | overflow: hidden; 19 | padding: 1rem; 20 | } 21 | 22 | .carbon-wrap { 23 | overflow: hidden; 24 | } 25 | 26 | .carbon-img { 27 | clear: left; 28 | display: block; 29 | float: left; 30 | } 31 | 32 | .carbon-text, 33 | .carbon-poweredby { 34 | display: block; 35 | margin-left: 140px; 36 | } 37 | 38 | .carbon-text, 39 | .carbon-text:hover, 40 | .carbon-text:focus { 41 | color: #fff; 42 | text-decoration: none; 43 | } 44 | 45 | .carbon-poweredby, 46 | .carbon-poweredby:hover, 47 | .carbon-poweredby:focus { 48 | color: #ddd; 49 | text-decoration: none; 50 | } 51 | 52 | @media (min-width: 768px) { 53 | .carbonads { 54 | float: right; 55 | margin-bottom: -1rem; 56 | margin-top: -1rem; 57 | max-width: 360px; 58 | } 59 | } 60 | 61 | .footer { 62 | font-size: 0.875rem; 63 | } 64 | 65 | .heart { 66 | color: #ddd; 67 | display: block; 68 | height: 2rem; 69 | line-height: 2rem; 70 | margin-bottom: 0; 71 | margin-top: 1rem; 72 | position: relative; 73 | text-align: center; 74 | width: 100%; 75 | } 76 | 77 | .heart:hover { 78 | color: #ff4136; 79 | } 80 | 81 | .heart::before { 82 | border-top: 1px solid #eee; 83 | content: " "; 84 | display: block; 85 | height: 0; 86 | left: 0; 87 | position: absolute; 88 | right: 0; 89 | top: 50%; 90 | } 91 | 92 | .heart::after { 93 | background-color: #fff; 94 | content: "♥"; 95 | padding-left: 0.5rem; 96 | padding-right: 0.5rem; 97 | position: relative; 98 | z-index: 1; 99 | } 100 | 101 | .docs-pictures { 102 | list-style: none; 103 | margin: 0; 104 | padding: 0; 105 | } 106 | 107 | .docs-pictures > li { 108 | border: 1px solid transparent; 109 | float: left; 110 | height: calc(100% / 3); 111 | margin: 0 -1px -1px 0; 112 | overflow: hidden; 113 | width: calc(100% / 3); 114 | } 115 | 116 | .docs-pictures > li > img { 117 | cursor: -webkit-zoom-in; 118 | cursor: zoom-in; 119 | width: 100%; 120 | } 121 | 122 | .docs-buttons > .btn-group, 123 | .docs-buttons > .input-group { 124 | margin-bottom: 5px; 125 | width: 100%; 126 | } 127 | 128 | .docs-buttons .input-group-prepend { 129 | width: 50%; 130 | } 131 | 132 | .docs-buttons .input-group-prepend .btn { 133 | width: 100%; 134 | } 135 | -------------------------------------------------------------------------------- /docs/js/main.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 'use strict'; 3 | 4 | var console = window.console || { log: function () {} }; 5 | var $images = $('.docs-pictures'); 6 | var $toggles = $('.docs-toggles'); 7 | var $buttons = $('.docs-buttons'); 8 | var options = { 9 | // inline: true, 10 | url: 'data-original', 11 | ready: function (e) { 12 | console.log(e.type); 13 | }, 14 | show: function (e) { 15 | console.log(e.type); 16 | }, 17 | shown: function (e) { 18 | console.log(e.type); 19 | }, 20 | hide: function (e) { 21 | console.log(e.type); 22 | }, 23 | hidden: function (e) { 24 | console.log(e.type); 25 | }, 26 | view: function (e) { 27 | console.log(e.type); 28 | }, 29 | viewed: function (e) { 30 | console.log(e.type); 31 | } 32 | }; 33 | 34 | function toggleButtons(mode) { 35 | if (/modal|inline|none/.test(mode)) { 36 | $buttons 37 | .find('button[data-enable]') 38 | .prop('disabled', true) 39 | .filter('[data-enable*="' + mode + '"]') 40 | .prop('disabled', false); 41 | } 42 | } 43 | 44 | $images.on({ 45 | ready: function (e) { 46 | console.log(e.type); 47 | }, 48 | show: function (e) { 49 | console.log(e.type); 50 | }, 51 | shown: function (e) { 52 | console.log(e.type); 53 | }, 54 | hide: function (e) { 55 | console.log(e.type); 56 | }, 57 | hidden: function (e) { 58 | console.log(e.type); 59 | }, 60 | view: function (e) { 61 | console.log(e.type); 62 | }, 63 | viewed: function (e) { 64 | console.log(e.type); 65 | } 66 | }).viewer(options); 67 | 68 | toggleButtons(options.inline ? 'inline' : 'modal'); 69 | 70 | $toggles.on('change', 'input', function () { 71 | var $input = $(this); 72 | var name = $input.attr('name'); 73 | 74 | options[name] = name === 'inline' ? $input.data('value') : $input.prop('checked'); 75 | $images.viewer('destroy').viewer(options); 76 | toggleButtons(options.inline ? 'inline' : 'modal'); 77 | }); 78 | 79 | $buttons.on('click', 'button', function () { 80 | var data = $(this).data(); 81 | var args = data.arguments || []; 82 | 83 | if (data.method) { 84 | if (data.target) { 85 | $images.viewer(data.method, $(data.target).val()); 86 | } else { 87 | $images.viewer(data.method, args[0], args[1]); 88 | } 89 | 90 | switch (data.method) { 91 | case 'scaleX': 92 | case 'scaleY': 93 | args[0] = -args[0]; 94 | break; 95 | 96 | case 'destroy': 97 | toggleButtons('none'); 98 | break; 99 | } 100 | } 101 | }); 102 | 103 | $('[data-toggle="tooltip"]').tooltip(); 104 | }); 105 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.0 (Dec 14 2019) 4 | 5 | - Upgrade the built-in Viewer.js to v1.5.0. 6 | 7 | ## 1.0.0 (Apr 1, 2018) 8 | 9 | - Upgrade Viewer.js to v1.0.0. 10 | 11 | ## 1.0.0-beta (Mar 15, 2018) 12 | 13 | - Upgrade Viewer.js to v1.0.0-rc.1. 14 | 15 | ## 1.0.0-alpha (Mar 11, 2018) 16 | 17 | - The core code of Viewer is replaced with [Viewer.js](https://github.com/fengyuanchen/viewerjs) now. 18 | 19 | ## 0.7.0 (Mar 11, 2018) 20 | 21 | - Emulate scroll bar width when modal opening. 22 | - Disallow to show again if it had shown. 23 | 24 | ## 0.6.0 (Oct 7, 2017) 25 | 26 | - Refactor in ES6. 27 | - Build CSS code with PostCSS. 28 | - Removed `build` event. 29 | - Renamed `built` event to `ready`. 30 | - Removed event namespace. 31 | 32 | ## 0.5.1 (Mar 11, 2016) 33 | 34 | - Fixed the issue of the "button" option (#8). 35 | - Fixed the issue of the "$.fn.viewer.setDefault" static method (#9). 36 | 37 | ## 0.5.0 (Jan 21, 2016) 38 | 39 | - Add more available values to the "title", "toolbar" and "navbar" options. 40 | - Support to toggle the visibility of title, toolbar and navbar between different screen widths. 41 | - Exit fullscreen when stop playing. 42 | - Fixed title not generated bug (#6). 43 | 44 | ## 0.4.0 (Jan 1, 2016) 45 | 46 | - Added "update" method for update image dynamically. 47 | - Hides title and toolbar on small screen (width < 768px). 48 | 49 | ## 0.3.1 (Dec 28, 2015) 50 | 51 | - Supports to zoom from event triggering point. 52 | - Fix a bug of the index of viewing image. 53 | 54 | ## 0.3.0 (Dec 24, 2015) 55 | 56 | - Add 2 new options: "view" and "viewed" 57 | - Add 2 new events: "view" and "viewed" 58 | - Add keyboard support: stop playing when tap the `Space` key 59 | - Fix lost transition after call full method in inline mode 60 | - Fix incorrect tooltip after switch image quickly 61 | 62 | ## 0.2.0 (Oct 18, 2015) 63 | 64 | - Added one new method: "moveTo" 65 | - Improved the image loading and showing 66 | 67 | ## 0.1.1 (Oct 7, 2015) 68 | 69 | - Fixed the issue of modal closing after zoomed in and zoomed out 70 | 71 | ## 0.1.0 (Sep 2, 2015) 72 | 73 | - Supports 2 modes: "modal" (default), "inline" 74 | - Supports 28 options: "inline", "button", "navbar", "title", "toolbar", "tooltip", "movable", "zoomable", "rotatable", "scalable", "transition", "fullscreen", "keyboard", "interval", "minWidth", "minHeight", "zoomRatio", "minZoomRatio", "maxZoomRatio", "zIndex", "zIndexInline", "url", "build", "built", "show", "shown", "hide", "hidden" 75 | - Supports 21 methods: "show", "hide", "view", "prev", "next", "move", "zoom", "zoomTo", "rotate", "rotateTo", "scale", "scaleX", "scaleY", "play", "stop", "full", "exit", "tooltip", "toggle", "reset", "destroy" 76 | - Supports 6 events: "build.viewer", "built.viewer", "show.viewer", "shown.viewer", "hide.viewer", "hidden.viewer" 77 | 78 | ## 0.0.1 (Apr 19, 2015) 79 | 80 | - Improve UI 81 | - Develop a alpha version 82 | 83 | ## 0.0.0 (Apr 19, 2014) 84 | 85 | - Design UI 86 | - Develop a draft version 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Viewer 2 | 3 | [![Build Status](https://img.shields.io/travis/fengyuanchen/viewer.svg)](https://travis-ci.org/fengyuanchen/viewer) [![Downloads](https://img.shields.io/npm/dm/imageviewer.svg)](https://www.npmjs.com/package/imageviewer) [![Version](https://img.shields.io/npm/v/imageviewer.svg)](https://www.npmjs.com/package/imageviewer) [![Dependencies](https://img.shields.io/david/fengyuanchen/viewer.svg)](https://www.npmjs.com/package/imageviewer) 4 | 5 | > A simple jQuery image viewing plugin. As of v1.0.0, the core code of Viewer is replaced with [Viewer.js](https://github.com/fengyuanchen/viewerjs). 6 | 7 | - [Demo](https://fengyuanchen.github.io/viewer) 8 | - [Viewer.js](https://github.com/fengyuanchen/viewerjs) - JavaScript image viewer (**recommended**) 9 | - [jquery-viewer](https://github.com/fengyuanchen/jquery-viewer) - A jQuery plugin wrapper for Viewer.js (**recommended** for jQuery users to use this instead of Viewer) 10 | 11 | ## Main 12 | 13 | ```text 14 | dist/ 15 | ├── viewer.css 16 | ├── viewer.min.css (compressed) 17 | ├── viewer.js (UMD) 18 | ├── viewer.min.js (UMD, compressed) 19 | ├── viewer.common.js (CommonJS, default) 20 | └── viewer.esm.js (ES Module) 21 | ``` 22 | 23 | ## Getting started 24 | 25 | ### Installation 26 | 27 | ```shell 28 | npm install imageviewer jquery 29 | ``` 30 | 31 | Include files: 32 | 33 | ```html 34 | 35 | 36 | 37 | ``` 38 | 39 | ### Usage 40 | 41 | Initialize with `$.fn.viewer` method. 42 | 43 | ```html 44 | 45 |
46 | Picture 47 |
48 | 49 |
50 | 55 |
56 | ``` 57 | 58 | ```js 59 | var $image = $('#image'); 60 | 61 | $image.viewer({ 62 | inline: true, 63 | viewed: function() { 64 | $image.viewer('zoomTo', 1); 65 | } 66 | }); 67 | 68 | // Get the Viewer.js instance after initialized 69 | var viewer = $image.data('viewer'); 70 | 71 | // View a list of images 72 | $('#images').viewer(); 73 | ``` 74 | 75 | ## Options 76 | 77 | See the available [options](https://github.com/fengyuanchen/viewerjs#options) of Viewer.js. 78 | 79 | ```js 80 | $().viewer(options); 81 | ``` 82 | 83 | ## Methods 84 | 85 | See the available [methods](https://github.com/fengyuanchen/viewerjs#methods) of Viewer.js. 86 | 87 | ```js 88 | $().viewer('method', argument1, , argument2, ..., argumentN); 89 | ``` 90 | 91 | ## Events 92 | 93 | See the available [events](https://github.com/fengyuanchen/viewerjs#events) of Viewer.js. 94 | 95 | ```js 96 | $().on('event', handler); 97 | ``` 98 | 99 | ## No conflict 100 | 101 | If you have to use other plugin with the same namespace, just call the `$.fn.viewer.noConflict` method to revert to it. 102 | 103 | ```html 104 | 105 | 106 | 110 | ``` 111 | 112 | ## Browser support 113 | 114 | It is the same as the [browser support of Viewer.js](https://github.com/fengyuanchen/viewerjs#browser-support). As a jQuery plugin, you also need to see the [jQuery Browser Support](https://jquery.com/browser-support/). 115 | 116 | ## Versioning 117 | 118 | Maintained under the [Semantic Versioning guidelines](https://semver.org/). 119 | 120 | ## License 121 | 122 | [MIT](https://opensource.org/licenses/MIT) © [Chen Fengyuan](https://chenfengyuan.com/) 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imageviewer", 3 | "description": "A simple jQuery image viewing plugin.", 4 | "version": "1.1.0", 5 | "main": "dist/viewer.common.js", 6 | "module": "dist/viewer.esm.js", 7 | "browser": "dist/viewer.js", 8 | "style": "dist/viewer.css", 9 | "files": [ 10 | "src", 11 | "dist" 12 | ], 13 | "scripts": { 14 | "build": "npm run build:css && npm run build:js", 15 | "build:css": "postcss src/index.css -o dist/viewer.css --no-map", 16 | "build:js": "rollup -c", 17 | "clear": "del-cli dist", 18 | "compress": "npm run compress:css && npm run compress:js", 19 | "compress:css": "postcss dist/viewer.css -u cssnano -o dist/viewer.min.css --no-map", 20 | "compress:js": "uglifyjs dist/viewer.js -o dist/viewer.min.js -c -m --comments /^!/", 21 | "copy": "cpy dist/viewer.css docs/css", 22 | "lint": "eslint src test *.js --fix", 23 | "release": "npm run clear && npm run lint && npm run build && npm run compress && npm run copy && npm test", 24 | "start": "npm-run-all --parallel watch:*", 25 | "test": "karma start", 26 | "watch:css": "postcss src/index.css -o docs/css/viewer.css -m -w", 27 | "watch:js": "rollup -c -m -w" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/fengyuanchen/viewer.git" 32 | }, 33 | "keywords": [ 34 | "image", 35 | "viewer", 36 | "jquery", 37 | "jquery-plugin" 38 | ], 39 | "author": { 40 | "name": "Chen Fengyuan", 41 | "url": "https://chenfengyuan.com/" 42 | }, 43 | "license": "MIT", 44 | "bugs": { 45 | "url": "https://github.com/fengyuanchen/viewer/issues" 46 | }, 47 | "homepage": "https://fengyuanchen.github.io/viewer", 48 | "dependencies": { 49 | "viewerjs": "^1.5.0" 50 | }, 51 | "devDependencies": { 52 | "@babel/core": "^7.7.5", 53 | "@babel/preset-env": "^7.7.6", 54 | "@commitlint/cli": "^8.2.0", 55 | "@commitlint/config-conventional": "^8.2.0", 56 | "babel-plugin-istanbul": "^5.2.0", 57 | "chai": "^4.2.0", 58 | "change-case": "^4.1.0", 59 | "cpy-cli": "^3.0.0", 60 | "create-banner": "^1.0.0", 61 | "cssnano": "^4.1.10", 62 | "del-cli": "^3.0.0", 63 | "eslint": "^6.7.2", 64 | "eslint-config-airbnb-base": "^14.0.0", 65 | "eslint-plugin-import": "^2.19.1", 66 | "husky": "^3.1.0", 67 | "jquery": "^3.4.1", 68 | "karma": "^4.4.1", 69 | "karma-chai": "^0.1.0", 70 | "karma-chrome-launcher": "^3.1.0", 71 | "karma-coverage-istanbul-reporter": "^2.1.1", 72 | "karma-mocha": "^1.3.0", 73 | "karma-mocha-reporter": "^2.2.5", 74 | "karma-rollup-preprocessor": "^7.0.2", 75 | "lint-staged": "^9.5.0", 76 | "mocha": "^6.2.2", 77 | "npm-run-all": "^4.1.5", 78 | "postcss-cli": "^6.1.3", 79 | "postcss-header": "^1.0.0", 80 | "postcss-import": "^12.0.1", 81 | "postcss-preset-env": "^6.7.0", 82 | "postcss-url": "^8.0.0", 83 | "puppeteer": "^2.0.0", 84 | "rollup": "^1.27.12", 85 | "rollup-plugin-babel": "^4.3.3", 86 | "rollup-plugin-commonjs": "^10.1.0", 87 | "rollup-plugin-node-resolve": "^5.2.0", 88 | "stylelint": "^12.0.0", 89 | "stylelint-config-standard": "^19.0.0", 90 | "stylelint-order": "^3.1.1", 91 | "uglify-js": "^3.7.2" 92 | }, 93 | "peerDependencies": { 94 | "jquery": ">=1.9.1" 95 | }, 96 | "browserslist": [ 97 | "last 2 versions", 98 | "> 1%", 99 | "not ie <= 8" 100 | ], 101 | "commitlint": { 102 | "extends": [ 103 | "@commitlint/config-conventional" 104 | ] 105 | }, 106 | "husky": { 107 | "hooks": { 108 | "pre-commit": "lint-staged", 109 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 110 | } 111 | }, 112 | "lint-staged": { 113 | "{src,test}/**/*.js|*.conf*.js": [ 114 | "eslint --fix", 115 | "git add" 116 | ], 117 | "{src,docs}/**/*.{css,html}": [ 118 | "stylelint --fix", 119 | "git add" 120 | ] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /dist/viewer.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Viewer v1.1.0 3 | * https://fengyuanchen.github.io/viewer 4 | * 5 | * Copyright 2015-present Chen Fengyuan 6 | * Released under the MIT license 7 | * 8 | * Date: 2019-12-14T11:48:37.240Z 9 | */.viewer-close:before,.viewer-flip-horizontal:before,.viewer-flip-vertical:before,.viewer-fullscreen-exit:before,.viewer-fullscreen:before,.viewer-next:before,.viewer-one-to-one:before,.viewer-play:before,.viewer-prev:before,.viewer-reset:before,.viewer-rotate-left:before,.viewer-rotate-right:before,.viewer-zoom-in:before,.viewer-zoom-out:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAAUCAYAAABWOyJDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAQPSURBVHic7Zs/iFxVFMa/0U2UaJGksUgnIVhYxVhpjDbZCBmLdAYECxsRFBTUamcXUiSNncgKQbSxsxH8gzAP3FU2jY0kKKJNiiiIghFlccnP4p3nPCdv3p9778vsLOcHB2bfveeb7955c3jvvNkBIMdxnD64a94GHMfZu3iBcRynN7zAOI7TG15gHCeeNUkr8zaxG2lbYDYsdgMbktBsP03jdQwljSXdtBhLOmtjowC9Mg9L+knSlcD8TNKpSA9lBpK2JF2VdDSR5n5J64m0qli399hNFMUlpshQii5jbXTbHGviB0nLNeNDSd9VO4A2UdB2fp+x0eCnaXxWXGA2X0au/3HgN9P4LFCjIANOJdrLr0zzZ+BEpNYDwKbpnQMeAw4m8HjQtM6Z9qa917zPQwFr3M5KgA6J5rTJCdFZJj9/lyvGhsDvwFNVuV2MhhjrK6b9bFiE+j1r87eBl4HDwCF7/U/k+ofAX5b/EXBv5JoLMuILzf3Ap6Z3EzgdqHMCuF7hcQf4HDgeoHnccncqdK/TvSDWffFXI/exICY/xZyqc6XLWF1UFZna4gJ7q8BsRvgd2/xXpo6P+D9dfT7PpECtA3cnWPM0GXGFZh/wgWltA+cDNC7X+AP4GzjZQe+k5dRxuYPeiuXU7e1qwLpDz7dFjXKRaSwuMLvAlG8zZlG+YmiK1HoFqT7wP2z+4Q45TfEGcMt01xLoNZEBTwRqD4BLpnMLeC1A41UmVxsXgXeBayV/Wx20rpTyrpnWRft7p6O/FdqzGrDukPNtkaMoMo3FBdBSQMOnYBCReyf05s126fU9ytfX98+mY54Kxnp7S9K3kj6U9KYdG0h6UdLbkh7poFXMfUnSOyVvL0h6VtIXHbS6nOP+s/Zm9mvyXW1uuC9ohZ72E9uDmXWLJOB1GxsH+DxPftsB8B6wlGDN02TAkxG6+4D3TWsbeC5CS8CDFce+AW500LhhOW2020TRjK3b21HEmgti9m0RonxbdMZeVzV+/4tF3cBpP7E9mKHNL5q8h5g0eYsCMQz0epq8gQrwMXAgcs0FGXGFRcB9wCemF9PkbYqM/Bas7fxLwNeJPdTdpo4itQti8lPMqTpXuozVRVXPpbHI3KkNTB1NfkL81j2mvhDp91HgV9MKuRIqrykj3WPq4rHyL+axj8/qGPmTqi6F9YDlHOvJU6oYcTsh/TYSzWmTE6JT19CtLTJt32D6CmHe0eQn1O8z5AXgT4sx4Vcu0/EQecMydB8z0hUWkTd2t4CrwNEePqMBcAR4mrBbwyXLPWJa8zrXmmLEhNBmfpkuY2102xxrih+pb+ieAb6vGhuA97UcJ5KR8gZ77K+99xxeYBzH6Q3/Z0fHcXrDC4zjOL3hBcZxnN74F+zlvXFWXF9PAAAAAElFTkSuQmCC");background-repeat:no-repeat;background-size:280px;color:transparent;display:block;font-size:0;height:20px;line-height:0;width:20px}.viewer-zoom-in:before{background-position:0 0;content:"Zoom In"}.viewer-zoom-out:before{background-position:-20px 0;content:"Zoom Out"}.viewer-one-to-one:before{background-position:-40px 0;content:"One to One"}.viewer-reset:before{background-position:-60px 0;content:"Reset"}.viewer-prev:before{background-position:-80px 0;content:"Previous"}.viewer-play:before{background-position:-100px 0;content:"Play"}.viewer-next:before{background-position:-120px 0;content:"Next"}.viewer-rotate-left:before{background-position:-140px 0;content:"Rotate Left"}.viewer-rotate-right:before{background-position:-160px 0;content:"Rotate Right"}.viewer-flip-horizontal:before{background-position:-180px 0;content:"Flip Horizontal"}.viewer-flip-vertical:before{background-position:-200px 0;content:"Flip Vertical"}.viewer-fullscreen:before{background-position:-220px 0;content:"Enter Full Screen"}.viewer-fullscreen-exit:before{background-position:-240px 0;content:"Exit Full Screen"}.viewer-close:before{background-position:-260px 0;content:"Close"}.viewer-container{bottom:0;direction:ltr;font-size:0;left:0;line-height:0;overflow:hidden;position:absolute;right:0;-webkit-tap-highlight-color:transparent;top:0;-ms-touch-action:none;touch-action:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.viewer-container::-moz-selection,.viewer-container ::-moz-selection{background-color:transparent}.viewer-container::selection,.viewer-container ::selection{background-color:transparent}.viewer-container img{display:block;height:auto;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.viewer-canvas{bottom:0;left:0;overflow:hidden;position:absolute;right:0;top:0}.viewer-canvas>img{height:auto;margin:15px auto;max-width:90%!important;width:auto}.viewer-footer{bottom:0;left:0;overflow:hidden;position:absolute;right:0;text-align:center}.viewer-navbar{background-color:rgba(0,0,0,.5);overflow:hidden}.viewer-list{-webkit-box-sizing:content-box;box-sizing:content-box;height:50px;margin:0;overflow:hidden;padding:1px 0}.viewer-list>li{color:transparent;cursor:pointer;float:left;font-size:0;height:50px;line-height:0;opacity:.5;overflow:hidden;-webkit-transition:opacity .15s;transition:opacity .15s;width:30px}.viewer-list>li:hover{opacity:.75}.viewer-list>li+li{margin-left:1px}.viewer-list>.viewer-loading{position:relative}.viewer-list>.viewer-loading:after{border-width:2px;height:20px;margin-left:-10px;margin-top:-10px;width:20px}.viewer-list>.viewer-active,.viewer-list>.viewer-active:hover{opacity:1}.viewer-player{background-color:#000;bottom:0;cursor:none;display:none;right:0}.viewer-player,.viewer-player>img{left:0;position:absolute;top:0}.viewer-toolbar>ul{display:inline-block;margin:0 auto 5px;overflow:hidden;padding:3px 0}.viewer-toolbar>ul>li{background-color:rgba(0,0,0,.5);border-radius:50%;cursor:pointer;float:left;height:24px;overflow:hidden;-webkit-transition:background-color .15s;transition:background-color .15s;width:24px}.viewer-toolbar>ul>li:hover{background-color:rgba(0,0,0,.8)}.viewer-toolbar>ul>li:before{margin:2px}.viewer-toolbar>ul>li+li{margin-left:1px}.viewer-toolbar>ul>.viewer-small{height:18px;margin-bottom:3px;margin-top:3px;width:18px}.viewer-toolbar>ul>.viewer-small:before{margin:-1px}.viewer-toolbar>ul>.viewer-large{height:30px;margin-bottom:-3px;margin-top:-3px;width:30px}.viewer-toolbar>ul>.viewer-large:before{margin:5px}.viewer-tooltip{background-color:rgba(0,0,0,.8);border-radius:10px;color:#fff;display:none;font-size:12px;height:20px;left:50%;line-height:20px;margin-left:-25px;margin-top:-10px;position:absolute;text-align:center;top:50%;width:50px}.viewer-title{color:#ccc;display:inline-block;font-size:12px;line-height:1;margin:0 5% 5px;max-width:90%;opacity:.8;overflow:hidden;text-overflow:ellipsis;-webkit-transition:opacity .15s;transition:opacity .15s;white-space:nowrap}.viewer-title:hover{opacity:1}.viewer-button{background-color:rgba(0,0,0,.5);border-radius:50%;cursor:pointer;height:80px;overflow:hidden;position:absolute;right:-40px;top:-40px;-webkit-transition:background-color .15s;transition:background-color .15s;width:80px}.viewer-button:focus,.viewer-button:hover{background-color:rgba(0,0,0,.8)}.viewer-button:before{bottom:15px;left:15px;position:absolute}.viewer-fixed{position:fixed}.viewer-open{overflow:hidden}.viewer-show{display:block}.viewer-hide{display:none}.viewer-backdrop{background-color:rgba(0,0,0,.5)}.viewer-invisible{visibility:hidden}.viewer-move{cursor:move;cursor:-webkit-grab;cursor:grab}.viewer-fade{opacity:0}.viewer-in{opacity:1}.viewer-transition{-webkit-transition:all .3s;transition:all .3s}@-webkit-keyframes viewer-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes viewer-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.viewer-loading:after{-webkit-animation:viewer-spinner 1s linear infinite;animation:viewer-spinner 1s linear infinite;border:4px solid hsla(0,0%,100%,.1);border-left-color:hsla(0,0%,100%,.5);border-radius:50%;content:"";display:inline-block;height:40px;left:50%;margin-left:-20px;margin-top:-20px;position:absolute;top:50%;width:40px;z-index:1}@media (max-width:767px){.viewer-hide-xs-down{display:none}}@media (max-width:991px){.viewer-hide-sm-down{display:none}}@media (max-width:1199px){.viewer-hide-md-down{display:none}} -------------------------------------------------------------------------------- /dist/viewer.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Viewer v1.1.0 3 | * https://fengyuanchen.github.io/viewer 4 | * 5 | * Copyright 2015-present Chen Fengyuan 6 | * Released under the MIT license 7 | * 8 | * Date: 2019-12-14T11:48:37.240Z 9 | */ 10 | 11 | .viewer-zoom-in::before, 12 | .viewer-zoom-out::before, 13 | .viewer-one-to-one::before, 14 | .viewer-reset::before, 15 | .viewer-prev::before, 16 | .viewer-play::before, 17 | .viewer-next::before, 18 | .viewer-rotate-left::before, 19 | .viewer-rotate-right::before, 20 | .viewer-flip-horizontal::before, 21 | .viewer-flip-vertical::before, 22 | .viewer-fullscreen::before, 23 | .viewer-fullscreen-exit::before, 24 | .viewer-close::before { 25 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAAUCAYAAABWOyJDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAQPSURBVHic7Zs/iFxVFMa/0U2UaJGksUgnIVhYxVhpjDbZCBmLdAYECxsRFBTUamcXUiSNncgKQbSxsxH8gzAP3FU2jY0kKKJNiiiIghFlccnP4p3nPCdv3p9778vsLOcHB2bfveeb7955c3jvvNkBIMdxnD64a94GHMfZu3iBcRynN7zAOI7TG15gHCeeNUkr8zaxG2lbYDYsdgMbktBsP03jdQwljSXdtBhLOmtjowC9Mg9L+knSlcD8TNKpSA9lBpK2JF2VdDSR5n5J64m0qli399hNFMUlpshQii5jbXTbHGviB0nLNeNDSd9VO4A2UdB2fp+x0eCnaXxWXGA2X0au/3HgN9P4LFCjIANOJdrLr0zzZ+BEpNYDwKbpnQMeAw4m8HjQtM6Z9qa917zPQwFr3M5KgA6J5rTJCdFZJj9/lyvGhsDvwFNVuV2MhhjrK6b9bFiE+j1r87eBl4HDwCF7/U/k+ofAX5b/EXBv5JoLMuILzf3Ap6Z3EzgdqHMCuF7hcQf4HDgeoHnccncqdK/TvSDWffFXI/exICY/xZyqc6XLWF1UFZna4gJ7q8BsRvgd2/xXpo6P+D9dfT7PpECtA3cnWPM0GXGFZh/wgWltA+cDNC7X+AP4GzjZQe+k5dRxuYPeiuXU7e1qwLpDz7dFjXKRaSwuMLvAlG8zZlG+YmiK1HoFqT7wP2z+4Q45TfEGcMt01xLoNZEBTwRqD4BLpnMLeC1A41UmVxsXgXeBayV/Wx20rpTyrpnWRft7p6O/FdqzGrDukPNtkaMoMo3FBdBSQMOnYBCReyf05s126fU9ytfX98+mY54Kxnp7S9K3kj6U9KYdG0h6UdLbkh7poFXMfUnSOyVvL0h6VtIXHbS6nOP+s/Zm9mvyXW1uuC9ohZ72E9uDmXWLJOB1GxsH+DxPftsB8B6wlGDN02TAkxG6+4D3TWsbeC5CS8CDFce+AW500LhhOW2020TRjK3b21HEmgti9m0RonxbdMZeVzV+/4tF3cBpP7E9mKHNL5q8h5g0eYsCMQz0epq8gQrwMXAgcs0FGXGFRcB9wCemF9PkbYqM/Bas7fxLwNeJPdTdpo4itQti8lPMqTpXuozVRVXPpbHI3KkNTB1NfkL81j2mvhDp91HgV9MKuRIqrykj3WPq4rHyL+axj8/qGPmTqi6F9YDlHOvJU6oYcTsh/TYSzWmTE6JT19CtLTJt32D6CmHe0eQn1O8z5AXgT4sx4Vcu0/EQecMydB8z0hUWkTd2t4CrwNEePqMBcAR4mrBbwyXLPWJa8zrXmmLEhNBmfpkuY2102xxrih+pb+ieAb6vGhuA97UcJ5KR8gZ77K+99xxeYBzH6Q3/Z0fHcXrDC4zjOL3hBcZxnN74F+zlvXFWXF9PAAAAAElFTkSuQmCC'); 26 | background-repeat: no-repeat; 27 | background-size: 280px; 28 | color: transparent; 29 | display: block; 30 | font-size: 0; 31 | height: 20px; 32 | line-height: 0; 33 | width: 20px; 34 | } 35 | 36 | .viewer-zoom-in::before { 37 | background-position: 0 0; 38 | content: 'Zoom In'; 39 | } 40 | 41 | .viewer-zoom-out::before { 42 | background-position: -20px 0; 43 | content: 'Zoom Out'; 44 | } 45 | 46 | .viewer-one-to-one::before { 47 | background-position: -40px 0; 48 | content: 'One to One'; 49 | } 50 | 51 | .viewer-reset::before { 52 | background-position: -60px 0; 53 | content: 'Reset'; 54 | } 55 | 56 | .viewer-prev::before { 57 | background-position: -80px 0; 58 | content: 'Previous'; 59 | } 60 | 61 | .viewer-play::before { 62 | background-position: -100px 0; 63 | content: 'Play'; 64 | } 65 | 66 | .viewer-next::before { 67 | background-position: -120px 0; 68 | content: 'Next'; 69 | } 70 | 71 | .viewer-rotate-left::before { 72 | background-position: -140px 0; 73 | content: 'Rotate Left'; 74 | } 75 | 76 | .viewer-rotate-right::before { 77 | background-position: -160px 0; 78 | content: 'Rotate Right'; 79 | } 80 | 81 | .viewer-flip-horizontal::before { 82 | background-position: -180px 0; 83 | content: 'Flip Horizontal'; 84 | } 85 | 86 | .viewer-flip-vertical::before { 87 | background-position: -200px 0; 88 | content: 'Flip Vertical'; 89 | } 90 | 91 | .viewer-fullscreen::before { 92 | background-position: -220px 0; 93 | content: 'Enter Full Screen'; 94 | } 95 | 96 | .viewer-fullscreen-exit::before { 97 | background-position: -240px 0; 98 | content: 'Exit Full Screen'; 99 | } 100 | 101 | .viewer-close::before { 102 | background-position: -260px 0; 103 | content: 'Close'; 104 | } 105 | 106 | .viewer-container { 107 | bottom: 0; 108 | direction: ltr; 109 | font-size: 0; 110 | left: 0; 111 | line-height: 0; 112 | overflow: hidden; 113 | position: absolute; 114 | right: 0; 115 | -webkit-tap-highlight-color: transparent; 116 | top: 0; 117 | -ms-touch-action: none; 118 | touch-action: none; 119 | -webkit-touch-callout: none; 120 | -webkit-user-select: none; 121 | -moz-user-select: none; 122 | -ms-user-select: none; 123 | user-select: none; 124 | } 125 | 126 | .viewer-container::-moz-selection, 127 | .viewer-container *::-moz-selection { 128 | background-color: transparent; 129 | } 130 | 131 | .viewer-container::selection, 132 | .viewer-container *::selection { 133 | background-color: transparent; 134 | } 135 | 136 | .viewer-container img { 137 | display: block; 138 | height: auto; 139 | max-height: none !important; 140 | max-width: none !important; 141 | min-height: 0 !important; 142 | min-width: 0 !important; 143 | width: 100%; 144 | } 145 | 146 | .viewer-canvas { 147 | bottom: 0; 148 | left: 0; 149 | overflow: hidden; 150 | position: absolute; 151 | right: 0; 152 | top: 0; 153 | } 154 | 155 | .viewer-canvas > img { 156 | height: auto; 157 | margin: 15px auto; 158 | max-width: 90% !important; 159 | width: auto; 160 | } 161 | 162 | .viewer-footer { 163 | bottom: 0; 164 | left: 0; 165 | overflow: hidden; 166 | position: absolute; 167 | right: 0; 168 | text-align: center; 169 | } 170 | 171 | .viewer-navbar { 172 | background-color: rgba(0, 0, 0, 0.5); 173 | overflow: hidden; 174 | } 175 | 176 | .viewer-list { 177 | -webkit-box-sizing: content-box; 178 | box-sizing: content-box; 179 | height: 50px; 180 | margin: 0; 181 | overflow: hidden; 182 | padding: 1px 0; 183 | } 184 | 185 | .viewer-list > li { 186 | color: transparent; 187 | cursor: pointer; 188 | float: left; 189 | font-size: 0; 190 | height: 50px; 191 | line-height: 0; 192 | opacity: 0.5; 193 | overflow: hidden; 194 | -webkit-transition: opacity 0.15s; 195 | transition: opacity 0.15s; 196 | width: 30px; 197 | } 198 | 199 | .viewer-list > li:hover { 200 | opacity: 0.75; 201 | } 202 | 203 | .viewer-list > li + li { 204 | margin-left: 1px; 205 | } 206 | 207 | .viewer-list > .viewer-loading { 208 | position: relative; 209 | } 210 | 211 | .viewer-list > .viewer-loading::after { 212 | border-width: 2px; 213 | height: 20px; 214 | margin-left: -10px; 215 | margin-top: -10px; 216 | width: 20px; 217 | } 218 | 219 | .viewer-list > .viewer-active, 220 | .viewer-list > .viewer-active:hover { 221 | opacity: 1; 222 | } 223 | 224 | .viewer-player { 225 | background-color: #000; 226 | bottom: 0; 227 | cursor: none; 228 | display: none; 229 | left: 0; 230 | position: absolute; 231 | right: 0; 232 | top: 0; 233 | } 234 | 235 | .viewer-player > img { 236 | left: 0; 237 | position: absolute; 238 | top: 0; 239 | } 240 | 241 | .viewer-toolbar > ul { 242 | display: inline-block; 243 | margin: 0 auto 5px; 244 | overflow: hidden; 245 | padding: 3px 0; 246 | } 247 | 248 | .viewer-toolbar > ul > li { 249 | background-color: rgba(0, 0, 0, 0.5); 250 | border-radius: 50%; 251 | cursor: pointer; 252 | float: left; 253 | height: 24px; 254 | overflow: hidden; 255 | -webkit-transition: background-color 0.15s; 256 | transition: background-color 0.15s; 257 | width: 24px; 258 | } 259 | 260 | .viewer-toolbar > ul > li:hover { 261 | background-color: rgba(0, 0, 0, 0.8); 262 | } 263 | 264 | .viewer-toolbar > ul > li::before { 265 | margin: 2px; 266 | } 267 | 268 | .viewer-toolbar > ul > li + li { 269 | margin-left: 1px; 270 | } 271 | 272 | .viewer-toolbar > ul > .viewer-small { 273 | height: 18px; 274 | margin-bottom: 3px; 275 | margin-top: 3px; 276 | width: 18px; 277 | } 278 | 279 | .viewer-toolbar > ul > .viewer-small::before { 280 | margin: -1px; 281 | } 282 | 283 | .viewer-toolbar > ul > .viewer-large { 284 | height: 30px; 285 | margin-bottom: -3px; 286 | margin-top: -3px; 287 | width: 30px; 288 | } 289 | 290 | .viewer-toolbar > ul > .viewer-large::before { 291 | margin: 5px; 292 | } 293 | 294 | .viewer-tooltip { 295 | background-color: rgba(0, 0, 0, 0.8); 296 | border-radius: 10px; 297 | color: #fff; 298 | display: none; 299 | font-size: 12px; 300 | height: 20px; 301 | left: 50%; 302 | line-height: 20px; 303 | margin-left: -25px; 304 | margin-top: -10px; 305 | position: absolute; 306 | text-align: center; 307 | top: 50%; 308 | width: 50px; 309 | } 310 | 311 | .viewer-title { 312 | color: #ccc; 313 | display: inline-block; 314 | font-size: 12px; 315 | line-height: 1; 316 | margin: 0 5% 5px; 317 | max-width: 90%; 318 | opacity: 0.8; 319 | overflow: hidden; 320 | text-overflow: ellipsis; 321 | -webkit-transition: opacity 0.15s; 322 | transition: opacity 0.15s; 323 | white-space: nowrap; 324 | } 325 | 326 | .viewer-title:hover { 327 | opacity: 1; 328 | } 329 | 330 | .viewer-button { 331 | background-color: rgba(0, 0, 0, 0.5); 332 | border-radius: 50%; 333 | cursor: pointer; 334 | height: 80px; 335 | overflow: hidden; 336 | position: absolute; 337 | right: -40px; 338 | top: -40px; 339 | -webkit-transition: background-color 0.15s; 340 | transition: background-color 0.15s; 341 | width: 80px; 342 | } 343 | 344 | .viewer-button:focus, 345 | .viewer-button:hover { 346 | background-color: rgba(0, 0, 0, 0.8); 347 | } 348 | 349 | .viewer-button::before { 350 | bottom: 15px; 351 | left: 15px; 352 | position: absolute; 353 | } 354 | 355 | .viewer-fixed { 356 | position: fixed; 357 | } 358 | 359 | .viewer-open { 360 | overflow: hidden; 361 | } 362 | 363 | .viewer-show { 364 | display: block; 365 | } 366 | 367 | .viewer-hide { 368 | display: none; 369 | } 370 | 371 | .viewer-backdrop { 372 | background-color: rgba(0, 0, 0, 0.5); 373 | } 374 | 375 | .viewer-invisible { 376 | visibility: hidden; 377 | } 378 | 379 | .viewer-move { 380 | cursor: move; 381 | cursor: -webkit-grab; 382 | cursor: grab; 383 | } 384 | 385 | .viewer-fade { 386 | opacity: 0; 387 | } 388 | 389 | .viewer-in { 390 | opacity: 1; 391 | } 392 | 393 | .viewer-transition { 394 | -webkit-transition: all 0.3s; 395 | transition: all 0.3s; 396 | } 397 | 398 | @-webkit-keyframes viewer-spinner { 399 | 0% { 400 | -webkit-transform: rotate(0deg); 401 | transform: rotate(0deg); 402 | } 403 | 404 | 100% { 405 | -webkit-transform: rotate(360deg); 406 | transform: rotate(360deg); 407 | } 408 | } 409 | 410 | @keyframes viewer-spinner { 411 | 0% { 412 | -webkit-transform: rotate(0deg); 413 | transform: rotate(0deg); 414 | } 415 | 416 | 100% { 417 | -webkit-transform: rotate(360deg); 418 | transform: rotate(360deg); 419 | } 420 | } 421 | 422 | .viewer-loading::after { 423 | -webkit-animation: viewer-spinner 1s linear infinite; 424 | animation: viewer-spinner 1s linear infinite; 425 | border: 4px solid rgba(255, 255, 255, 0.1); 426 | border-left-color: rgba(255, 255, 255, 0.5); 427 | border-radius: 50%; 428 | content: ''; 429 | display: inline-block; 430 | height: 40px; 431 | left: 50%; 432 | margin-left: -20px; 433 | margin-top: -20px; 434 | position: absolute; 435 | top: 50%; 436 | width: 40px; 437 | z-index: 1; 438 | } 439 | 440 | @media (max-width: 767px) { 441 | .viewer-hide-xs-down { 442 | display: none; 443 | } 444 | } 445 | 446 | @media (max-width: 991px) { 447 | .viewer-hide-sm-down { 448 | display: none; 449 | } 450 | } 451 | 452 | @media (max-width: 1199px) { 453 | .viewer-hide-md-down { 454 | display: none; 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /docs/css/viewer.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Viewer v1.1.0 3 | * https://fengyuanchen.github.io/viewer 4 | * 5 | * Copyright 2015-present Chen Fengyuan 6 | * Released under the MIT license 7 | * 8 | * Date: 2019-12-14T11:48:37.240Z 9 | */ 10 | 11 | .viewer-zoom-in::before, 12 | .viewer-zoom-out::before, 13 | .viewer-one-to-one::before, 14 | .viewer-reset::before, 15 | .viewer-prev::before, 16 | .viewer-play::before, 17 | .viewer-next::before, 18 | .viewer-rotate-left::before, 19 | .viewer-rotate-right::before, 20 | .viewer-flip-horizontal::before, 21 | .viewer-flip-vertical::before, 22 | .viewer-fullscreen::before, 23 | .viewer-fullscreen-exit::before, 24 | .viewer-close::before { 25 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAAUCAYAAABWOyJDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAQPSURBVHic7Zs/iFxVFMa/0U2UaJGksUgnIVhYxVhpjDbZCBmLdAYECxsRFBTUamcXUiSNncgKQbSxsxH8gzAP3FU2jY0kKKJNiiiIghFlccnP4p3nPCdv3p9778vsLOcHB2bfveeb7955c3jvvNkBIMdxnD64a94GHMfZu3iBcRynN7zAOI7TG15gHCeeNUkr8zaxG2lbYDYsdgMbktBsP03jdQwljSXdtBhLOmtjowC9Mg9L+knSlcD8TNKpSA9lBpK2JF2VdDSR5n5J64m0qli399hNFMUlpshQii5jbXTbHGviB0nLNeNDSd9VO4A2UdB2fp+x0eCnaXxWXGA2X0au/3HgN9P4LFCjIANOJdrLr0zzZ+BEpNYDwKbpnQMeAw4m8HjQtM6Z9qa917zPQwFr3M5KgA6J5rTJCdFZJj9/lyvGhsDvwFNVuV2MhhjrK6b9bFiE+j1r87eBl4HDwCF7/U/k+ofAX5b/EXBv5JoLMuILzf3Ap6Z3EzgdqHMCuF7hcQf4HDgeoHnccncqdK/TvSDWffFXI/exICY/xZyqc6XLWF1UFZna4gJ7q8BsRvgd2/xXpo6P+D9dfT7PpECtA3cnWPM0GXGFZh/wgWltA+cDNC7X+AP4GzjZQe+k5dRxuYPeiuXU7e1qwLpDz7dFjXKRaSwuMLvAlG8zZlG+YmiK1HoFqT7wP2z+4Q45TfEGcMt01xLoNZEBTwRqD4BLpnMLeC1A41UmVxsXgXeBayV/Wx20rpTyrpnWRft7p6O/FdqzGrDukPNtkaMoMo3FBdBSQMOnYBCReyf05s126fU9ytfX98+mY54Kxnp7S9K3kj6U9KYdG0h6UdLbkh7poFXMfUnSOyVvL0h6VtIXHbS6nOP+s/Zm9mvyXW1uuC9ohZ72E9uDmXWLJOB1GxsH+DxPftsB8B6wlGDN02TAkxG6+4D3TWsbeC5CS8CDFce+AW500LhhOW2020TRjK3b21HEmgti9m0RonxbdMZeVzV+/4tF3cBpP7E9mKHNL5q8h5g0eYsCMQz0epq8gQrwMXAgcs0FGXGFRcB9wCemF9PkbYqM/Bas7fxLwNeJPdTdpo4itQti8lPMqTpXuozVRVXPpbHI3KkNTB1NfkL81j2mvhDp91HgV9MKuRIqrykj3WPq4rHyL+axj8/qGPmTqi6F9YDlHOvJU6oYcTsh/TYSzWmTE6JT19CtLTJt32D6CmHe0eQn1O8z5AXgT4sx4Vcu0/EQecMydB8z0hUWkTd2t4CrwNEePqMBcAR4mrBbwyXLPWJa8zrXmmLEhNBmfpkuY2102xxrih+pb+ieAb6vGhuA97UcJ5KR8gZ77K+99xxeYBzH6Q3/Z0fHcXrDC4zjOL3hBcZxnN74F+zlvXFWXF9PAAAAAElFTkSuQmCC'); 26 | background-repeat: no-repeat; 27 | background-size: 280px; 28 | color: transparent; 29 | display: block; 30 | font-size: 0; 31 | height: 20px; 32 | line-height: 0; 33 | width: 20px; 34 | } 35 | 36 | .viewer-zoom-in::before { 37 | background-position: 0 0; 38 | content: 'Zoom In'; 39 | } 40 | 41 | .viewer-zoom-out::before { 42 | background-position: -20px 0; 43 | content: 'Zoom Out'; 44 | } 45 | 46 | .viewer-one-to-one::before { 47 | background-position: -40px 0; 48 | content: 'One to One'; 49 | } 50 | 51 | .viewer-reset::before { 52 | background-position: -60px 0; 53 | content: 'Reset'; 54 | } 55 | 56 | .viewer-prev::before { 57 | background-position: -80px 0; 58 | content: 'Previous'; 59 | } 60 | 61 | .viewer-play::before { 62 | background-position: -100px 0; 63 | content: 'Play'; 64 | } 65 | 66 | .viewer-next::before { 67 | background-position: -120px 0; 68 | content: 'Next'; 69 | } 70 | 71 | .viewer-rotate-left::before { 72 | background-position: -140px 0; 73 | content: 'Rotate Left'; 74 | } 75 | 76 | .viewer-rotate-right::before { 77 | background-position: -160px 0; 78 | content: 'Rotate Right'; 79 | } 80 | 81 | .viewer-flip-horizontal::before { 82 | background-position: -180px 0; 83 | content: 'Flip Horizontal'; 84 | } 85 | 86 | .viewer-flip-vertical::before { 87 | background-position: -200px 0; 88 | content: 'Flip Vertical'; 89 | } 90 | 91 | .viewer-fullscreen::before { 92 | background-position: -220px 0; 93 | content: 'Enter Full Screen'; 94 | } 95 | 96 | .viewer-fullscreen-exit::before { 97 | background-position: -240px 0; 98 | content: 'Exit Full Screen'; 99 | } 100 | 101 | .viewer-close::before { 102 | background-position: -260px 0; 103 | content: 'Close'; 104 | } 105 | 106 | .viewer-container { 107 | bottom: 0; 108 | direction: ltr; 109 | font-size: 0; 110 | left: 0; 111 | line-height: 0; 112 | overflow: hidden; 113 | position: absolute; 114 | right: 0; 115 | -webkit-tap-highlight-color: transparent; 116 | top: 0; 117 | -ms-touch-action: none; 118 | touch-action: none; 119 | -webkit-touch-callout: none; 120 | -webkit-user-select: none; 121 | -moz-user-select: none; 122 | -ms-user-select: none; 123 | user-select: none; 124 | } 125 | 126 | .viewer-container::-moz-selection, 127 | .viewer-container *::-moz-selection { 128 | background-color: transparent; 129 | } 130 | 131 | .viewer-container::selection, 132 | .viewer-container *::selection { 133 | background-color: transparent; 134 | } 135 | 136 | .viewer-container img { 137 | display: block; 138 | height: auto; 139 | max-height: none !important; 140 | max-width: none !important; 141 | min-height: 0 !important; 142 | min-width: 0 !important; 143 | width: 100%; 144 | } 145 | 146 | .viewer-canvas { 147 | bottom: 0; 148 | left: 0; 149 | overflow: hidden; 150 | position: absolute; 151 | right: 0; 152 | top: 0; 153 | } 154 | 155 | .viewer-canvas > img { 156 | height: auto; 157 | margin: 15px auto; 158 | max-width: 90% !important; 159 | width: auto; 160 | } 161 | 162 | .viewer-footer { 163 | bottom: 0; 164 | left: 0; 165 | overflow: hidden; 166 | position: absolute; 167 | right: 0; 168 | text-align: center; 169 | } 170 | 171 | .viewer-navbar { 172 | background-color: rgba(0, 0, 0, 0.5); 173 | overflow: hidden; 174 | } 175 | 176 | .viewer-list { 177 | -webkit-box-sizing: content-box; 178 | box-sizing: content-box; 179 | height: 50px; 180 | margin: 0; 181 | overflow: hidden; 182 | padding: 1px 0; 183 | } 184 | 185 | .viewer-list > li { 186 | color: transparent; 187 | cursor: pointer; 188 | float: left; 189 | font-size: 0; 190 | height: 50px; 191 | line-height: 0; 192 | opacity: 0.5; 193 | overflow: hidden; 194 | -webkit-transition: opacity 0.15s; 195 | transition: opacity 0.15s; 196 | width: 30px; 197 | } 198 | 199 | .viewer-list > li:hover { 200 | opacity: 0.75; 201 | } 202 | 203 | .viewer-list > li + li { 204 | margin-left: 1px; 205 | } 206 | 207 | .viewer-list > .viewer-loading { 208 | position: relative; 209 | } 210 | 211 | .viewer-list > .viewer-loading::after { 212 | border-width: 2px; 213 | height: 20px; 214 | margin-left: -10px; 215 | margin-top: -10px; 216 | width: 20px; 217 | } 218 | 219 | .viewer-list > .viewer-active, 220 | .viewer-list > .viewer-active:hover { 221 | opacity: 1; 222 | } 223 | 224 | .viewer-player { 225 | background-color: #000; 226 | bottom: 0; 227 | cursor: none; 228 | display: none; 229 | left: 0; 230 | position: absolute; 231 | right: 0; 232 | top: 0; 233 | } 234 | 235 | .viewer-player > img { 236 | left: 0; 237 | position: absolute; 238 | top: 0; 239 | } 240 | 241 | .viewer-toolbar > ul { 242 | display: inline-block; 243 | margin: 0 auto 5px; 244 | overflow: hidden; 245 | padding: 3px 0; 246 | } 247 | 248 | .viewer-toolbar > ul > li { 249 | background-color: rgba(0, 0, 0, 0.5); 250 | border-radius: 50%; 251 | cursor: pointer; 252 | float: left; 253 | height: 24px; 254 | overflow: hidden; 255 | -webkit-transition: background-color 0.15s; 256 | transition: background-color 0.15s; 257 | width: 24px; 258 | } 259 | 260 | .viewer-toolbar > ul > li:hover { 261 | background-color: rgba(0, 0, 0, 0.8); 262 | } 263 | 264 | .viewer-toolbar > ul > li::before { 265 | margin: 2px; 266 | } 267 | 268 | .viewer-toolbar > ul > li + li { 269 | margin-left: 1px; 270 | } 271 | 272 | .viewer-toolbar > ul > .viewer-small { 273 | height: 18px; 274 | margin-bottom: 3px; 275 | margin-top: 3px; 276 | width: 18px; 277 | } 278 | 279 | .viewer-toolbar > ul > .viewer-small::before { 280 | margin: -1px; 281 | } 282 | 283 | .viewer-toolbar > ul > .viewer-large { 284 | height: 30px; 285 | margin-bottom: -3px; 286 | margin-top: -3px; 287 | width: 30px; 288 | } 289 | 290 | .viewer-toolbar > ul > .viewer-large::before { 291 | margin: 5px; 292 | } 293 | 294 | .viewer-tooltip { 295 | background-color: rgba(0, 0, 0, 0.8); 296 | border-radius: 10px; 297 | color: #fff; 298 | display: none; 299 | font-size: 12px; 300 | height: 20px; 301 | left: 50%; 302 | line-height: 20px; 303 | margin-left: -25px; 304 | margin-top: -10px; 305 | position: absolute; 306 | text-align: center; 307 | top: 50%; 308 | width: 50px; 309 | } 310 | 311 | .viewer-title { 312 | color: #ccc; 313 | display: inline-block; 314 | font-size: 12px; 315 | line-height: 1; 316 | margin: 0 5% 5px; 317 | max-width: 90%; 318 | opacity: 0.8; 319 | overflow: hidden; 320 | text-overflow: ellipsis; 321 | -webkit-transition: opacity 0.15s; 322 | transition: opacity 0.15s; 323 | white-space: nowrap; 324 | } 325 | 326 | .viewer-title:hover { 327 | opacity: 1; 328 | } 329 | 330 | .viewer-button { 331 | background-color: rgba(0, 0, 0, 0.5); 332 | border-radius: 50%; 333 | cursor: pointer; 334 | height: 80px; 335 | overflow: hidden; 336 | position: absolute; 337 | right: -40px; 338 | top: -40px; 339 | -webkit-transition: background-color 0.15s; 340 | transition: background-color 0.15s; 341 | width: 80px; 342 | } 343 | 344 | .viewer-button:focus, 345 | .viewer-button:hover { 346 | background-color: rgba(0, 0, 0, 0.8); 347 | } 348 | 349 | .viewer-button::before { 350 | bottom: 15px; 351 | left: 15px; 352 | position: absolute; 353 | } 354 | 355 | .viewer-fixed { 356 | position: fixed; 357 | } 358 | 359 | .viewer-open { 360 | overflow: hidden; 361 | } 362 | 363 | .viewer-show { 364 | display: block; 365 | } 366 | 367 | .viewer-hide { 368 | display: none; 369 | } 370 | 371 | .viewer-backdrop { 372 | background-color: rgba(0, 0, 0, 0.5); 373 | } 374 | 375 | .viewer-invisible { 376 | visibility: hidden; 377 | } 378 | 379 | .viewer-move { 380 | cursor: move; 381 | cursor: -webkit-grab; 382 | cursor: grab; 383 | } 384 | 385 | .viewer-fade { 386 | opacity: 0; 387 | } 388 | 389 | .viewer-in { 390 | opacity: 1; 391 | } 392 | 393 | .viewer-transition { 394 | -webkit-transition: all 0.3s; 395 | transition: all 0.3s; 396 | } 397 | 398 | @-webkit-keyframes viewer-spinner { 399 | 0% { 400 | -webkit-transform: rotate(0deg); 401 | transform: rotate(0deg); 402 | } 403 | 404 | 100% { 405 | -webkit-transform: rotate(360deg); 406 | transform: rotate(360deg); 407 | } 408 | } 409 | 410 | @keyframes viewer-spinner { 411 | 0% { 412 | -webkit-transform: rotate(0deg); 413 | transform: rotate(0deg); 414 | } 415 | 416 | 100% { 417 | -webkit-transform: rotate(360deg); 418 | transform: rotate(360deg); 419 | } 420 | } 421 | 422 | .viewer-loading::after { 423 | -webkit-animation: viewer-spinner 1s linear infinite; 424 | animation: viewer-spinner 1s linear infinite; 425 | border: 4px solid rgba(255, 255, 255, 0.1); 426 | border-left-color: rgba(255, 255, 255, 0.5); 427 | border-radius: 50%; 428 | content: ''; 429 | display: inline-block; 430 | height: 40px; 431 | left: 50%; 432 | margin-left: -20px; 433 | margin-top: -20px; 434 | position: absolute; 435 | top: 50%; 436 | width: 40px; 437 | z-index: 1; 438 | } 439 | 440 | @media (max-width: 767px) { 441 | .viewer-hide-xs-down { 442 | display: none; 443 | } 444 | } 445 | 446 | @media (max-width: 991px) { 447 | .viewer-hide-sm-down { 448 | display: none; 449 | } 450 | } 451 | 452 | @media (max-width: 1199px) { 453 | .viewer-hide-md-down { 454 | display: none; 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Viewer 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 25 | 42 | 43 | 44 |
45 |
46 |
47 |
48 |

Viewer v1.1.0

49 |

A simple jQuery image viewing plugin.

50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 |
59 | 60 | 61 |
62 |

Overview

63 |
64 |
65 |
66 |

Options

67 |
68 |
69 |
70 | 73 | 76 |
77 | 78 |
79 |
    80 |
  • 81 |
    82 | 83 | 84 |
    85 |
  • 86 |
  • 87 |
    88 | 89 | 90 |
    91 |
  • 92 |
  • 93 |
    94 | 95 | 96 |
    97 |
  • 98 |
  • 99 |
    100 | 101 | 102 |
    103 |
  • 104 |
  • 105 |
    106 | 107 | 108 |
    109 |
  • 110 |
  • 111 |
    112 | 113 | 114 |
    115 |
  • 116 |
  • 117 |
    118 | 119 | 120 |
    121 |
  • 122 |
  • 123 |
    124 | 125 | 126 |
    127 |
  • 128 |
  • 129 |
    130 | 131 | 132 |
    133 |
  • 134 |
  • 135 |
    136 | 137 | 138 |
    139 |
  • 140 |
  • 141 |
    142 | 143 | 144 |
    145 |
  • 146 |
  • 147 |
    148 | 149 | 150 |
    151 |
  • 152 |
  • 153 |
    154 | 155 | 156 |
    157 |
  • 158 |
  • 159 |
    160 | 161 | 162 |
    163 |
  • 164 |
  • 165 |
    166 | 167 | 168 |
    169 |
  • 170 |
  • 171 |
    172 | 173 | 174 |
    175 |
  • 176 |
  • 177 |
    178 | 179 | 180 |
    181 |
  • 182 |
  • 183 |
    184 | 185 | 186 |
    187 |
  • 188 |
  • 189 |
    190 | 191 | 192 |
    193 |
  • 194 |
195 |
196 |
197 |
198 |
199 |

Demo

200 |
201 |
202 |
    203 |
  • Cuo Na Lake
  • 204 |
  • Tibetan Plateau
  • 205 |
  • Jokhang Temple
  • 206 |
  • Potala Palace 1
  • 207 |
  • Potala Palace 2
  • 208 |
  • Potala Palace 3
  • 209 |
  • Lhasa River
  • 210 |
  • Namtso 1
  • 211 |
  • Namtso 2
  • 212 |
213 |
214 |
215 |
216 |

Methods

217 |
218 |
219 |
220 | 221 | 222 | 223 | 224 |
225 |
226 | 227 | 228 | 229 | 230 |
231 |
232 | 233 | 234 | 235 | 236 |
237 |
238 | 239 | 240 | 241 | 242 |
243 |
244 | 245 | 246 | 247 | 248 |
249 |
250 | 251 | 252 |
253 |
254 | 255 | 256 |
257 |
258 | 259 | 260 |
261 |
262 | 263 | 264 | 265 | 266 |
267 |
268 | 269 | 270 | 271 | 272 |
273 |
274 | 275 | 276 | 277 | 278 |
279 |
280 | 281 | 282 |
283 | 284 |
285 |
286 |
287 |
288 | 289 | 290 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | -------------------------------------------------------------------------------- /dist/viewer.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Viewer v1.1.0 3 | * https://fengyuanchen.github.io/viewer 4 | * 5 | * Copyright 2015-present Chen Fengyuan 6 | * Released under the MIT license 7 | * 8 | * Date: 2019-12-14T11:48:41.205Z 9 | */ 10 | !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],i):i((t=t||self).jQuery)}(this,function(c){"use strict";function i(t){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function s(t,i){for(var e=0;e=this.length||this.viewed&&i===this.index)return this;if(!this.isShown)return this.index=i,this.show();this.viewing&&this.viewing.abort();var e=this.element,s=this.options,o=this.title,a=this.canvas,r=this.items[i],h=r.querySelector("img"),l=ft(h,"originalUrl"),c=h.getAttribute("alt"),u=document.createElement("img");if(u.src=l,u.alt=c,nt(s.view)&&wt(e,"view",s.view,{once:!0}),!1===bt(e,"view",{originalImage:this.images[i],index:i,image:u})||!this.isShown||this.hiding||this.played)return this;this.image=u,ct(this.items[this.index],g),lt(r,g),this.viewed=!1,this.index=i,this.imageData={},lt(u,T),s.loading&<(a,E),a.innerHTML="",a.appendChild(u),this.renderList(),o.innerHTML="";function d(){var t,i=n.imageData,e=Array.isArray(s.title)?s.title[1]:s.title;o.innerHTML=_(t=nt(e)?e.call(n,u,i):"".concat(c," (").concat(i.naturalWidth," × ").concat(i.naturalHeight,")"))?t.replace(/&(?!amp;|quot;|#39;|lt;|gt;)/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">"):t}var m;return wt(e,K,d,{once:!0}),this.viewing={abort:function(){pt(e,K,d),u.complete?this.imageRendering?this.imageRendering.abort():this.imageInitializing&&this.imageInitializing.abort():(u.src="",pt(u,X,m),this.timeout&&clearTimeout(this.timeout))}},u.complete?this.load():(wt(u,X,m=this.load.bind(this),{once:!0}),this.timeout&&clearTimeout(this.timeout),this.timeout=setTimeout(function(){ct(u,T),n.timeout=!1},1e3)),this},prev:function(t){var i=0Math.abs(r)&&(this.pointers={},1
';var n=e.querySelector(".".concat(p,"-container")),s=n.querySelector(".".concat(p,"-title")),o=n.querySelector(".".concat(p,"-toolbar")),a=n.querySelector(".".concat(p,"-navbar")),r=n.querySelector(".".concat(p,"-button")),l=n.querySelector(".".concat(p,"-canvas"));if(this.parent=i,this.viewer=n,this.title=s,this.toolbar=o,this.navbar=a,this.button=r,this.canvas=l,this.footer=n.querySelector(".".concat(p,"-footer")),this.tooltipBox=n.querySelector(".".concat(p,"-tooltip")),this.player=n.querySelector(".".concat(p,"-player")),this.list=n.querySelector(".".concat(p,"-list")),lt(s,h.title?zt(Array.isArray(h.title)?h.title[0]:h.title):k),lt(a,h.navbar?zt(h.navbar):k),ut(r,k,!h.button),h.backdrop&&(lt(n,"".concat(p,"-backdrop")),h.inline||"static"===h.backdrop||gt(l,U,"hide")),_(h.className)&&h.className&&h.className.split(Z).forEach(function(t){lt(n,t)}),h.toolbar){var c=document.createElement("ul"),u=et(h.toolbar),d=$.slice(0,3),m=$.slice(7,9),f=$.slice(9);u||lt(o,zt(h.toolbar)),st(u?h.toolbar:$,function(t,i){var e=u&&et(t),n=u?mt(i):t,s=e&&!J(t.show)?t.show:t;if(s&&(h.zoomable||-1===d.indexOf(n))&&(h.rotatable||-1===m.indexOf(n))&&(h.scalable||-1===f.indexOf(n))){var o=e&&!J(t.size)?t.size:t,a=e&&!J(t.click)?t.click:t,r=document.createElement("li");r.setAttribute("role","button"),lt(r,"".concat(p,"-").concat(n)),nt(a)||gt(r,U,n),G(s)&<(r,zt(s)),-1!==["small","large"].indexOf(o)?lt(r,"".concat(p,"-").concat(o)):"play"===n&<(r,"".concat(p,"-large")),nt(a)&&wt(r,L,a),c.appendChild(r)}}),o.appendChild(c)}else lt(o,k);if(!h.rotatable){var g=o.querySelectorAll('li[class*="rotate"]');lt(g,T),st(g,function(t){o.appendChild(t)})}if(h.inline)lt(r,x),rt(n,{zIndex:h.zIndexInline}),"static"===window.getComputedStyle(i).position&&rt(i,{position:"relative"}),i.insertBefore(n,t.nextSibling);else{lt(r,w),lt(n,y),lt(n,b),lt(n,k),rt(n,{zIndex:h.zIndex});var v=h.container;_(v)&&(v=t.ownerDocument.querySelector(v)),(v=v||this.body).appendChild(n)}h.inline&&(this.render(),this.bind(),this.isShown=!0),this.ready=!0,nt(h.ready)&&wt(t,W,h.ready,{once:!0}),!1!==bt(t,W)?this.ready&&h.inline&&this.view(this.index):this.ready=!1}}}])&&s(t.prototype,i),n&&s(t,n),e}();if(ot(Lt.prototype,Tt,Et,It,Ot,St),c.fn){var Nt=c.fn.viewer,qt="viewer";c.fn.viewer=function(r){for(var t=arguments.length,h=new Array(1' + '' + '
' + '
' + '
' + ''; 315 | 316 | var IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined'; 317 | var WINDOW = IS_BROWSER ? window : {}; 318 | var IS_TOUCH_DEVICE = IS_BROWSER ? 'ontouchstart' in WINDOW.document.documentElement : false; 319 | var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false; 320 | var NAMESPACE = 'viewer'; // Actions 321 | 322 | var ACTION_MOVE = 'move'; 323 | var ACTION_SWITCH = 'switch'; 324 | var ACTION_ZOOM = 'zoom'; // Classes 325 | 326 | var CLASS_ACTIVE = "".concat(NAMESPACE, "-active"); 327 | var CLASS_CLOSE = "".concat(NAMESPACE, "-close"); 328 | var CLASS_FADE = "".concat(NAMESPACE, "-fade"); 329 | var CLASS_FIXED = "".concat(NAMESPACE, "-fixed"); 330 | var CLASS_FULLSCREEN = "".concat(NAMESPACE, "-fullscreen"); 331 | var CLASS_FULLSCREEN_EXIT = "".concat(NAMESPACE, "-fullscreen-exit"); 332 | var CLASS_HIDE = "".concat(NAMESPACE, "-hide"); 333 | var CLASS_HIDE_MD_DOWN = "".concat(NAMESPACE, "-hide-md-down"); 334 | var CLASS_HIDE_SM_DOWN = "".concat(NAMESPACE, "-hide-sm-down"); 335 | var CLASS_HIDE_XS_DOWN = "".concat(NAMESPACE, "-hide-xs-down"); 336 | var CLASS_IN = "".concat(NAMESPACE, "-in"); 337 | var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible"); 338 | var CLASS_LOADING = "".concat(NAMESPACE, "-loading"); 339 | var CLASS_MOVE = "".concat(NAMESPACE, "-move"); 340 | var CLASS_OPEN = "".concat(NAMESPACE, "-open"); 341 | var CLASS_SHOW = "".concat(NAMESPACE, "-show"); 342 | var CLASS_TRANSITION = "".concat(NAMESPACE, "-transition"); // Events 343 | 344 | var EVENT_CLICK = 'click'; 345 | var EVENT_DBLCLICK = 'dblclick'; 346 | var EVENT_DRAG_START = 'dragstart'; 347 | var EVENT_HIDDEN = 'hidden'; 348 | var EVENT_HIDE = 'hide'; 349 | var EVENT_KEY_DOWN = 'keydown'; 350 | var EVENT_LOAD = 'load'; 351 | var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown'; 352 | var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove'; 353 | var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup'; 354 | var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START; 355 | var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE; 356 | var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END; 357 | var EVENT_READY = 'ready'; 358 | var EVENT_RESIZE = 'resize'; 359 | var EVENT_SHOW = 'show'; 360 | var EVENT_SHOWN = 'shown'; 361 | var EVENT_TRANSITION_END = 'transitionend'; 362 | var EVENT_VIEW = 'view'; 363 | var EVENT_VIEWED = 'viewed'; 364 | var EVENT_WHEEL = 'wheel'; 365 | var EVENT_ZOOM = 'zoom'; 366 | var EVENT_ZOOMED = 'zoomed'; // Data keys 367 | 368 | var DATA_ACTION = "".concat(NAMESPACE, "Action"); // RegExps 369 | 370 | var REGEXP_SPACES = /\s\s*/; // Misc 371 | 372 | var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'next', 'rotate-left', 'rotate-right', 'flip-horizontal', 'flip-vertical']; 373 | 374 | /** 375 | * Check if the given value is a string. 376 | * @param {*} value - The value to check. 377 | * @returns {boolean} Returns `true` if the given value is a string, else `false`. 378 | */ 379 | 380 | function isString(value) { 381 | return typeof value === 'string'; 382 | } 383 | /** 384 | * Check if the given value is not a number. 385 | */ 386 | 387 | var isNaN = Number.isNaN || WINDOW.isNaN; 388 | /** 389 | * Check if the given value is a number. 390 | * @param {*} value - The value to check. 391 | * @returns {boolean} Returns `true` if the given value is a number, else `false`. 392 | */ 393 | 394 | function isNumber(value) { 395 | return typeof value === 'number' && !isNaN(value); 396 | } 397 | /** 398 | * Check if the given value is undefined. 399 | * @param {*} value - The value to check. 400 | * @returns {boolean} Returns `true` if the given value is undefined, else `false`. 401 | */ 402 | 403 | function isUndefined(value) { 404 | return typeof value === 'undefined'; 405 | } 406 | /** 407 | * Check if the given value is an object. 408 | * @param {*} value - The value to check. 409 | * @returns {boolean} Returns `true` if the given value is an object, else `false`. 410 | */ 411 | 412 | function isObject(value) { 413 | return _typeof(value) === 'object' && value !== null; 414 | } 415 | var hasOwnProperty = Object.prototype.hasOwnProperty; 416 | /** 417 | * Check if the given value is a plain object. 418 | * @param {*} value - The value to check. 419 | * @returns {boolean} Returns `true` if the given value is a plain object, else `false`. 420 | */ 421 | 422 | function isPlainObject(value) { 423 | if (!isObject(value)) { 424 | return false; 425 | } 426 | 427 | try { 428 | var _constructor = value.constructor; 429 | var prototype = _constructor.prototype; 430 | return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf'); 431 | } catch (error) { 432 | return false; 433 | } 434 | } 435 | /** 436 | * Check if the given value is a function. 437 | * @param {*} value - The value to check. 438 | * @returns {boolean} Returns `true` if the given value is a function, else `false`. 439 | */ 440 | 441 | function isFunction(value) { 442 | return typeof value === 'function'; 443 | } 444 | /** 445 | * Iterate the given data. 446 | * @param {*} data - The data to iterate. 447 | * @param {Function} callback - The process function for each element. 448 | * @returns {*} The original data. 449 | */ 450 | 451 | function forEach(data, callback) { 452 | if (data && isFunction(callback)) { 453 | if (Array.isArray(data) || isNumber(data.length) 454 | /* array-like */ 455 | ) { 456 | var length = data.length; 457 | var i; 458 | 459 | for (i = 0; i < length; i += 1) { 460 | if (callback.call(data, data[i], i, data) === false) { 461 | break; 462 | } 463 | } 464 | } else if (isObject(data)) { 465 | Object.keys(data).forEach(function (key) { 466 | callback.call(data, data[key], key, data); 467 | }); 468 | } 469 | } 470 | 471 | return data; 472 | } 473 | /** 474 | * Extend the given object. 475 | * @param {*} obj - The object to be extended. 476 | * @param {*} args - The rest objects which will be merged to the first object. 477 | * @returns {Object} The extended object. 478 | */ 479 | 480 | var assign = Object.assign || function assign(obj) { 481 | for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 482 | args[_key - 1] = arguments[_key]; 483 | } 484 | 485 | if (isObject(obj) && args.length > 0) { 486 | args.forEach(function (arg) { 487 | if (isObject(arg)) { 488 | Object.keys(arg).forEach(function (key) { 489 | obj[key] = arg[key]; 490 | }); 491 | } 492 | }); 493 | } 494 | 495 | return obj; 496 | }; 497 | var REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/; 498 | /** 499 | * Apply styles to the given element. 500 | * @param {Element} element - The target element. 501 | * @param {Object} styles - The styles for applying. 502 | */ 503 | 504 | function setStyle(element, styles) { 505 | var style = element.style; 506 | forEach(styles, function (value, property) { 507 | if (REGEXP_SUFFIX.test(property) && isNumber(value)) { 508 | value += 'px'; 509 | } 510 | 511 | style[property] = value; 512 | }); 513 | } 514 | /** 515 | * Escape a string for using in HTML. 516 | * @param {String} value - The string to escape. 517 | * @returns {String} Returns the escaped string. 518 | */ 519 | 520 | function escapeHTMLEntities(value) { 521 | return isString(value) ? value.replace(/&(?!amp;|quot;|#39;|lt;|gt;)/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '>') : value; 522 | } 523 | /** 524 | * Check if the given element has a special class. 525 | * @param {Element} element - The element to check. 526 | * @param {string} value - The class to search. 527 | * @returns {boolean} Returns `true` if the special class was found. 528 | */ 529 | 530 | function hasClass(element, value) { 531 | if (!element || !value) { 532 | return false; 533 | } 534 | 535 | return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1; 536 | } 537 | /** 538 | * Add classes to the given element. 539 | * @param {Element} element - The target element. 540 | * @param {string} value - The classes to be added. 541 | */ 542 | 543 | function addClass(element, value) { 544 | if (!element || !value) { 545 | return; 546 | } 547 | 548 | if (isNumber(element.length)) { 549 | forEach(element, function (elem) { 550 | addClass(elem, value); 551 | }); 552 | return; 553 | } 554 | 555 | if (element.classList) { 556 | element.classList.add(value); 557 | return; 558 | } 559 | 560 | var className = element.className.trim(); 561 | 562 | if (!className) { 563 | element.className = value; 564 | } else if (className.indexOf(value) < 0) { 565 | element.className = "".concat(className, " ").concat(value); 566 | } 567 | } 568 | /** 569 | * Remove classes from the given element. 570 | * @param {Element} element - The target element. 571 | * @param {string} value - The classes to be removed. 572 | */ 573 | 574 | function removeClass(element, value) { 575 | if (!element || !value) { 576 | return; 577 | } 578 | 579 | if (isNumber(element.length)) { 580 | forEach(element, function (elem) { 581 | removeClass(elem, value); 582 | }); 583 | return; 584 | } 585 | 586 | if (element.classList) { 587 | element.classList.remove(value); 588 | return; 589 | } 590 | 591 | if (element.className.indexOf(value) >= 0) { 592 | element.className = element.className.replace(value, ''); 593 | } 594 | } 595 | /** 596 | * Add or remove classes from the given element. 597 | * @param {Element} element - The target element. 598 | * @param {string} value - The classes to be toggled. 599 | * @param {boolean} added - Add only. 600 | */ 601 | 602 | function toggleClass(element, value, added) { 603 | if (!value) { 604 | return; 605 | } 606 | 607 | if (isNumber(element.length)) { 608 | forEach(element, function (elem) { 609 | toggleClass(elem, value, added); 610 | }); 611 | return; 612 | } // IE10-11 doesn't support the second parameter of `classList.toggle` 613 | 614 | 615 | if (added) { 616 | addClass(element, value); 617 | } else { 618 | removeClass(element, value); 619 | } 620 | } 621 | var REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g; 622 | /** 623 | * Transform the given string from camelCase to kebab-case 624 | * @param {string} value - The value to transform. 625 | * @returns {string} The transformed value. 626 | */ 627 | 628 | function hyphenate(value) { 629 | return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase(); 630 | } 631 | /** 632 | * Get data from the given element. 633 | * @param {Element} element - The target element. 634 | * @param {string} name - The data key to get. 635 | * @returns {string} The data value. 636 | */ 637 | 638 | function getData(element, name) { 639 | if (isObject(element[name])) { 640 | return element[name]; 641 | } 642 | 643 | if (element.dataset) { 644 | return element.dataset[name]; 645 | } 646 | 647 | return element.getAttribute("data-".concat(hyphenate(name))); 648 | } 649 | /** 650 | * Set data to the given element. 651 | * @param {Element} element - The target element. 652 | * @param {string} name - The data key to set. 653 | * @param {string} data - The data value. 654 | */ 655 | 656 | function setData(element, name, data) { 657 | if (isObject(data)) { 658 | element[name] = data; 659 | } else if (element.dataset) { 660 | element.dataset[name] = data; 661 | } else { 662 | element.setAttribute("data-".concat(hyphenate(name)), data); 663 | } 664 | } 665 | 666 | var onceSupported = function () { 667 | var supported = false; 668 | 669 | if (IS_BROWSER) { 670 | var once = false; 671 | 672 | var listener = function listener() {}; 673 | 674 | var options = Object.defineProperty({}, 'once', { 675 | get: function get() { 676 | supported = true; 677 | return once; 678 | }, 679 | 680 | /** 681 | * This setter can fix a `TypeError` in strict mode 682 | * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only} 683 | * @param {boolean} value - The value to set 684 | */ 685 | set: function set(value) { 686 | once = value; 687 | } 688 | }); 689 | WINDOW.addEventListener('test', listener, options); 690 | WINDOW.removeEventListener('test', listener, options); 691 | } 692 | 693 | return supported; 694 | }(); 695 | /** 696 | * Remove event listener from the target element. 697 | * @param {Element} element - The event target. 698 | * @param {string} type - The event type(s). 699 | * @param {Function} listener - The event listener. 700 | * @param {Object} options - The event options. 701 | */ 702 | 703 | 704 | function removeListener(element, type, listener) { 705 | var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; 706 | var handler = listener; 707 | type.trim().split(REGEXP_SPACES).forEach(function (event) { 708 | if (!onceSupported) { 709 | var listeners = element.listeners; 710 | 711 | if (listeners && listeners[event] && listeners[event][listener]) { 712 | handler = listeners[event][listener]; 713 | delete listeners[event][listener]; 714 | 715 | if (Object.keys(listeners[event]).length === 0) { 716 | delete listeners[event]; 717 | } 718 | 719 | if (Object.keys(listeners).length === 0) { 720 | delete element.listeners; 721 | } 722 | } 723 | } 724 | 725 | element.removeEventListener(event, handler, options); 726 | }); 727 | } 728 | /** 729 | * Add event listener to the target element. 730 | * @param {Element} element - The event target. 731 | * @param {string} type - The event type(s). 732 | * @param {Function} listener - The event listener. 733 | * @param {Object} options - The event options. 734 | */ 735 | 736 | function addListener(element, type, listener) { 737 | var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; 738 | var _handler = listener; 739 | type.trim().split(REGEXP_SPACES).forEach(function (event) { 740 | if (options.once && !onceSupported) { 741 | var _element$listeners = element.listeners, 742 | listeners = _element$listeners === void 0 ? {} : _element$listeners; 743 | 744 | _handler = function handler() { 745 | delete listeners[event][listener]; 746 | element.removeEventListener(event, _handler, options); 747 | 748 | for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 749 | args[_key2] = arguments[_key2]; 750 | } 751 | 752 | listener.apply(element, args); 753 | }; 754 | 755 | if (!listeners[event]) { 756 | listeners[event] = {}; 757 | } 758 | 759 | if (listeners[event][listener]) { 760 | element.removeEventListener(event, listeners[event][listener], options); 761 | } 762 | 763 | listeners[event][listener] = _handler; 764 | element.listeners = listeners; 765 | } 766 | 767 | element.addEventListener(event, _handler, options); 768 | }); 769 | } 770 | /** 771 | * Dispatch event on the target element. 772 | * @param {Element} element - The event target. 773 | * @param {string} type - The event type(s). 774 | * @param {Object} data - The additional event data. 775 | * @returns {boolean} Indicate if the event is default prevented or not. 776 | */ 777 | 778 | function dispatchEvent(element, type, data) { 779 | var event; // Event and CustomEvent on IE9-11 are global objects, not constructors 780 | 781 | if (isFunction(Event) && isFunction(CustomEvent)) { 782 | event = new CustomEvent(type, { 783 | detail: data, 784 | bubbles: true, 785 | cancelable: true 786 | }); 787 | } else { 788 | event = document.createEvent('CustomEvent'); 789 | event.initCustomEvent(type, true, true, data); 790 | } 791 | 792 | return element.dispatchEvent(event); 793 | } 794 | /** 795 | * Get the offset base on the document. 796 | * @param {Element} element - The target element. 797 | * @returns {Object} The offset data. 798 | */ 799 | 800 | function getOffset(element) { 801 | var box = element.getBoundingClientRect(); 802 | return { 803 | left: box.left + (window.pageXOffset - document.documentElement.clientLeft), 804 | top: box.top + (window.pageYOffset - document.documentElement.clientTop) 805 | }; 806 | } 807 | /** 808 | * Get transforms base on the given object. 809 | * @param {Object} obj - The target object. 810 | * @returns {string} A string contains transform values. 811 | */ 812 | 813 | function getTransforms(_ref) { 814 | var rotate = _ref.rotate, 815 | scaleX = _ref.scaleX, 816 | scaleY = _ref.scaleY, 817 | translateX = _ref.translateX, 818 | translateY = _ref.translateY; 819 | var values = []; 820 | 821 | if (isNumber(translateX) && translateX !== 0) { 822 | values.push("translateX(".concat(translateX, "px)")); 823 | } 824 | 825 | if (isNumber(translateY) && translateY !== 0) { 826 | values.push("translateY(".concat(translateY, "px)")); 827 | } // Rotate should come first before scale to match orientation transform 828 | 829 | 830 | if (isNumber(rotate) && rotate !== 0) { 831 | values.push("rotate(".concat(rotate, "deg)")); 832 | } 833 | 834 | if (isNumber(scaleX) && scaleX !== 1) { 835 | values.push("scaleX(".concat(scaleX, ")")); 836 | } 837 | 838 | if (isNumber(scaleY) && scaleY !== 1) { 839 | values.push("scaleY(".concat(scaleY, ")")); 840 | } 841 | 842 | var transform = values.length ? values.join(' ') : 'none'; 843 | return { 844 | WebkitTransform: transform, 845 | msTransform: transform, 846 | transform: transform 847 | }; 848 | } 849 | /** 850 | * Get an image name from an image url. 851 | * @param {string} url - The target url. 852 | * @example 853 | * // picture.jpg 854 | * getImageNameFromURL('https://domain.com/path/to/picture.jpg?size=1280×960') 855 | * @returns {string} A string contains the image name. 856 | */ 857 | 858 | function getImageNameFromURL(url) { 859 | return isString(url) ? decodeURIComponent(url.replace(/^.*\//, '').replace(/[?&#].*$/, '')) : ''; 860 | } 861 | var IS_SAFARI = WINDOW.navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(WINDOW.navigator.userAgent); 862 | /** 863 | * Get an image's natural sizes. 864 | * @param {string} image - The target image. 865 | * @param {Function} callback - The callback function. 866 | * @returns {HTMLImageElement} The new image. 867 | */ 868 | 869 | function getImageNaturalSizes(image, callback) { 870 | var newImage = document.createElement('img'); // Modern browsers (except Safari) 871 | 872 | if (image.naturalWidth && !IS_SAFARI) { 873 | callback(image.naturalWidth, image.naturalHeight); 874 | return newImage; 875 | } 876 | 877 | var body = document.body || document.documentElement; 878 | 879 | newImage.onload = function () { 880 | callback(newImage.width, newImage.height); 881 | 882 | if (!IS_SAFARI) { 883 | body.removeChild(newImage); 884 | } 885 | }; 886 | 887 | newImage.src = image.src; // iOS Safari will convert the image automatically 888 | // with its orientation once append it into DOM 889 | 890 | if (!IS_SAFARI) { 891 | newImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;'; 892 | body.appendChild(newImage); 893 | } 894 | 895 | return newImage; 896 | } 897 | /** 898 | * Get the related class name of a responsive type number. 899 | * @param {string} type - The responsive type. 900 | * @returns {string} The related class name. 901 | */ 902 | 903 | function getResponsiveClass(type) { 904 | switch (type) { 905 | case 2: 906 | return CLASS_HIDE_XS_DOWN; 907 | 908 | case 3: 909 | return CLASS_HIDE_SM_DOWN; 910 | 911 | case 4: 912 | return CLASS_HIDE_MD_DOWN; 913 | 914 | default: 915 | return ''; 916 | } 917 | } 918 | /** 919 | * Get the max ratio of a group of pointers. 920 | * @param {string} pointers - The target pointers. 921 | * @returns {number} The result ratio. 922 | */ 923 | 924 | function getMaxZoomRatio(pointers) { 925 | var pointers2 = _objectSpread2({}, pointers); 926 | 927 | var ratios = []; 928 | forEach(pointers, function (pointer, pointerId) { 929 | delete pointers2[pointerId]; 930 | forEach(pointers2, function (pointer2) { 931 | var x1 = Math.abs(pointer.startX - pointer2.startX); 932 | var y1 = Math.abs(pointer.startY - pointer2.startY); 933 | var x2 = Math.abs(pointer.endX - pointer2.endX); 934 | var y2 = Math.abs(pointer.endY - pointer2.endY); 935 | var z1 = Math.sqrt(x1 * x1 + y1 * y1); 936 | var z2 = Math.sqrt(x2 * x2 + y2 * y2); 937 | var ratio = (z2 - z1) / z1; 938 | ratios.push(ratio); 939 | }); 940 | }); 941 | ratios.sort(function (a, b) { 942 | return Math.abs(a) < Math.abs(b); 943 | }); 944 | return ratios[0]; 945 | } 946 | /** 947 | * Get a pointer from an event object. 948 | * @param {Object} event - The target event object. 949 | * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not. 950 | * @returns {Object} The result pointer contains start and/or end point coordinates. 951 | */ 952 | 953 | function getPointer(_ref2, endOnly) { 954 | var pageX = _ref2.pageX, 955 | pageY = _ref2.pageY; 956 | var end = { 957 | endX: pageX, 958 | endY: pageY 959 | }; 960 | return endOnly ? end : _objectSpread2({ 961 | timeStamp: Date.now(), 962 | startX: pageX, 963 | startY: pageY 964 | }, end); 965 | } 966 | /** 967 | * Get the center point coordinate of a group of pointers. 968 | * @param {Object} pointers - The target pointers. 969 | * @returns {Object} The center point coordinate. 970 | */ 971 | 972 | function getPointersCenter(pointers) { 973 | var pageX = 0; 974 | var pageY = 0; 975 | var count = 0; 976 | forEach(pointers, function (_ref3) { 977 | var startX = _ref3.startX, 978 | startY = _ref3.startY; 979 | pageX += startX; 980 | pageY += startY; 981 | count += 1; 982 | }); 983 | pageX /= count; 984 | pageY /= count; 985 | return { 986 | pageX: pageX, 987 | pageY: pageY 988 | }; 989 | } 990 | 991 | var render = { 992 | render: function render() { 993 | this.initContainer(); 994 | this.initViewer(); 995 | this.initList(); 996 | this.renderViewer(); 997 | }, 998 | initContainer: function initContainer() { 999 | this.containerData = { 1000 | width: window.innerWidth, 1001 | height: window.innerHeight 1002 | }; 1003 | }, 1004 | initViewer: function initViewer() { 1005 | var options = this.options, 1006 | parent = this.parent; 1007 | var viewerData; 1008 | 1009 | if (options.inline) { 1010 | viewerData = { 1011 | width: Math.max(parent.offsetWidth, options.minWidth), 1012 | height: Math.max(parent.offsetHeight, options.minHeight) 1013 | }; 1014 | this.parentData = viewerData; 1015 | } 1016 | 1017 | if (this.fulled || !viewerData) { 1018 | viewerData = this.containerData; 1019 | } 1020 | 1021 | this.viewerData = assign({}, viewerData); 1022 | }, 1023 | renderViewer: function renderViewer() { 1024 | if (this.options.inline && !this.fulled) { 1025 | setStyle(this.viewer, this.viewerData); 1026 | } 1027 | }, 1028 | initList: function initList() { 1029 | var _this = this; 1030 | 1031 | var element = this.element, 1032 | options = this.options, 1033 | list = this.list; 1034 | var items = []; // initList may be called in this.update, so should keep idempotent 1035 | 1036 | list.innerHTML = ''; 1037 | forEach(this.images, function (image, index) { 1038 | var src = image.src; 1039 | var alt = image.alt || getImageNameFromURL(src); 1040 | var url = options.url; 1041 | 1042 | if (isString(url)) { 1043 | url = image.getAttribute(url); 1044 | } else if (isFunction(url)) { 1045 | url = url.call(_this, image); 1046 | } 1047 | 1048 | if (src || url) { 1049 | var item = document.createElement('li'); 1050 | var img = document.createElement('img'); 1051 | img.src = src || url; 1052 | img.alt = alt; 1053 | img.setAttribute('data-index', index); 1054 | img.setAttribute('data-original-url', url || src); 1055 | img.setAttribute('data-viewer-action', 'view'); 1056 | img.setAttribute('role', 'button'); 1057 | item.appendChild(img); 1058 | list.appendChild(item); 1059 | items.push(item); 1060 | } 1061 | }); 1062 | this.items = items; 1063 | forEach(items, function (item) { 1064 | var image = item.firstElementChild; 1065 | setData(image, 'filled', true); 1066 | 1067 | if (options.loading) { 1068 | addClass(item, CLASS_LOADING); 1069 | } 1070 | 1071 | addListener(image, EVENT_LOAD, function (event) { 1072 | if (options.loading) { 1073 | removeClass(item, CLASS_LOADING); 1074 | } 1075 | 1076 | _this.loadImage(event); 1077 | }, { 1078 | once: true 1079 | }); 1080 | }); 1081 | 1082 | if (options.transition) { 1083 | addListener(element, EVENT_VIEWED, function () { 1084 | addClass(list, CLASS_TRANSITION); 1085 | }, { 1086 | once: true 1087 | }); 1088 | } 1089 | }, 1090 | renderList: function renderList(index) { 1091 | var i = index || this.index; 1092 | var width = this.items[i].offsetWidth || 30; 1093 | var outerWidth = width + 1; // 1 pixel of `margin-left` width 1094 | // Place the active item in the center of the screen 1095 | 1096 | setStyle(this.list, assign({ 1097 | width: outerWidth * this.length 1098 | }, getTransforms({ 1099 | translateX: (this.viewerData.width - width) / 2 - outerWidth * i 1100 | }))); 1101 | }, 1102 | resetList: function resetList() { 1103 | var list = this.list; 1104 | list.innerHTML = ''; 1105 | removeClass(list, CLASS_TRANSITION); 1106 | setStyle(list, getTransforms({ 1107 | translateX: 0 1108 | })); 1109 | }, 1110 | initImage: function initImage(done) { 1111 | var _this2 = this; 1112 | 1113 | var options = this.options, 1114 | image = this.image, 1115 | viewerData = this.viewerData; 1116 | var footerHeight = this.footer.offsetHeight; 1117 | var viewerWidth = viewerData.width; 1118 | var viewerHeight = Math.max(viewerData.height - footerHeight, footerHeight); 1119 | var oldImageData = this.imageData || {}; 1120 | var sizingImage; 1121 | this.imageInitializing = { 1122 | abort: function abort() { 1123 | sizingImage.onload = null; 1124 | } 1125 | }; 1126 | sizingImage = getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { 1127 | var aspectRatio = naturalWidth / naturalHeight; 1128 | var width = viewerWidth; 1129 | var height = viewerHeight; 1130 | _this2.imageInitializing = false; 1131 | 1132 | if (viewerHeight * aspectRatio > viewerWidth) { 1133 | height = viewerWidth / aspectRatio; 1134 | } else { 1135 | width = viewerHeight * aspectRatio; 1136 | } 1137 | 1138 | width = Math.min(width * 0.9, naturalWidth); 1139 | height = Math.min(height * 0.9, naturalHeight); 1140 | var imageData = { 1141 | naturalWidth: naturalWidth, 1142 | naturalHeight: naturalHeight, 1143 | aspectRatio: aspectRatio, 1144 | ratio: width / naturalWidth, 1145 | width: width, 1146 | height: height, 1147 | left: (viewerWidth - width) / 2, 1148 | top: (viewerHeight - height) / 2 1149 | }; 1150 | var initialImageData = assign({}, imageData); 1151 | 1152 | if (options.rotatable) { 1153 | imageData.rotate = oldImageData.rotate || 0; 1154 | initialImageData.rotate = 0; 1155 | } 1156 | 1157 | if (options.scalable) { 1158 | imageData.scaleX = oldImageData.scaleX || 1; 1159 | imageData.scaleY = oldImageData.scaleY || 1; 1160 | initialImageData.scaleX = 1; 1161 | initialImageData.scaleY = 1; 1162 | } 1163 | 1164 | _this2.imageData = imageData; 1165 | _this2.initialImageData = initialImageData; 1166 | 1167 | if (done) { 1168 | done(); 1169 | } 1170 | }); 1171 | }, 1172 | renderImage: function renderImage(done) { 1173 | var _this3 = this; 1174 | 1175 | var image = this.image, 1176 | imageData = this.imageData; 1177 | setStyle(image, assign({ 1178 | width: imageData.width, 1179 | height: imageData.height, 1180 | // XXX: Not to use translateX/Y to avoid image shaking when zooming 1181 | marginLeft: imageData.left, 1182 | marginTop: imageData.top 1183 | }, getTransforms(imageData))); 1184 | 1185 | if (done) { 1186 | if ((this.viewing || this.zooming) && this.options.transition) { 1187 | var onTransitionEnd = function onTransitionEnd() { 1188 | _this3.imageRendering = false; 1189 | done(); 1190 | }; 1191 | 1192 | this.imageRendering = { 1193 | abort: function abort() { 1194 | removeListener(image, EVENT_TRANSITION_END, onTransitionEnd); 1195 | } 1196 | }; 1197 | addListener(image, EVENT_TRANSITION_END, onTransitionEnd, { 1198 | once: true 1199 | }); 1200 | } else { 1201 | done(); 1202 | } 1203 | } 1204 | }, 1205 | resetImage: function resetImage() { 1206 | // this.image only defined after viewed 1207 | if (this.viewing || this.viewed) { 1208 | var image = this.image; 1209 | 1210 | if (this.viewing) { 1211 | this.viewing.abort(); 1212 | } 1213 | 1214 | image.parentNode.removeChild(image); 1215 | this.image = null; 1216 | } 1217 | } 1218 | }; 1219 | 1220 | var events = { 1221 | bind: function bind() { 1222 | var options = this.options, 1223 | viewer = this.viewer, 1224 | canvas = this.canvas; 1225 | var document = this.element.ownerDocument; 1226 | addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this)); 1227 | addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this)); 1228 | addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this)); 1229 | addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this)); 1230 | addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this)); 1231 | addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this)); 1232 | addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this)); 1233 | 1234 | if (options.zoomable && options.zoomOnWheel) { 1235 | addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), { 1236 | passive: false, 1237 | capture: true 1238 | }); 1239 | } 1240 | 1241 | if (options.toggleOnDblclick) { 1242 | addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this)); 1243 | } 1244 | }, 1245 | unbind: function unbind() { 1246 | var options = this.options, 1247 | viewer = this.viewer, 1248 | canvas = this.canvas; 1249 | var document = this.element.ownerDocument; 1250 | removeListener(viewer, EVENT_CLICK, this.onClick); 1251 | removeListener(viewer, EVENT_DRAG_START, this.onDragStart); 1252 | removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown); 1253 | removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove); 1254 | removeListener(document, EVENT_POINTER_UP, this.onPointerUp); 1255 | removeListener(document, EVENT_KEY_DOWN, this.onKeyDown); 1256 | removeListener(window, EVENT_RESIZE, this.onResize); 1257 | 1258 | if (options.zoomable && options.zoomOnWheel) { 1259 | removeListener(viewer, EVENT_WHEEL, this.onWheel, { 1260 | passive: false, 1261 | capture: true 1262 | }); 1263 | } 1264 | 1265 | if (options.toggleOnDblclick) { 1266 | removeListener(canvas, EVENT_DBLCLICK, this.onDblclick); 1267 | } 1268 | } 1269 | }; 1270 | 1271 | var handlers = { 1272 | click: function click(event) { 1273 | var target = event.target; 1274 | var options = this.options, 1275 | imageData = this.imageData; 1276 | var action = getData(target, DATA_ACTION); // Cancel the emulated click when the native click event was triggered. 1277 | 1278 | if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) { 1279 | clearTimeout(this.clickCanvasTimeout); 1280 | } 1281 | 1282 | switch (action) { 1283 | case 'mix': 1284 | if (this.played) { 1285 | this.stop(); 1286 | } else if (options.inline) { 1287 | if (this.fulled) { 1288 | this.exit(); 1289 | } else { 1290 | this.full(); 1291 | } 1292 | } else { 1293 | this.hide(); 1294 | } 1295 | 1296 | break; 1297 | 1298 | case 'hide': 1299 | this.hide(); 1300 | break; 1301 | 1302 | case 'view': 1303 | this.view(getData(target, 'index')); 1304 | break; 1305 | 1306 | case 'zoom-in': 1307 | this.zoom(0.1, true); 1308 | break; 1309 | 1310 | case 'zoom-out': 1311 | this.zoom(-0.1, true); 1312 | break; 1313 | 1314 | case 'one-to-one': 1315 | this.toggle(); 1316 | break; 1317 | 1318 | case 'reset': 1319 | this.reset(); 1320 | break; 1321 | 1322 | case 'prev': 1323 | this.prev(options.loop); 1324 | break; 1325 | 1326 | case 'play': 1327 | this.play(options.fullscreen); 1328 | break; 1329 | 1330 | case 'next': 1331 | this.next(options.loop); 1332 | break; 1333 | 1334 | case 'rotate-left': 1335 | this.rotate(-90); 1336 | break; 1337 | 1338 | case 'rotate-right': 1339 | this.rotate(90); 1340 | break; 1341 | 1342 | case 'flip-horizontal': 1343 | this.scaleX(-imageData.scaleX || -1); 1344 | break; 1345 | 1346 | case 'flip-vertical': 1347 | this.scaleY(-imageData.scaleY || -1); 1348 | break; 1349 | 1350 | default: 1351 | if (this.played) { 1352 | this.stop(); 1353 | } 1354 | 1355 | } 1356 | }, 1357 | dblclick: function dblclick(event) { 1358 | event.preventDefault(); 1359 | 1360 | if (this.viewed && event.target === this.image) { 1361 | // Cancel the emulated double click when the native dblclick event was triggered. 1362 | if (IS_TOUCH_DEVICE && event.isTrusted) { 1363 | clearTimeout(this.doubleClickImageTimeout); 1364 | } 1365 | 1366 | this.toggle(); 1367 | } 1368 | }, 1369 | load: function load() { 1370 | var _this = this; 1371 | 1372 | if (this.timeout) { 1373 | clearTimeout(this.timeout); 1374 | this.timeout = false; 1375 | } 1376 | 1377 | var element = this.element, 1378 | options = this.options, 1379 | image = this.image, 1380 | index = this.index, 1381 | viewerData = this.viewerData; 1382 | removeClass(image, CLASS_INVISIBLE); 1383 | 1384 | if (options.loading) { 1385 | removeClass(this.canvas, CLASS_LOADING); 1386 | } 1387 | 1388 | image.style.cssText = 'height:0;' + "margin-left:".concat(viewerData.width / 2, "px;") + "margin-top:".concat(viewerData.height / 2, "px;") + 'max-width:none!important;' + 'position:absolute;' + 'width:0;'; 1389 | this.initImage(function () { 1390 | toggleClass(image, CLASS_MOVE, options.movable); 1391 | toggleClass(image, CLASS_TRANSITION, options.transition); 1392 | 1393 | _this.renderImage(function () { 1394 | _this.viewed = true; 1395 | _this.viewing = false; 1396 | 1397 | if (isFunction(options.viewed)) { 1398 | addListener(element, EVENT_VIEWED, options.viewed, { 1399 | once: true 1400 | }); 1401 | } 1402 | 1403 | dispatchEvent(element, EVENT_VIEWED, { 1404 | originalImage: _this.images[index], 1405 | index: index, 1406 | image: image 1407 | }); 1408 | }); 1409 | }); 1410 | }, 1411 | loadImage: function loadImage(event) { 1412 | var image = event.target; 1413 | var parent = image.parentNode; 1414 | var parentWidth = parent.offsetWidth || 30; 1415 | var parentHeight = parent.offsetHeight || 50; 1416 | var filled = !!getData(image, 'filled'); 1417 | getImageNaturalSizes(image, function (naturalWidth, naturalHeight) { 1418 | var aspectRatio = naturalWidth / naturalHeight; 1419 | var width = parentWidth; 1420 | var height = parentHeight; 1421 | 1422 | if (parentHeight * aspectRatio > parentWidth) { 1423 | if (filled) { 1424 | width = parentHeight * aspectRatio; 1425 | } else { 1426 | height = parentWidth / aspectRatio; 1427 | } 1428 | } else if (filled) { 1429 | height = parentWidth / aspectRatio; 1430 | } else { 1431 | width = parentHeight * aspectRatio; 1432 | } 1433 | 1434 | setStyle(image, assign({ 1435 | width: width, 1436 | height: height 1437 | }, getTransforms({ 1438 | translateX: (parentWidth - width) / 2, 1439 | translateY: (parentHeight - height) / 2 1440 | }))); 1441 | }); 1442 | }, 1443 | keydown: function keydown(event) { 1444 | var options = this.options; 1445 | 1446 | if (!this.fulled || !options.keyboard) { 1447 | return; 1448 | } 1449 | 1450 | switch (event.keyCode || event.which || event.charCode) { 1451 | // Escape 1452 | case 27: 1453 | if (this.played) { 1454 | this.stop(); 1455 | } else if (options.inline) { 1456 | if (this.fulled) { 1457 | this.exit(); 1458 | } 1459 | } else { 1460 | this.hide(); 1461 | } 1462 | 1463 | break; 1464 | // Space 1465 | 1466 | case 32: 1467 | if (this.played) { 1468 | this.stop(); 1469 | } 1470 | 1471 | break; 1472 | // ArrowLeft 1473 | 1474 | case 37: 1475 | this.prev(options.loop); 1476 | break; 1477 | // ArrowUp 1478 | 1479 | case 38: 1480 | // Prevent scroll on Firefox 1481 | event.preventDefault(); // Zoom in 1482 | 1483 | this.zoom(options.zoomRatio, true); 1484 | break; 1485 | // ArrowRight 1486 | 1487 | case 39: 1488 | this.next(options.loop); 1489 | break; 1490 | // ArrowDown 1491 | 1492 | case 40: 1493 | // Prevent scroll on Firefox 1494 | event.preventDefault(); // Zoom out 1495 | 1496 | this.zoom(-options.zoomRatio, true); 1497 | break; 1498 | // Ctrl + 0 1499 | 1500 | case 48: // Fall through 1501 | // Ctrl + 1 1502 | // eslint-disable-next-line no-fallthrough 1503 | 1504 | case 49: 1505 | if (event.ctrlKey) { 1506 | event.preventDefault(); 1507 | this.toggle(); 1508 | } 1509 | 1510 | break; 1511 | } 1512 | }, 1513 | dragstart: function dragstart(event) { 1514 | if (event.target.tagName.toLowerCase() === 'img') { 1515 | event.preventDefault(); 1516 | } 1517 | }, 1518 | pointerdown: function pointerdown(event) { 1519 | var options = this.options, 1520 | pointers = this.pointers; 1521 | var buttons = event.buttons, 1522 | button = event.button; 1523 | 1524 | if (!this.viewed || this.showing || this.viewing || this.hiding // Handle mouse event and pointer event and ignore touch event 1525 | || (event.type === 'mousedown' || event.type === 'pointerdown' && event.pointerType === 'mouse') && ( // No primary button (Usually the left button) 1526 | isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0 // Open context menu 1527 | || event.ctrlKey)) { 1528 | return; 1529 | } // Prevent default behaviours as page zooming in touch devices. 1530 | 1531 | 1532 | event.preventDefault(); 1533 | 1534 | if (event.changedTouches) { 1535 | forEach(event.changedTouches, function (touch) { 1536 | pointers[touch.identifier] = getPointer(touch); 1537 | }); 1538 | } else { 1539 | pointers[event.pointerId || 0] = getPointer(event); 1540 | } 1541 | 1542 | var action = options.movable ? ACTION_MOVE : false; 1543 | 1544 | if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) { 1545 | action = ACTION_ZOOM; 1546 | } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) { 1547 | action = ACTION_SWITCH; 1548 | } 1549 | 1550 | if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { 1551 | removeClass(this.image, CLASS_TRANSITION); 1552 | } 1553 | 1554 | this.action = action; 1555 | }, 1556 | pointermove: function pointermove(event) { 1557 | var pointers = this.pointers, 1558 | action = this.action; 1559 | 1560 | if (!this.viewed || !action) { 1561 | return; 1562 | } 1563 | 1564 | event.preventDefault(); 1565 | 1566 | if (event.changedTouches) { 1567 | forEach(event.changedTouches, function (touch) { 1568 | assign(pointers[touch.identifier] || {}, getPointer(touch, true)); 1569 | }); 1570 | } else { 1571 | assign(pointers[event.pointerId || 0] || {}, getPointer(event, true)); 1572 | } 1573 | 1574 | this.change(event); 1575 | }, 1576 | pointerup: function pointerup(event) { 1577 | var _this2 = this; 1578 | 1579 | var options = this.options, 1580 | action = this.action, 1581 | pointers = this.pointers; 1582 | var pointer; 1583 | 1584 | if (event.changedTouches) { 1585 | forEach(event.changedTouches, function (touch) { 1586 | pointer = pointers[touch.identifier]; 1587 | delete pointers[touch.identifier]; 1588 | }); 1589 | } else { 1590 | pointer = pointers[event.pointerId || 0]; 1591 | delete pointers[event.pointerId || 0]; 1592 | } 1593 | 1594 | if (!action) { 1595 | return; 1596 | } 1597 | 1598 | event.preventDefault(); 1599 | 1600 | if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) { 1601 | addClass(this.image, CLASS_TRANSITION); 1602 | } 1603 | 1604 | this.action = false; // Emulate click and double click in touch devices to support backdrop and image zooming (#210). 1605 | 1606 | if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) { 1607 | clearTimeout(this.clickCanvasTimeout); 1608 | clearTimeout(this.doubleClickImageTimeout); 1609 | 1610 | if (options.toggleOnDblclick && this.viewed && event.target === this.image) { 1611 | if (this.imageClicked) { 1612 | this.imageClicked = false; // This timeout will be cleared later when a native dblclick event is triggering 1613 | 1614 | this.doubleClickImageTimeout = setTimeout(function () { 1615 | dispatchEvent(_this2.image, EVENT_DBLCLICK); 1616 | }, 50); 1617 | } else { 1618 | this.imageClicked = true; // The default timing of a double click in Windows is 500 ms 1619 | 1620 | this.doubleClickImageTimeout = setTimeout(function () { 1621 | _this2.imageClicked = false; 1622 | }, 500); 1623 | } 1624 | } else { 1625 | this.imageClicked = false; 1626 | 1627 | if (options.backdrop && options.backdrop !== 'static' && event.target === this.canvas) { 1628 | // This timeout will be cleared later when a native click event is triggering 1629 | this.clickCanvasTimeout = setTimeout(function () { 1630 | dispatchEvent(_this2.canvas, EVENT_CLICK); 1631 | }, 50); 1632 | } 1633 | } 1634 | } 1635 | }, 1636 | resize: function resize() { 1637 | var _this3 = this; 1638 | 1639 | if (!this.isShown || this.hiding) { 1640 | return; 1641 | } 1642 | 1643 | this.initContainer(); 1644 | this.initViewer(); 1645 | this.renderViewer(); 1646 | this.renderList(); 1647 | 1648 | if (this.viewed) { 1649 | this.initImage(function () { 1650 | _this3.renderImage(); 1651 | }); 1652 | } 1653 | 1654 | if (this.played) { 1655 | if (this.options.fullscreen && this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { 1656 | this.stop(); 1657 | return; 1658 | } 1659 | 1660 | forEach(this.player.getElementsByTagName('img'), function (image) { 1661 | addListener(image, EVENT_LOAD, _this3.loadImage.bind(_this3), { 1662 | once: true 1663 | }); 1664 | dispatchEvent(image, EVENT_LOAD); 1665 | }); 1666 | } 1667 | }, 1668 | wheel: function wheel(event) { 1669 | var _this4 = this; 1670 | 1671 | if (!this.viewed) { 1672 | return; 1673 | } 1674 | 1675 | event.preventDefault(); // Limit wheel speed to prevent zoom too fast 1676 | 1677 | if (this.wheeling) { 1678 | return; 1679 | } 1680 | 1681 | this.wheeling = true; 1682 | setTimeout(function () { 1683 | _this4.wheeling = false; 1684 | }, 50); 1685 | var ratio = Number(this.options.zoomRatio) || 0.1; 1686 | var delta = 1; 1687 | 1688 | if (event.deltaY) { 1689 | delta = event.deltaY > 0 ? 1 : -1; 1690 | } else if (event.wheelDelta) { 1691 | delta = -event.wheelDelta / 120; 1692 | } else if (event.detail) { 1693 | delta = event.detail > 0 ? 1 : -1; 1694 | } 1695 | 1696 | this.zoom(-delta * ratio, true, event); 1697 | } 1698 | }; 1699 | 1700 | var methods = { 1701 | /** Show the viewer (only available in modal mode) 1702 | * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not. 1703 | * @returns {Viewer} this 1704 | */ 1705 | show: function show() { 1706 | var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 1707 | var element = this.element, 1708 | options = this.options; 1709 | 1710 | if (options.inline || this.showing || this.isShown || this.showing) { 1711 | return this; 1712 | } 1713 | 1714 | if (!this.ready) { 1715 | this.build(); 1716 | 1717 | if (this.ready) { 1718 | this.show(immediate); 1719 | } 1720 | 1721 | return this; 1722 | } 1723 | 1724 | if (isFunction(options.show)) { 1725 | addListener(element, EVENT_SHOW, options.show, { 1726 | once: true 1727 | }); 1728 | } 1729 | 1730 | if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) { 1731 | return this; 1732 | } 1733 | 1734 | if (this.hiding) { 1735 | this.transitioning.abort(); 1736 | } 1737 | 1738 | this.showing = true; 1739 | this.open(); 1740 | var viewer = this.viewer; 1741 | removeClass(viewer, CLASS_HIDE); 1742 | 1743 | if (options.transition && !immediate) { 1744 | var shown = this.shown.bind(this); 1745 | this.transitioning = { 1746 | abort: function abort() { 1747 | removeListener(viewer, EVENT_TRANSITION_END, shown); 1748 | removeClass(viewer, CLASS_IN); 1749 | } 1750 | }; 1751 | addClass(viewer, CLASS_TRANSITION); // Force reflow to enable CSS3 transition 1752 | 1753 | viewer.initialOffsetWidth = viewer.offsetWidth; 1754 | addListener(viewer, EVENT_TRANSITION_END, shown, { 1755 | once: true 1756 | }); 1757 | addClass(viewer, CLASS_IN); 1758 | } else { 1759 | addClass(viewer, CLASS_IN); 1760 | this.shown(); 1761 | } 1762 | 1763 | return this; 1764 | }, 1765 | 1766 | /** 1767 | * Hide the viewer (only available in modal mode) 1768 | * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not. 1769 | * @returns {Viewer} this 1770 | */ 1771 | hide: function hide() { 1772 | var immediate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 1773 | var element = this.element, 1774 | options = this.options; 1775 | 1776 | if (options.inline || this.hiding || !(this.isShown || this.showing)) { 1777 | return this; 1778 | } 1779 | 1780 | if (isFunction(options.hide)) { 1781 | addListener(element, EVENT_HIDE, options.hide, { 1782 | once: true 1783 | }); 1784 | } 1785 | 1786 | if (dispatchEvent(element, EVENT_HIDE) === false) { 1787 | return this; 1788 | } 1789 | 1790 | if (this.showing) { 1791 | this.transitioning.abort(); 1792 | } 1793 | 1794 | this.hiding = true; 1795 | 1796 | if (this.played) { 1797 | this.stop(); 1798 | } else if (this.viewing) { 1799 | this.viewing.abort(); 1800 | } 1801 | 1802 | var viewer = this.viewer; 1803 | 1804 | if (options.transition && !immediate) { 1805 | var hidden = this.hidden.bind(this); 1806 | 1807 | var hide = function hide() { 1808 | // XXX: It seems the `event.stopPropagation()` method does not work here 1809 | setTimeout(function () { 1810 | addListener(viewer, EVENT_TRANSITION_END, hidden, { 1811 | once: true 1812 | }); 1813 | removeClass(viewer, CLASS_IN); 1814 | }, 0); 1815 | }; 1816 | 1817 | this.transitioning = { 1818 | abort: function abort() { 1819 | if (this.viewed) { 1820 | removeListener(this.image, EVENT_TRANSITION_END, hide); 1821 | } else { 1822 | removeListener(viewer, EVENT_TRANSITION_END, hidden); 1823 | } 1824 | } 1825 | }; // Note that the `CLASS_TRANSITION` class will be removed on pointer down (#255) 1826 | 1827 | if (this.viewed && hasClass(this.image, CLASS_TRANSITION)) { 1828 | addListener(this.image, EVENT_TRANSITION_END, hide, { 1829 | once: true 1830 | }); 1831 | this.zoomTo(0, false, false, true); 1832 | } else { 1833 | hide(); 1834 | } 1835 | } else { 1836 | removeClass(viewer, CLASS_IN); 1837 | this.hidden(); 1838 | } 1839 | 1840 | return this; 1841 | }, 1842 | 1843 | /** 1844 | * View one of the images with image's index 1845 | * @param {number} index - The index of the image to view. 1846 | * @returns {Viewer} this 1847 | */ 1848 | view: function view() { 1849 | var _this = this; 1850 | 1851 | var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex; 1852 | index = Number(index) || 0; 1853 | 1854 | if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) { 1855 | return this; 1856 | } 1857 | 1858 | if (!this.isShown) { 1859 | this.index = index; 1860 | return this.show(); 1861 | } 1862 | 1863 | if (this.viewing) { 1864 | this.viewing.abort(); 1865 | } 1866 | 1867 | var element = this.element, 1868 | options = this.options, 1869 | title = this.title, 1870 | canvas = this.canvas; 1871 | var item = this.items[index]; 1872 | var img = item.querySelector('img'); 1873 | var url = getData(img, 'originalUrl'); 1874 | var alt = img.getAttribute('alt'); 1875 | var image = document.createElement('img'); 1876 | image.src = url; 1877 | image.alt = alt; 1878 | 1879 | if (isFunction(options.view)) { 1880 | addListener(element, EVENT_VIEW, options.view, { 1881 | once: true 1882 | }); 1883 | } 1884 | 1885 | if (dispatchEvent(element, EVENT_VIEW, { 1886 | originalImage: this.images[index], 1887 | index: index, 1888 | image: image 1889 | }) === false || !this.isShown || this.hiding || this.played) { 1890 | return this; 1891 | } 1892 | 1893 | this.image = image; 1894 | removeClass(this.items[this.index], CLASS_ACTIVE); 1895 | addClass(item, CLASS_ACTIVE); 1896 | this.viewed = false; 1897 | this.index = index; 1898 | this.imageData = {}; 1899 | addClass(image, CLASS_INVISIBLE); 1900 | 1901 | if (options.loading) { 1902 | addClass(canvas, CLASS_LOADING); 1903 | } 1904 | 1905 | canvas.innerHTML = ''; 1906 | canvas.appendChild(image); // Center current item 1907 | 1908 | this.renderList(); // Clear title 1909 | 1910 | title.innerHTML = ''; // Generate title after viewed 1911 | 1912 | var onViewed = function onViewed() { 1913 | var imageData = _this.imageData; 1914 | var render = Array.isArray(options.title) ? options.title[1] : options.title; 1915 | title.innerHTML = escapeHTMLEntities(isFunction(render) ? render.call(_this, image, imageData) : "".concat(alt, " (").concat(imageData.naturalWidth, " \xD7 ").concat(imageData.naturalHeight, ")")); 1916 | }; 1917 | 1918 | var onLoad; 1919 | addListener(element, EVENT_VIEWED, onViewed, { 1920 | once: true 1921 | }); 1922 | this.viewing = { 1923 | abort: function abort() { 1924 | removeListener(element, EVENT_VIEWED, onViewed); 1925 | 1926 | if (image.complete) { 1927 | if (this.imageRendering) { 1928 | this.imageRendering.abort(); 1929 | } else if (this.imageInitializing) { 1930 | this.imageInitializing.abort(); 1931 | } 1932 | } else { 1933 | // Cancel download to save bandwidth. 1934 | image.src = ''; 1935 | removeListener(image, EVENT_LOAD, onLoad); 1936 | 1937 | if (this.timeout) { 1938 | clearTimeout(this.timeout); 1939 | } 1940 | } 1941 | } 1942 | }; 1943 | 1944 | if (image.complete) { 1945 | this.load(); 1946 | } else { 1947 | addListener(image, EVENT_LOAD, onLoad = this.load.bind(this), { 1948 | once: true 1949 | }); 1950 | 1951 | if (this.timeout) { 1952 | clearTimeout(this.timeout); 1953 | } // Make the image visible if it fails to load within 1s 1954 | 1955 | 1956 | this.timeout = setTimeout(function () { 1957 | removeClass(image, CLASS_INVISIBLE); 1958 | _this.timeout = false; 1959 | }, 1000); 1960 | } 1961 | 1962 | return this; 1963 | }, 1964 | 1965 | /** 1966 | * View the previous image 1967 | * @param {boolean} [loop=false] - Indicate if view the last one 1968 | * when it is the first one at present. 1969 | * @returns {Viewer} this 1970 | */ 1971 | prev: function prev() { 1972 | var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 1973 | var index = this.index - 1; 1974 | 1975 | if (index < 0) { 1976 | index = loop ? this.length - 1 : 0; 1977 | } 1978 | 1979 | this.view(index); 1980 | return this; 1981 | }, 1982 | 1983 | /** 1984 | * View the next image 1985 | * @param {boolean} [loop=false] - Indicate if view the first one 1986 | * when it is the last one at present. 1987 | * @returns {Viewer} this 1988 | */ 1989 | next: function next() { 1990 | var loop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 1991 | var maxIndex = this.length - 1; 1992 | var index = this.index + 1; 1993 | 1994 | if (index > maxIndex) { 1995 | index = loop ? 0 : maxIndex; 1996 | } 1997 | 1998 | this.view(index); 1999 | return this; 2000 | }, 2001 | 2002 | /** 2003 | * Move the image with relative offsets. 2004 | * @param {number} offsetX - The relative offset distance on the x-axis. 2005 | * @param {number} offsetY - The relative offset distance on the y-axis. 2006 | * @returns {Viewer} this 2007 | */ 2008 | move: function move(offsetX, offsetY) { 2009 | var imageData = this.imageData; 2010 | this.moveTo(isUndefined(offsetX) ? offsetX : imageData.left + Number(offsetX), isUndefined(offsetY) ? offsetY : imageData.top + Number(offsetY)); 2011 | return this; 2012 | }, 2013 | 2014 | /** 2015 | * Move the image to an absolute point. 2016 | * @param {number} x - The x-axis coordinate. 2017 | * @param {number} [y=x] - The y-axis coordinate. 2018 | * @returns {Viewer} this 2019 | */ 2020 | moveTo: function moveTo(x) { 2021 | var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x; 2022 | var imageData = this.imageData; 2023 | x = Number(x); 2024 | y = Number(y); 2025 | 2026 | if (this.viewed && !this.played && this.options.movable) { 2027 | var changed = false; 2028 | 2029 | if (isNumber(x)) { 2030 | imageData.left = x; 2031 | changed = true; 2032 | } 2033 | 2034 | if (isNumber(y)) { 2035 | imageData.top = y; 2036 | changed = true; 2037 | } 2038 | 2039 | if (changed) { 2040 | this.renderImage(); 2041 | } 2042 | } 2043 | 2044 | return this; 2045 | }, 2046 | 2047 | /** 2048 | * Zoom the image with a relative ratio. 2049 | * @param {number} ratio - The target ratio. 2050 | * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not. 2051 | * @param {Event} [_originalEvent=null] - The original event if any. 2052 | * @returns {Viewer} this 2053 | */ 2054 | zoom: function zoom(ratio) { 2055 | var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 2056 | 2057 | var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; 2058 | 2059 | var imageData = this.imageData; 2060 | ratio = Number(ratio); 2061 | 2062 | if (ratio < 0) { 2063 | ratio = 1 / (1 - ratio); 2064 | } else { 2065 | ratio = 1 + ratio; 2066 | } 2067 | 2068 | this.zoomTo(imageData.width * ratio / imageData.naturalWidth, hasTooltip, _originalEvent); 2069 | return this; 2070 | }, 2071 | 2072 | /** 2073 | * Zoom the image to an absolute ratio. 2074 | * @param {number} ratio - The target ratio. 2075 | * @param {boolean} [hasTooltip=false] - Indicates if it has a tooltip or not. 2076 | * @param {Event} [_originalEvent=null] - The original event if any. 2077 | * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not. 2078 | * @returns {Viewer} this 2079 | */ 2080 | zoomTo: function zoomTo(ratio) { 2081 | var _this2 = this; 2082 | 2083 | var hasTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 2084 | 2085 | var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; 2086 | 2087 | var _zoomable = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; 2088 | 2089 | var element = this.element, 2090 | options = this.options, 2091 | pointers = this.pointers, 2092 | imageData = this.imageData; 2093 | var width = imageData.width, 2094 | height = imageData.height, 2095 | left = imageData.left, 2096 | top = imageData.top, 2097 | naturalWidth = imageData.naturalWidth, 2098 | naturalHeight = imageData.naturalHeight; 2099 | ratio = Math.max(0, ratio); 2100 | 2101 | if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) { 2102 | if (!_zoomable) { 2103 | var minZoomRatio = Math.max(0.01, options.minZoomRatio); 2104 | var maxZoomRatio = Math.min(100, options.maxZoomRatio); 2105 | ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio); 2106 | } 2107 | 2108 | if (_originalEvent && ratio > 0.95 && ratio < 1.05) { 2109 | ratio = 1; 2110 | } 2111 | 2112 | var newWidth = naturalWidth * ratio; 2113 | var newHeight = naturalHeight * ratio; 2114 | var offsetWidth = newWidth - width; 2115 | var offsetHeight = newHeight - height; 2116 | var oldRatio = width / naturalWidth; 2117 | 2118 | if (isFunction(options.zoom)) { 2119 | addListener(element, EVENT_ZOOM, options.zoom, { 2120 | once: true 2121 | }); 2122 | } 2123 | 2124 | if (dispatchEvent(element, EVENT_ZOOM, { 2125 | ratio: ratio, 2126 | oldRatio: oldRatio, 2127 | originalEvent: _originalEvent 2128 | }) === false) { 2129 | return this; 2130 | } 2131 | 2132 | this.zooming = true; 2133 | 2134 | if (_originalEvent) { 2135 | var offset = getOffset(this.viewer); 2136 | var center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : { 2137 | pageX: _originalEvent.pageX, 2138 | pageY: _originalEvent.pageY 2139 | }; // Zoom from the triggering point of the event 2140 | 2141 | imageData.left -= offsetWidth * ((center.pageX - offset.left - left) / width); 2142 | imageData.top -= offsetHeight * ((center.pageY - offset.top - top) / height); 2143 | } else { 2144 | // Zoom from the center of the image 2145 | imageData.left -= offsetWidth / 2; 2146 | imageData.top -= offsetHeight / 2; 2147 | } 2148 | 2149 | imageData.width = newWidth; 2150 | imageData.height = newHeight; 2151 | imageData.ratio = ratio; 2152 | this.renderImage(function () { 2153 | _this2.zooming = false; 2154 | 2155 | if (isFunction(options.zoomed)) { 2156 | addListener(element, EVENT_ZOOMED, options.zoomed, { 2157 | once: true 2158 | }); 2159 | } 2160 | 2161 | dispatchEvent(element, EVENT_ZOOMED, { 2162 | ratio: ratio, 2163 | oldRatio: oldRatio, 2164 | originalEvent: _originalEvent 2165 | }); 2166 | }); 2167 | 2168 | if (hasTooltip) { 2169 | this.tooltip(); 2170 | } 2171 | } 2172 | 2173 | return this; 2174 | }, 2175 | 2176 | /** 2177 | * Rotate the image with a relative degree. 2178 | * @param {number} degree - The rotate degree. 2179 | * @returns {Viewer} this 2180 | */ 2181 | rotate: function rotate(degree) { 2182 | this.rotateTo((this.imageData.rotate || 0) + Number(degree)); 2183 | return this; 2184 | }, 2185 | 2186 | /** 2187 | * Rotate the image to an absolute degree. 2188 | * @param {number} degree - The rotate degree. 2189 | * @returns {Viewer} this 2190 | */ 2191 | rotateTo: function rotateTo(degree) { 2192 | var imageData = this.imageData; 2193 | degree = Number(degree); 2194 | 2195 | if (isNumber(degree) && this.viewed && !this.played && this.options.rotatable) { 2196 | imageData.rotate = degree; 2197 | this.renderImage(); 2198 | } 2199 | 2200 | return this; 2201 | }, 2202 | 2203 | /** 2204 | * Scale the image on the x-axis. 2205 | * @param {number} scaleX - The scale ratio on the x-axis. 2206 | * @returns {Viewer} this 2207 | */ 2208 | scaleX: function scaleX(_scaleX) { 2209 | this.scale(_scaleX, this.imageData.scaleY); 2210 | return this; 2211 | }, 2212 | 2213 | /** 2214 | * Scale the image on the y-axis. 2215 | * @param {number} scaleY - The scale ratio on the y-axis. 2216 | * @returns {Viewer} this 2217 | */ 2218 | scaleY: function scaleY(_scaleY) { 2219 | this.scale(this.imageData.scaleX, _scaleY); 2220 | return this; 2221 | }, 2222 | 2223 | /** 2224 | * Scale the image. 2225 | * @param {number} scaleX - The scale ratio on the x-axis. 2226 | * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis. 2227 | * @returns {Viewer} this 2228 | */ 2229 | scale: function scale(scaleX) { 2230 | var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX; 2231 | var imageData = this.imageData; 2232 | scaleX = Number(scaleX); 2233 | scaleY = Number(scaleY); 2234 | 2235 | if (this.viewed && !this.played && this.options.scalable) { 2236 | var changed = false; 2237 | 2238 | if (isNumber(scaleX)) { 2239 | imageData.scaleX = scaleX; 2240 | changed = true; 2241 | } 2242 | 2243 | if (isNumber(scaleY)) { 2244 | imageData.scaleY = scaleY; 2245 | changed = true; 2246 | } 2247 | 2248 | if (changed) { 2249 | this.renderImage(); 2250 | } 2251 | } 2252 | 2253 | return this; 2254 | }, 2255 | 2256 | /** 2257 | * Play the images 2258 | * @param {boolean} [fullscreen=false] - Indicate if request fullscreen or not. 2259 | * @returns {Viewer} this 2260 | */ 2261 | play: function play() { 2262 | var _this3 = this; 2263 | 2264 | var fullscreen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 2265 | 2266 | if (!this.isShown || this.played) { 2267 | return this; 2268 | } 2269 | 2270 | var options = this.options, 2271 | player = this.player; 2272 | var onLoad = this.loadImage.bind(this); 2273 | var list = []; 2274 | var total = 0; 2275 | var index = 0; 2276 | this.played = true; 2277 | this.onLoadWhenPlay = onLoad; 2278 | 2279 | if (fullscreen) { 2280 | this.requestFullscreen(); 2281 | } 2282 | 2283 | addClass(player, CLASS_SHOW); 2284 | forEach(this.items, function (item, i) { 2285 | var img = item.querySelector('img'); 2286 | var image = document.createElement('img'); 2287 | image.src = getData(img, 'originalUrl'); 2288 | image.alt = img.getAttribute('alt'); 2289 | total += 1; 2290 | addClass(image, CLASS_FADE); 2291 | toggleClass(image, CLASS_TRANSITION, options.transition); 2292 | 2293 | if (hasClass(item, CLASS_ACTIVE)) { 2294 | addClass(image, CLASS_IN); 2295 | index = i; 2296 | } 2297 | 2298 | list.push(image); 2299 | addListener(image, EVENT_LOAD, onLoad, { 2300 | once: true 2301 | }); 2302 | player.appendChild(image); 2303 | }); 2304 | 2305 | if (isNumber(options.interval) && options.interval > 0) { 2306 | var play = function play() { 2307 | _this3.playing = setTimeout(function () { 2308 | removeClass(list[index], CLASS_IN); 2309 | index += 1; 2310 | index = index < total ? index : 0; 2311 | addClass(list[index], CLASS_IN); 2312 | play(); 2313 | }, options.interval); 2314 | }; 2315 | 2316 | if (total > 1) { 2317 | play(); 2318 | } 2319 | } 2320 | 2321 | return this; 2322 | }, 2323 | // Stop play 2324 | stop: function stop() { 2325 | var _this4 = this; 2326 | 2327 | if (!this.played) { 2328 | return this; 2329 | } 2330 | 2331 | var player = this.player; 2332 | this.played = false; 2333 | clearTimeout(this.playing); 2334 | forEach(player.getElementsByTagName('img'), function (image) { 2335 | removeListener(image, EVENT_LOAD, _this4.onLoadWhenPlay); 2336 | }); 2337 | removeClass(player, CLASS_SHOW); 2338 | player.innerHTML = ''; 2339 | this.exitFullscreen(); 2340 | return this; 2341 | }, 2342 | // Enter modal mode (only available in inline mode) 2343 | full: function full() { 2344 | var _this5 = this; 2345 | 2346 | var options = this.options, 2347 | viewer = this.viewer, 2348 | image = this.image, 2349 | list = this.list; 2350 | 2351 | if (!this.isShown || this.played || this.fulled || !options.inline) { 2352 | return this; 2353 | } 2354 | 2355 | this.fulled = true; 2356 | this.open(); 2357 | addClass(this.button, CLASS_FULLSCREEN_EXIT); 2358 | 2359 | if (options.transition) { 2360 | removeClass(list, CLASS_TRANSITION); 2361 | 2362 | if (this.viewed) { 2363 | removeClass(image, CLASS_TRANSITION); 2364 | } 2365 | } 2366 | 2367 | addClass(viewer, CLASS_FIXED); 2368 | viewer.setAttribute('style', ''); 2369 | setStyle(viewer, { 2370 | zIndex: options.zIndex 2371 | }); 2372 | this.initContainer(); 2373 | this.viewerData = assign({}, this.containerData); 2374 | this.renderList(); 2375 | 2376 | if (this.viewed) { 2377 | this.initImage(function () { 2378 | _this5.renderImage(function () { 2379 | if (options.transition) { 2380 | setTimeout(function () { 2381 | addClass(image, CLASS_TRANSITION); 2382 | addClass(list, CLASS_TRANSITION); 2383 | }, 0); 2384 | } 2385 | }); 2386 | }); 2387 | } 2388 | 2389 | return this; 2390 | }, 2391 | // Exit modal mode (only available in inline mode) 2392 | exit: function exit() { 2393 | var _this6 = this; 2394 | 2395 | var options = this.options, 2396 | viewer = this.viewer, 2397 | image = this.image, 2398 | list = this.list; 2399 | 2400 | if (!this.isShown || this.played || !this.fulled || !options.inline) { 2401 | return this; 2402 | } 2403 | 2404 | this.fulled = false; 2405 | this.close(); 2406 | removeClass(this.button, CLASS_FULLSCREEN_EXIT); 2407 | 2408 | if (options.transition) { 2409 | removeClass(list, CLASS_TRANSITION); 2410 | 2411 | if (this.viewed) { 2412 | removeClass(image, CLASS_TRANSITION); 2413 | } 2414 | } 2415 | 2416 | removeClass(viewer, CLASS_FIXED); 2417 | setStyle(viewer, { 2418 | zIndex: options.zIndexInline 2419 | }); 2420 | this.viewerData = assign({}, this.parentData); 2421 | this.renderViewer(); 2422 | this.renderList(); 2423 | 2424 | if (this.viewed) { 2425 | this.initImage(function () { 2426 | _this6.renderImage(function () { 2427 | if (options.transition) { 2428 | setTimeout(function () { 2429 | addClass(image, CLASS_TRANSITION); 2430 | addClass(list, CLASS_TRANSITION); 2431 | }, 0); 2432 | } 2433 | }); 2434 | }); 2435 | } 2436 | 2437 | return this; 2438 | }, 2439 | // Show the current ratio of the image with percentage 2440 | tooltip: function tooltip() { 2441 | var _this7 = this; 2442 | 2443 | var options = this.options, 2444 | tooltipBox = this.tooltipBox, 2445 | imageData = this.imageData; 2446 | 2447 | if (!this.viewed || this.played || !options.tooltip) { 2448 | return this; 2449 | } 2450 | 2451 | tooltipBox.textContent = "".concat(Math.round(imageData.ratio * 100), "%"); 2452 | 2453 | if (!this.tooltipping) { 2454 | if (options.transition) { 2455 | if (this.fading) { 2456 | dispatchEvent(tooltipBox, EVENT_TRANSITION_END); 2457 | } 2458 | 2459 | addClass(tooltipBox, CLASS_SHOW); 2460 | addClass(tooltipBox, CLASS_FADE); 2461 | addClass(tooltipBox, CLASS_TRANSITION); // Force reflow to enable CSS3 transition 2462 | 2463 | tooltipBox.initialOffsetWidth = tooltipBox.offsetWidth; 2464 | addClass(tooltipBox, CLASS_IN); 2465 | } else { 2466 | addClass(tooltipBox, CLASS_SHOW); 2467 | } 2468 | } else { 2469 | clearTimeout(this.tooltipping); 2470 | } 2471 | 2472 | this.tooltipping = setTimeout(function () { 2473 | if (options.transition) { 2474 | addListener(tooltipBox, EVENT_TRANSITION_END, function () { 2475 | removeClass(tooltipBox, CLASS_SHOW); 2476 | removeClass(tooltipBox, CLASS_FADE); 2477 | removeClass(tooltipBox, CLASS_TRANSITION); 2478 | _this7.fading = false; 2479 | }, { 2480 | once: true 2481 | }); 2482 | removeClass(tooltipBox, CLASS_IN); 2483 | _this7.fading = true; 2484 | } else { 2485 | removeClass(tooltipBox, CLASS_SHOW); 2486 | } 2487 | 2488 | _this7.tooltipping = false; 2489 | }, 1000); 2490 | return this; 2491 | }, 2492 | // Toggle the image size between its natural size and initial size 2493 | toggle: function toggle() { 2494 | if (this.imageData.ratio === 1) { 2495 | this.zoomTo(this.initialImageData.ratio, true); 2496 | } else { 2497 | this.zoomTo(1, true); 2498 | } 2499 | 2500 | return this; 2501 | }, 2502 | // Reset the image to its initial state 2503 | reset: function reset() { 2504 | if (this.viewed && !this.played) { 2505 | this.imageData = assign({}, this.initialImageData); 2506 | this.renderImage(); 2507 | } 2508 | 2509 | return this; 2510 | }, 2511 | // Update viewer when images changed 2512 | update: function update() { 2513 | var element = this.element, 2514 | options = this.options, 2515 | isImg = this.isImg; // Destroy viewer if the target image was deleted 2516 | 2517 | if (isImg && !element.parentNode) { 2518 | return this.destroy(); 2519 | } 2520 | 2521 | var images = []; 2522 | forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) { 2523 | if (options.filter) { 2524 | if (options.filter(image)) { 2525 | images.push(image); 2526 | } 2527 | } else { 2528 | images.push(image); 2529 | } 2530 | }); 2531 | 2532 | if (!images.length) { 2533 | return this; 2534 | } 2535 | 2536 | this.images = images; 2537 | this.length = images.length; 2538 | 2539 | if (this.ready) { 2540 | var indexes = []; 2541 | forEach(this.items, function (item, i) { 2542 | var img = item.querySelector('img'); 2543 | var image = images[i]; 2544 | 2545 | if (image && img) { 2546 | if (image.src !== img.src) { 2547 | indexes.push(i); 2548 | } 2549 | } else { 2550 | indexes.push(i); 2551 | } 2552 | }); 2553 | setStyle(this.list, { 2554 | width: 'auto' 2555 | }); 2556 | this.initList(); 2557 | 2558 | if (this.isShown) { 2559 | if (this.length) { 2560 | if (this.viewed) { 2561 | var index = indexes.indexOf(this.index); 2562 | 2563 | if (index >= 0) { 2564 | this.viewed = false; 2565 | this.view(Math.max(this.index - (index + 1), 0)); 2566 | } else { 2567 | addClass(this.items[this.index], CLASS_ACTIVE); 2568 | } 2569 | } 2570 | } else { 2571 | this.image = null; 2572 | this.viewed = false; 2573 | this.index = 0; 2574 | this.imageData = {}; 2575 | this.canvas.innerHTML = ''; 2576 | this.title.innerHTML = ''; 2577 | } 2578 | } 2579 | } else { 2580 | this.build(); 2581 | } 2582 | 2583 | return this; 2584 | }, 2585 | // Destroy the viewer 2586 | destroy: function destroy() { 2587 | var element = this.element, 2588 | options = this.options; 2589 | 2590 | if (!element[NAMESPACE]) { 2591 | return this; 2592 | } 2593 | 2594 | this.destroyed = true; 2595 | 2596 | if (this.ready) { 2597 | if (this.played) { 2598 | this.stop(); 2599 | } 2600 | 2601 | if (options.inline) { 2602 | if (this.fulled) { 2603 | this.exit(); 2604 | } 2605 | 2606 | this.unbind(); 2607 | } else if (this.isShown) { 2608 | if (this.viewing) { 2609 | if (this.imageRendering) { 2610 | this.imageRendering.abort(); 2611 | } else if (this.imageInitializing) { 2612 | this.imageInitializing.abort(); 2613 | } 2614 | } 2615 | 2616 | if (this.hiding) { 2617 | this.transitioning.abort(); 2618 | } 2619 | 2620 | this.hidden(); 2621 | } else if (this.showing) { 2622 | this.transitioning.abort(); 2623 | this.hidden(); 2624 | } 2625 | 2626 | this.ready = false; 2627 | this.viewer.parentNode.removeChild(this.viewer); 2628 | } else if (options.inline) { 2629 | if (this.delaying) { 2630 | this.delaying.abort(); 2631 | } else if (this.initializing) { 2632 | this.initializing.abort(); 2633 | } 2634 | } 2635 | 2636 | if (!options.inline) { 2637 | removeListener(element, EVENT_CLICK, this.onStart); 2638 | } 2639 | 2640 | element[NAMESPACE] = undefined; 2641 | return this; 2642 | } 2643 | }; 2644 | 2645 | var others = { 2646 | open: function open() { 2647 | var body = this.body; 2648 | addClass(body, CLASS_OPEN); 2649 | body.style.paddingRight = "".concat(this.scrollbarWidth + (parseFloat(this.initialBodyPaddingRight) || 0), "px"); 2650 | }, 2651 | close: function close() { 2652 | var body = this.body; 2653 | removeClass(body, CLASS_OPEN); 2654 | body.style.paddingRight = this.initialBodyPaddingRight; 2655 | }, 2656 | shown: function shown() { 2657 | var element = this.element, 2658 | options = this.options; 2659 | this.fulled = true; 2660 | this.isShown = true; 2661 | this.render(); 2662 | this.bind(); 2663 | this.showing = false; 2664 | 2665 | if (isFunction(options.shown)) { 2666 | addListener(element, EVENT_SHOWN, options.shown, { 2667 | once: true 2668 | }); 2669 | } 2670 | 2671 | if (dispatchEvent(element, EVENT_SHOWN) === false) { 2672 | return; 2673 | } 2674 | 2675 | if (this.ready && this.isShown && !this.hiding) { 2676 | this.view(this.index); 2677 | } 2678 | }, 2679 | hidden: function hidden() { 2680 | var element = this.element, 2681 | options = this.options; 2682 | this.fulled = false; 2683 | this.viewed = false; 2684 | this.isShown = false; 2685 | this.close(); 2686 | this.unbind(); 2687 | addClass(this.viewer, CLASS_HIDE); 2688 | this.resetList(); 2689 | this.resetImage(); 2690 | this.hiding = false; 2691 | 2692 | if (!this.destroyed) { 2693 | if (isFunction(options.hidden)) { 2694 | addListener(element, EVENT_HIDDEN, options.hidden, { 2695 | once: true 2696 | }); 2697 | } 2698 | 2699 | dispatchEvent(element, EVENT_HIDDEN); 2700 | } 2701 | }, 2702 | requestFullscreen: function requestFullscreen() { 2703 | var document = this.element.ownerDocument; 2704 | 2705 | if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { 2706 | var documentElement = document.documentElement; // Element.requestFullscreen() 2707 | 2708 | if (documentElement.requestFullscreen) { 2709 | documentElement.requestFullscreen(); 2710 | } else if (documentElement.webkitRequestFullscreen) { 2711 | documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); 2712 | } else if (documentElement.mozRequestFullScreen) { 2713 | documentElement.mozRequestFullScreen(); 2714 | } else if (documentElement.msRequestFullscreen) { 2715 | documentElement.msRequestFullscreen(); 2716 | } 2717 | } 2718 | }, 2719 | exitFullscreen: function exitFullscreen() { 2720 | var document = this.element.ownerDocument; 2721 | 2722 | if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) { 2723 | // Document.exitFullscreen() 2724 | if (document.exitFullscreen) { 2725 | document.exitFullscreen(); 2726 | } else if (document.webkitExitFullscreen) { 2727 | document.webkitExitFullscreen(); 2728 | } else if (document.mozCancelFullScreen) { 2729 | document.mozCancelFullScreen(); 2730 | } else if (document.msExitFullscreen) { 2731 | document.msExitFullscreen(); 2732 | } 2733 | } 2734 | }, 2735 | change: function change(event) { 2736 | var options = this.options, 2737 | pointers = this.pointers; 2738 | var pointer = pointers[Object.keys(pointers)[0]]; 2739 | var offsetX = pointer.endX - pointer.startX; 2740 | var offsetY = pointer.endY - pointer.startY; 2741 | 2742 | switch (this.action) { 2743 | // Move the current image 2744 | case ACTION_MOVE: 2745 | this.move(offsetX, offsetY); 2746 | break; 2747 | // Zoom the current image 2748 | 2749 | case ACTION_ZOOM: 2750 | this.zoom(getMaxZoomRatio(pointers), false, event); 2751 | break; 2752 | 2753 | case ACTION_SWITCH: 2754 | { 2755 | this.action = 'switched'; 2756 | var absoluteOffsetX = Math.abs(offsetX); 2757 | 2758 | if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) { 2759 | // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers. 2760 | this.pointers = {}; 2761 | 2762 | if (offsetX > 1) { 2763 | this.prev(options.loop); 2764 | } else if (offsetX < -1) { 2765 | this.next(options.loop); 2766 | } 2767 | } 2768 | 2769 | break; 2770 | } 2771 | } // Override 2772 | 2773 | 2774 | forEach(pointers, function (p) { 2775 | p.startX = p.endX; 2776 | p.startY = p.endY; 2777 | }); 2778 | }, 2779 | isSwitchable: function isSwitchable() { 2780 | var imageData = this.imageData, 2781 | viewerData = this.viewerData; 2782 | return this.length > 1 && imageData.left >= 0 && imageData.top >= 0 && imageData.width <= viewerData.width && imageData.height <= viewerData.height; 2783 | } 2784 | }; 2785 | 2786 | var AnotherViewer = WINDOW.Viewer; 2787 | 2788 | var Viewer = 2789 | /*#__PURE__*/ 2790 | function () { 2791 | /** 2792 | * Create a new Viewer. 2793 | * @param {Element} element - The target element for viewing. 2794 | * @param {Object} [options={}] - The configuration options. 2795 | */ 2796 | function Viewer(element) { 2797 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 2798 | 2799 | _classCallCheck(this, Viewer); 2800 | 2801 | if (!element || element.nodeType !== 1) { 2802 | throw new Error('The first argument is required and must be an element.'); 2803 | } 2804 | 2805 | this.element = element; 2806 | this.options = assign({}, DEFAULTS, isPlainObject(options) && options); 2807 | this.action = false; 2808 | this.fading = false; 2809 | this.fulled = false; 2810 | this.hiding = false; 2811 | this.imageClicked = false; 2812 | this.imageData = {}; 2813 | this.index = this.options.initialViewIndex; 2814 | this.isImg = false; 2815 | this.isShown = false; 2816 | this.length = 0; 2817 | this.played = false; 2818 | this.playing = false; 2819 | this.pointers = {}; 2820 | this.ready = false; 2821 | this.showing = false; 2822 | this.timeout = false; 2823 | this.tooltipping = false; 2824 | this.viewed = false; 2825 | this.viewing = false; 2826 | this.wheeling = false; 2827 | this.zooming = false; 2828 | this.init(); 2829 | } 2830 | 2831 | _createClass(Viewer, [{ 2832 | key: "init", 2833 | value: function init() { 2834 | var _this = this; 2835 | 2836 | var element = this.element, 2837 | options = this.options; 2838 | 2839 | if (element[NAMESPACE]) { 2840 | return; 2841 | } 2842 | 2843 | element[NAMESPACE] = this; 2844 | var isImg = element.tagName.toLowerCase() === 'img'; 2845 | var images = []; 2846 | forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) { 2847 | if (isFunction(options.filter)) { 2848 | if (options.filter.call(_this, image)) { 2849 | images.push(image); 2850 | } 2851 | } else { 2852 | images.push(image); 2853 | } 2854 | }); 2855 | this.isImg = isImg; 2856 | this.length = images.length; 2857 | this.images = images; 2858 | var ownerDocument = element.ownerDocument; 2859 | var body = ownerDocument.body || ownerDocument.documentElement; 2860 | this.body = body; 2861 | this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth; 2862 | this.initialBodyPaddingRight = window.getComputedStyle(body).paddingRight; // Override `transition` option if it is not supported 2863 | 2864 | if (isUndefined(document.createElement(NAMESPACE).style.transition)) { 2865 | options.transition = false; 2866 | } 2867 | 2868 | if (options.inline) { 2869 | var count = 0; 2870 | 2871 | var progress = function progress() { 2872 | count += 1; 2873 | 2874 | if (count === _this.length) { 2875 | var timeout; 2876 | _this.initializing = false; 2877 | _this.delaying = { 2878 | abort: function abort() { 2879 | clearTimeout(timeout); 2880 | } 2881 | }; // build asynchronously to keep `this.viewer` is accessible in `ready` event handler. 2882 | 2883 | timeout = setTimeout(function () { 2884 | _this.delaying = false; 2885 | 2886 | _this.build(); 2887 | }, 0); 2888 | } 2889 | }; 2890 | 2891 | this.initializing = { 2892 | abort: function abort() { 2893 | forEach(images, function (image) { 2894 | if (!image.complete) { 2895 | removeListener(image, EVENT_LOAD, progress); 2896 | } 2897 | }); 2898 | } 2899 | }; 2900 | forEach(images, function (image) { 2901 | if (image.complete) { 2902 | progress(); 2903 | } else { 2904 | addListener(image, EVENT_LOAD, progress, { 2905 | once: true 2906 | }); 2907 | } 2908 | }); 2909 | } else { 2910 | addListener(element, EVENT_CLICK, this.onStart = function (_ref) { 2911 | var target = _ref.target; 2912 | 2913 | if (target.tagName.toLowerCase() === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) { 2914 | _this.view(_this.images.indexOf(target)); 2915 | } 2916 | }); 2917 | } 2918 | } 2919 | }, { 2920 | key: "build", 2921 | value: function build() { 2922 | if (this.ready) { 2923 | return; 2924 | } 2925 | 2926 | var element = this.element, 2927 | options = this.options; 2928 | var parent = element.parentNode; 2929 | var template = document.createElement('div'); 2930 | template.innerHTML = TEMPLATE; 2931 | var viewer = template.querySelector(".".concat(NAMESPACE, "-container")); 2932 | var title = viewer.querySelector(".".concat(NAMESPACE, "-title")); 2933 | var toolbar = viewer.querySelector(".".concat(NAMESPACE, "-toolbar")); 2934 | var navbar = viewer.querySelector(".".concat(NAMESPACE, "-navbar")); 2935 | var button = viewer.querySelector(".".concat(NAMESPACE, "-button")); 2936 | var canvas = viewer.querySelector(".".concat(NAMESPACE, "-canvas")); 2937 | this.parent = parent; 2938 | this.viewer = viewer; 2939 | this.title = title; 2940 | this.toolbar = toolbar; 2941 | this.navbar = navbar; 2942 | this.button = button; 2943 | this.canvas = canvas; 2944 | this.footer = viewer.querySelector(".".concat(NAMESPACE, "-footer")); 2945 | this.tooltipBox = viewer.querySelector(".".concat(NAMESPACE, "-tooltip")); 2946 | this.player = viewer.querySelector(".".concat(NAMESPACE, "-player")); 2947 | this.list = viewer.querySelector(".".concat(NAMESPACE, "-list")); 2948 | addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title)); 2949 | addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar)); 2950 | toggleClass(button, CLASS_HIDE, !options.button); 2951 | 2952 | if (options.backdrop) { 2953 | addClass(viewer, "".concat(NAMESPACE, "-backdrop")); 2954 | 2955 | if (!options.inline && options.backdrop !== 'static') { 2956 | setData(canvas, DATA_ACTION, 'hide'); 2957 | } 2958 | } 2959 | 2960 | if (isString(options.className) && options.className) { 2961 | // In case there are multiple class names 2962 | options.className.split(REGEXP_SPACES).forEach(function (className) { 2963 | addClass(viewer, className); 2964 | }); 2965 | } 2966 | 2967 | if (options.toolbar) { 2968 | var list = document.createElement('ul'); 2969 | var custom = isPlainObject(options.toolbar); 2970 | var zoomButtons = BUTTONS.slice(0, 3); 2971 | var rotateButtons = BUTTONS.slice(7, 9); 2972 | var scaleButtons = BUTTONS.slice(9); 2973 | 2974 | if (!custom) { 2975 | addClass(toolbar, getResponsiveClass(options.toolbar)); 2976 | } 2977 | 2978 | forEach(custom ? options.toolbar : BUTTONS, function (value, index) { 2979 | var deep = custom && isPlainObject(value); 2980 | var name = custom ? hyphenate(index) : value; 2981 | var show = deep && !isUndefined(value.show) ? value.show : value; 2982 | 2983 | if (!show || !options.zoomable && zoomButtons.indexOf(name) !== -1 || !options.rotatable && rotateButtons.indexOf(name) !== -1 || !options.scalable && scaleButtons.indexOf(name) !== -1) { 2984 | return; 2985 | } 2986 | 2987 | var size = deep && !isUndefined(value.size) ? value.size : value; 2988 | var click = deep && !isUndefined(value.click) ? value.click : value; 2989 | var item = document.createElement('li'); 2990 | item.setAttribute('role', 'button'); 2991 | addClass(item, "".concat(NAMESPACE, "-").concat(name)); 2992 | 2993 | if (!isFunction(click)) { 2994 | setData(item, DATA_ACTION, name); 2995 | } 2996 | 2997 | if (isNumber(show)) { 2998 | addClass(item, getResponsiveClass(show)); 2999 | } 3000 | 3001 | if (['small', 'large'].indexOf(size) !== -1) { 3002 | addClass(item, "".concat(NAMESPACE, "-").concat(size)); 3003 | } else if (name === 'play') { 3004 | addClass(item, "".concat(NAMESPACE, "-large")); 3005 | } 3006 | 3007 | if (isFunction(click)) { 3008 | addListener(item, EVENT_CLICK, click); 3009 | } 3010 | 3011 | list.appendChild(item); 3012 | }); 3013 | toolbar.appendChild(list); 3014 | } else { 3015 | addClass(toolbar, CLASS_HIDE); 3016 | } 3017 | 3018 | if (!options.rotatable) { 3019 | var rotates = toolbar.querySelectorAll('li[class*="rotate"]'); 3020 | addClass(rotates, CLASS_INVISIBLE); 3021 | forEach(rotates, function (rotate) { 3022 | toolbar.appendChild(rotate); 3023 | }); 3024 | } 3025 | 3026 | if (options.inline) { 3027 | addClass(button, CLASS_FULLSCREEN); 3028 | setStyle(viewer, { 3029 | zIndex: options.zIndexInline 3030 | }); 3031 | 3032 | if (window.getComputedStyle(parent).position === 'static') { 3033 | setStyle(parent, { 3034 | position: 'relative' 3035 | }); 3036 | } 3037 | 3038 | parent.insertBefore(viewer, element.nextSibling); 3039 | } else { 3040 | addClass(button, CLASS_CLOSE); 3041 | addClass(viewer, CLASS_FIXED); 3042 | addClass(viewer, CLASS_FADE); 3043 | addClass(viewer, CLASS_HIDE); 3044 | setStyle(viewer, { 3045 | zIndex: options.zIndex 3046 | }); 3047 | var container = options.container; 3048 | 3049 | if (isString(container)) { 3050 | container = element.ownerDocument.querySelector(container); 3051 | } 3052 | 3053 | if (!container) { 3054 | container = this.body; 3055 | } 3056 | 3057 | container.appendChild(viewer); 3058 | } 3059 | 3060 | if (options.inline) { 3061 | this.render(); 3062 | this.bind(); 3063 | this.isShown = true; 3064 | } 3065 | 3066 | this.ready = true; 3067 | 3068 | if (isFunction(options.ready)) { 3069 | addListener(element, EVENT_READY, options.ready, { 3070 | once: true 3071 | }); 3072 | } 3073 | 3074 | if (dispatchEvent(element, EVENT_READY) === false) { 3075 | this.ready = false; 3076 | return; 3077 | } 3078 | 3079 | if (this.ready && options.inline) { 3080 | this.view(this.index); 3081 | } 3082 | } 3083 | /** 3084 | * Get the no conflict viewer class. 3085 | * @returns {Viewer} The viewer class. 3086 | */ 3087 | 3088 | }], [{ 3089 | key: "noConflict", 3090 | value: function noConflict() { 3091 | window.Viewer = AnotherViewer; 3092 | return Viewer; 3093 | } 3094 | /** 3095 | * Change the default options. 3096 | * @param {Object} options - The new default options. 3097 | */ 3098 | 3099 | }, { 3100 | key: "setDefaults", 3101 | value: function setDefaults(options) { 3102 | assign(DEFAULTS, isPlainObject(options) && options); 3103 | } 3104 | }]); 3105 | 3106 | return Viewer; 3107 | }(); 3108 | 3109 | assign(Viewer.prototype, render, events, handlers, methods, others); 3110 | 3111 | if ($.fn) { 3112 | var AnotherViewer$1 = $.fn.viewer; 3113 | var NAMESPACE$1 = 'viewer'; 3114 | 3115 | $.fn.viewer = function jQueryViewer(option) { 3116 | for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 3117 | args[_key - 1] = arguments[_key]; 3118 | } 3119 | 3120 | var result; 3121 | this.each(function (i, element) { 3122 | var $element = $(element); 3123 | var isDestroy = option === 'destroy'; 3124 | var viewer = $element.data(NAMESPACE$1); 3125 | 3126 | if (!viewer) { 3127 | if (isDestroy) { 3128 | return; 3129 | } 3130 | 3131 | var options = $.extend({}, $element.data(), $.isPlainObject(option) && option); 3132 | viewer = new Viewer(element, options); 3133 | $element.data(NAMESPACE$1, viewer); 3134 | } 3135 | 3136 | if (typeof option === 'string') { 3137 | var fn = viewer[option]; 3138 | 3139 | if ($.isFunction(fn)) { 3140 | result = fn.apply(viewer, args); 3141 | 3142 | if (result === viewer) { 3143 | result = undefined; 3144 | } 3145 | 3146 | if (isDestroy) { 3147 | $element.removeData(NAMESPACE$1); 3148 | } 3149 | } 3150 | } 3151 | }); 3152 | return result !== undefined ? result : this; 3153 | }; 3154 | 3155 | $.fn.viewer.Constructor = Viewer; 3156 | $.fn.viewer.setDefaults = Viewer.setDefaults; 3157 | 3158 | $.fn.viewer.noConflict = function noConflict() { 3159 | $.fn.viewer = AnotherViewer$1; 3160 | return this; 3161 | }; 3162 | } 3163 | --------------------------------------------------------------------------------