├── .editorconfig ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE.txt ├── README.md ├── api.md ├── bower.json ├── dist ├── angular-bootstrap-lightbox.css ├── angular-bootstrap-lightbox.js ├── angular-bootstrap-lightbox.min.css └── angular-bootstrap-lightbox.min.js ├── index.js ├── package.json └── src ├── angular-bootstrap-lightbox.css ├── image-loader-service.js ├── lightbox-service.js ├── lightbox-src-directive.js ├── lightbox.html └── module.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.tmp/ 2 | /_site/ 3 | /bower_components/ 4 | /node_modules/ 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "globals": { 9 | "angular": true, 10 | "document": true, 11 | "Image": true 12 | }, 13 | "immed": true, 14 | "indent": 2, 15 | "latedef": true, 16 | "newcap": true, 17 | "noarg": true, 18 | "quotmark": "single", 19 | "regexp": true, 20 | "undef": true, 21 | "unused": true, 22 | "strict": false, 23 | "trailing": true, 24 | "smarttabs": true, 25 | "white": true 26 | } 27 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | // Load grunt tasks when they are needed 4 | require('jit-grunt')(grunt, { 5 | ngtemplates: 'grunt-angular-templates', 6 | jsdoc2md: 'grunt-jsdoc-to-markdown' 7 | }); 8 | 9 | // Time how long tasks take. Can help when optimizing build times 10 | require('time-grunt')(grunt); 11 | 12 | grunt.initConfig({ 13 | pkg: grunt.file.readJSON('package.json'), 14 | library: grunt.file.readJSON('bower.json'), 15 | concat: { 16 | options: { 17 | separator: '' 18 | }, 19 | library: { 20 | src: [ 21 | 'src/module.js', 22 | '.tmp/templates.js', 23 | 'src/image-loader-service.js', 24 | 'src/lightbox-service.js', 25 | 'src/lightbox-src-directive.js' 26 | ], 27 | dest: 'dist/<%= library.name %>.js' 28 | }, 29 | css: { 30 | src: [ 31 | 'src/<%= library.name %>.css', 32 | ], 33 | dest: 'dist/<%= library.name %>.css' 34 | } 35 | }, 36 | jshint: { 37 | beforeConcat: { 38 | src: ['Gruntfile.js', 'src/**/*.js'] 39 | }, 40 | options: { 41 | jshintrc: '.jshintrc' 42 | } 43 | }, 44 | ngAnnotate: { 45 | dist: { 46 | files: [{ 47 | src: '<%= concat.library.dest %>', 48 | dest: 'dist/<%= library.name %>.min.js' 49 | }] 50 | } 51 | }, 52 | uglify: { 53 | options: { 54 | banner: '/*! <%= pkg.name %> */\n' 55 | }, 56 | jid: { 57 | files: { 58 | 'dist/<%= library.name %>.min.js': ['dist/<%= library.name %>.min.js'] 59 | } 60 | } 61 | }, 62 | cssmin: { 63 | minify: { 64 | expand: true, 65 | cwd: 'src', 66 | src: ['*.css'], 67 | dest: 'dist', 68 | ext: '.min.css' 69 | } 70 | }, 71 | ngtemplates: { 72 | 'bootstrapLightbox': { 73 | cwd: 'src', 74 | src: '*.html', 75 | dest: '.tmp/templates.js', 76 | options: { 77 | htmlmin: { 78 | collapseBooleanAttributes: true, 79 | collapseWhitespace: true, 80 | removeAttributeQuotes: true, 81 | removeComments: true, 82 | removeEmptyAttributes: false, 83 | removeRedundantAttributes: true 84 | } 85 | } 86 | } 87 | }, 88 | jsdoc2md: { 89 | doc: { 90 | src: 'src/*.js', 91 | dest: 'api.md' 92 | } 93 | }, 94 | }); 95 | 96 | grunt.registerTask('default', [ 97 | 'ngtemplates', 98 | 'jshint:beforeConcat', 99 | 'concat', 100 | 'ngAnnotate', 101 | 'uglify', 102 | 'cssmin', 103 | ]); 104 | }; 105 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-bootstrap-lightbox 2 | 3 | This lightbox displays images using an [AngularUI Bootstrap Modal](http://angular-ui.github.io/bootstrap/#/modal) (v0.14). 4 | 5 | When the lightbox is opened, navigating to the previous/next image can be achieved by clicking buttons above the image, clicking the left/right arrow keys, or swiping to the left/right (optional with ngTouch). The escape key for closing the modal is automatically binded by AngularUI Bootstrap. 6 | 7 | Large images are scaled to fit inside the window. An optional image caption overlays the top left corner of the image. 8 | 9 | ## Demos 10 | 11 | [Demos](http://compact.github.io/angular-bootstrap-lightbox/) / [Plunker](http://plnkr.co/edit/pEXXDYLl0NtkqupEXaJK?p=preview) 12 | 13 | ## Setup 14 | 15 | 1. Install in one of the following ways: 16 | 17 | * Install with Bower: `bower install angular-bootstrap-lightbox --save` 18 | * Install with npm: `npm install angular-bootstrap-lightbox --save` 19 | * Manually save the script and stylesheet from [`dist`](dist). 20 | 21 | 2. Include the stylesheet in your app: 22 | 23 | ```html 24 | 25 | ``` 26 | 27 | 3. Include the script in your app: 28 | 29 | ```html 30 | 31 | ``` 32 | 33 | 4. The Angular module is named `bootstrapLightbox`. Add it as a dependency to your module: 34 | 35 | ```js 36 | angular.module('app', ['bootstrapLightbox']); 37 | ``` 38 | 39 | 5. Optional dependencies: 40 | 41 | * To enable swipe navigation in the lightbox, include the [ngTouch](https://docs.angularjs.org/api/ngTouch) script. 42 | * To show a loading bar while an image is loading, include the [angular-loading-bar](https://github.com/chieffancypants/angular-loading-bar) script. 43 | * For video support, include the [ng-videosharing-embed](https://github.com/erost/ng-videosharing-embed) script. 44 | 45 | ## Basic example 46 | 47 | Gallery: 48 | 49 | ```html 50 | 57 | ``` 58 | 59 | Controller: 60 | 61 | ```js 62 | angular.module('app').controller('GalleryCtrl', function ($scope, Lightbox) { 63 | $scope.images = [ 64 | { 65 | 'url': '1.jpg', 66 | 'caption': 'Optional caption', 67 | 'thumbUrl': 'thumb1.jpg' // used only for this example 68 | }, 69 | { 70 | 'url': '2.gif', 71 | 'thumbUrl': 'thumb2.jpg' 72 | }, 73 | { 74 | 'url': '3.png', 75 | 'thumbUrl': 'thumb3.png' 76 | } 77 | ]; 78 | 79 | $scope.openLightboxModal = function (index) { 80 | Lightbox.openModal($scope.images, index); 81 | }; 82 | }); 83 | ``` 84 | 85 | ## Configuration 86 | 87 | ### Changing the appearance of the modal lightbox 88 | 89 | The default view template for the lightbox is [lightbox.html](src/lightbox.html). Its look can be changed by making your own custom template and/or adding CSS rules; for example, use the selector `.lightbox-image-caption` to style the caption. 90 | 91 | If you make your own template and save it at `lightbox.html`, no further code is necessary. If you save it at a different path, set it in the provider: 92 | 93 | ```js 94 | angular.module('app').config(function (LightboxProvider) { 95 | // set a custom template 96 | LightboxProvider.templateUrl = 'path/to/your-template.html'; 97 | }); 98 | ``` 99 | 100 | ### Disabling the keyboard navigation 101 | 102 | The keyboard navigation in the lightbox with the left/right arrow keys can be enabled/disabled at any time by changing the value of the boolean `Lightbox.keyboardNavEnabled`. 103 | 104 | ### Array of images 105 | 106 | The first argument to `Lightbox.openModal` must be an array, and its elements may be of any type. In the basic example above, it is an array of objects with properties `url` and `caption`, but this is only the default and is not required. To let the `Lightbox` service know the correct values, set these methods in the provider: 107 | 108 | ```js 109 | angular.module('app').config(function (LightboxProvider) { 110 | LightboxProvider.getImageUrl = function (image) { 111 | return '/base/dir/' + image.getName(); 112 | }; 113 | 114 | LightboxProvider.getImageCaption = function (image) { 115 | return image.label; 116 | }; 117 | }); 118 | ``` 119 | 120 | ### Image and modal scaling 121 | 122 | By default, images are scaled only if they are too large for the modal to contain without scrolling. 123 | 124 | If you want all images to be scaled to the maximum possible dimensions, update the `Lightbox.fullScreenMode` boolean: 125 | 126 | ```js 127 | angular.module('app').config(function (LightboxProvider) { 128 | LightboxProvider.fullScreenMode = true; 129 | }); 130 | ``` 131 | 132 | For more custom behaviour, see the [documentation](src/lightbox-service.js) of the methods `calculateImageDimensionLimits` and `calculateModalDimensions`. 133 | 134 | ### Videos 135 | 136 | An element in the array of 'images' is considered a video if it is an object with a `type` property having the value `video` (see the [demo](http://compact.github.io/angular-bootstrap-lightbox/demo5/index.html)). To change this, write your own `LightboxProvider.isVideo` method. 137 | 138 | By default, a video is embedded directly if its url ends in `.mp4`, `.ogg`, or `.webm`. Every other url is considered a video from an external sharing service (such as YouTube). To change this check, edit the `LightboxProvider.isSharedVideo` method. The `ng-videosharing-embed` library is used for embedding shared videos if it is included in your app. You can use another video library by changing the template. 139 | 140 | For now, the maximum video dimensions are fixed at 1280x720 (16:9). 141 | 142 | ## Development 143 | 144 | * [API documentation](api.md) of the services and directive in the Angular module 145 | 146 | * Setup: 147 | 148 | ```sh 149 | npm install 150 | bower install 151 | ``` 152 | 153 | * Build: 154 | 155 | ```sh 156 | grunt 157 | ``` 158 | 159 | * Generate docs: 160 | 161 | ```sh 162 | grunt jsdoc2md 163 | ``` 164 | 165 | * Serve the GitHub Pages: 166 | 167 | ```sh 168 | git checkout gh-pages 169 | git checkout master -- dist/angular-bootstrap-lightbox.min.js dist/angular-bootstrap-lightbox.min.css 170 | bundle install 171 | bundle exec jekyll serve 172 | ``` 173 | -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- 1 | 2 | ## bootstrapLightbox : object 3 | **Kind**: global namespace 4 | 5 | * [bootstrapLightbox](#bootstrapLightbox) : object 6 | * [.ImageLoader](#bootstrapLightbox.ImageLoader) 7 | * [.load](#bootstrapLightbox.ImageLoader.load) ⇒ Promise 8 | * [.Lightbox](#bootstrapLightbox.Lightbox) 9 | * [.templateUrl](#bootstrapLightbox.Lightbox.templateUrl) : String 10 | * [.fullScreenMode](#bootstrapLightbox.Lightbox.fullScreenMode) : Boolean 11 | * [.getImageUrl](#bootstrapLightbox.Lightbox.getImageUrl) ⇒ String 12 | * [.getImageCaption](#bootstrapLightbox.Lightbox.getImageCaption) ⇒ String 13 | * [.calculateImageDimensionLimits](#bootstrapLightbox.Lightbox.calculateImageDimensionLimits) ⇒ Object 14 | * [.calculateModalDimensions](#bootstrapLightbox.Lightbox.calculateModalDimensions) ⇒ Object 15 | * [.isVideo](#bootstrapLightbox.Lightbox.isVideo) ⇒ Boolean 16 | * [.isSharedVideo](#bootstrapLightbox.Lightbox.isSharedVideo) ⇒ Boolean 17 | * [.images](#bootstrapLightbox.Lightbox.images) : Array 18 | * [.index](#bootstrapLightbox.Lightbox.index) : Number 19 | * [.keyboardNavEnabled](#bootstrapLightbox.Lightbox.keyboardNavEnabled) : Boolean 20 | * [.image](#bootstrapLightbox.Lightbox.image) : \* 21 | * [.modalInstance](#bootstrapLightbox.Lightbox.modalInstance) : Object 22 | * [.imageUrl](#bootstrapLightbox.Lightbox.imageUrl) : String 23 | * [.imageCaption](#bootstrapLightbox.Lightbox.imageCaption) : String 24 | * [.loading](#bootstrapLightbox.Lightbox.loading) : Boolean 25 | * [.openModal](#bootstrapLightbox.Lightbox.openModal) ⇒ Object 26 | * [.closeModal](#bootstrapLightbox.Lightbox.closeModal) : function 27 | * [.setImage](#bootstrapLightbox.Lightbox.setImage) : function 28 | * [.firstImage](#bootstrapLightbox.Lightbox.firstImage) : function 29 | * [.prevImage](#bootstrapLightbox.Lightbox.prevImage) : function 30 | * [.nextImage](#bootstrapLightbox.Lightbox.nextImage) : function 31 | * [.lastImage](#bootstrapLightbox.Lightbox.lastImage) : function 32 | * [.setImages](#bootstrapLightbox.Lightbox.setImages) : function 33 | * [.lightboxSrc](#bootstrapLightbox.lightboxSrc) 34 | 35 | 36 | ### bootstrapLightbox.ImageLoader 37 | Service for loading an image. 38 | 39 | **Kind**: static class of [bootstrapLightbox](#bootstrapLightbox) 40 | 41 | #### ImageLoader.load ⇒ Promise 42 | Load the image at the given URL. 43 | 44 | **Kind**: static property of [ImageLoader](#bootstrapLightbox.ImageLoader) 45 | **Returns**: Promise - A $q promise that resolves when the image has loaded 46 | successfully. 47 | 48 | | Param | Type | 49 | | --- | --- | 50 | | url | String | 51 | 52 | 53 | ### bootstrapLightbox.Lightbox 54 | Lightbox service. 55 | 56 | **Kind**: static class of [bootstrapLightbox](#bootstrapLightbox) 57 | 58 | * [.Lightbox](#bootstrapLightbox.Lightbox) 59 | * [.templateUrl](#bootstrapLightbox.Lightbox.templateUrl) : String 60 | * [.fullScreenMode](#bootstrapLightbox.Lightbox.fullScreenMode) : Boolean 61 | * [.getImageUrl](#bootstrapLightbox.Lightbox.getImageUrl) ⇒ String 62 | * [.getImageCaption](#bootstrapLightbox.Lightbox.getImageCaption) ⇒ String 63 | * [.calculateImageDimensionLimits](#bootstrapLightbox.Lightbox.calculateImageDimensionLimits) ⇒ Object 64 | * [.calculateModalDimensions](#bootstrapLightbox.Lightbox.calculateModalDimensions) ⇒ Object 65 | * [.isVideo](#bootstrapLightbox.Lightbox.isVideo) ⇒ Boolean 66 | * [.isSharedVideo](#bootstrapLightbox.Lightbox.isSharedVideo) ⇒ Boolean 67 | * [.images](#bootstrapLightbox.Lightbox.images) : Array 68 | * [.index](#bootstrapLightbox.Lightbox.index) : Number 69 | * [.keyboardNavEnabled](#bootstrapLightbox.Lightbox.keyboardNavEnabled) : Boolean 70 | * [.image](#bootstrapLightbox.Lightbox.image) : \* 71 | * [.modalInstance](#bootstrapLightbox.Lightbox.modalInstance) : Object 72 | * [.imageUrl](#bootstrapLightbox.Lightbox.imageUrl) : String 73 | * [.imageCaption](#bootstrapLightbox.Lightbox.imageCaption) : String 74 | * [.loading](#bootstrapLightbox.Lightbox.loading) : Boolean 75 | * [.openModal](#bootstrapLightbox.Lightbox.openModal) ⇒ Object 76 | * [.closeModal](#bootstrapLightbox.Lightbox.closeModal) : function 77 | * [.setImage](#bootstrapLightbox.Lightbox.setImage) : function 78 | * [.firstImage](#bootstrapLightbox.Lightbox.firstImage) : function 79 | * [.prevImage](#bootstrapLightbox.Lightbox.prevImage) : function 80 | * [.nextImage](#bootstrapLightbox.Lightbox.nextImage) : function 81 | * [.lastImage](#bootstrapLightbox.Lightbox.lastImage) : function 82 | * [.setImages](#bootstrapLightbox.Lightbox.setImages) : function 83 | 84 | 85 | #### Lightbox.templateUrl : String 86 | Template URL passed into `$uibModal.open()`. 87 | 88 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 89 | 90 | #### Lightbox.fullScreenMode : Boolean 91 | Whether images should be scaled to the maximum possible dimensions. 92 | 93 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 94 | 95 | #### Lightbox.getImageUrl ⇒ String 96 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 97 | **Returns**: String - The URL of the given image. 98 | 99 | | Param | Type | Description | 100 | | --- | --- | --- | 101 | | image | \* | An element in the array of images. | 102 | 103 | 104 | #### Lightbox.getImageCaption ⇒ String 105 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 106 | **Returns**: String - The caption of the given image. 107 | 108 | | Param | Type | Description | 109 | | --- | --- | --- | 110 | | image | \* | An element in the array of images. | 111 | 112 | 113 | #### Lightbox.calculateImageDimensionLimits ⇒ Object 114 | Calculate the max and min limits to the width and height of the displayed 115 | image (all are optional). The max dimensions override the min 116 | dimensions if they conflict. 117 | 118 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 119 | **Returns**: Object - May optionally contain the properties `minWidth`, 120 | `minHeight`, `maxWidth`, and `maxHeight`. 121 | 122 | | Param | Type | Description | 123 | | --- | --- | --- | 124 | | dimensions | Object | Contains the properties `windowWidth`, `windowHeight`, `imageWidth`, and `imageHeight`. | 125 | 126 | 127 | #### Lightbox.calculateModalDimensions ⇒ Object 128 | Calculate the width and height of the modal. This method gets called 129 | after the width and height of the image, as displayed inside the modal, 130 | are calculated. 131 | 132 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 133 | **Returns**: Object - Must contain the properties `width` and `height`. 134 | 135 | | Param | Type | Description | 136 | | --- | --- | --- | 137 | | dimensions | Object | Contains the properties `windowWidth`, `windowHeight`, `imageDisplayWidth`, and `imageDisplayHeight`. | 138 | 139 | 140 | #### Lightbox.isVideo ⇒ Boolean 141 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 142 | **Returns**: Boolean - Whether the provided element is a video. 143 | 144 | | Param | Type | Description | 145 | | --- | --- | --- | 146 | | image | \* | An element in the array of images. | 147 | 148 | 149 | #### Lightbox.isSharedVideo ⇒ Boolean 150 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 151 | **Returns**: Boolean - Whether the provided element is a video that is to be 152 | embedded with an external service like YouTube. By default, this is 153 | determined by the url not ending in `.mp4`, `.ogg`, or `.webm`. 154 | 155 | | Param | Type | Description | 156 | | --- | --- | --- | 157 | | image | \* | An element in the array of images. | 158 | 159 | 160 | #### Lightbox.images : Array 161 | Array of all images to be shown in the lightbox (not `Image` objects). 162 | 163 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 164 | 165 | #### Lightbox.index : Number 166 | The index in the `Lightbox.images` aray of the image that is currently 167 | shown in the lightbox. 168 | 169 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 170 | 171 | #### Lightbox.keyboardNavEnabled : Boolean 172 | Whether keyboard navigation is currently enabled for navigating through 173 | images in the lightbox. 174 | 175 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 176 | 177 | #### Lightbox.image : \* 178 | The image currently shown in the lightbox. 179 | 180 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 181 | 182 | #### Lightbox.modalInstance : Object 183 | The UI Bootstrap modal instance. See {@link 184 | http://angular-ui.github.io/bootstrap/#/modal}. 185 | 186 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 187 | 188 | #### Lightbox.imageUrl : String 189 | The URL of the current image. This is a property of the service rather 190 | than of `Lightbox.image` because `Lightbox.image` need not be an 191 | object, and besides it would be poor practice to alter the given 192 | objects. 193 | 194 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 195 | 196 | #### Lightbox.imageCaption : String 197 | The optional caption of the current image. 198 | 199 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 200 | 201 | #### Lightbox.loading : Boolean 202 | Whether an image is currently being loaded. 203 | 204 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 205 | 206 | #### Lightbox.openModal ⇒ Object 207 | Open the lightbox modal. 208 | 209 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 210 | **Returns**: Object - The created UI Bootstrap modal instance. 211 | 212 | | Param | Type | Description | 213 | | --- | --- | --- | 214 | | newImages | Array | An array of images. Each image may be of any type. | 215 | | newIndex | Number | The index in `newImages` to set as the current image. | 216 | | modalParams | Object | Custom params for the angular UI bootstrap modal (in $uibModal.open()). | 217 | 218 | 219 | #### Lightbox.closeModal : function 220 | Close the lightbox modal. 221 | 222 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 223 | 224 | | Param | Type | Description | 225 | | --- | --- | --- | 226 | | result | \* | This argument can be useful if the modal promise gets handler(s) attached to it. | 227 | 228 | 229 | #### Lightbox.setImage : function 230 | This method can be used in all methods which navigate/change the 231 | current image. 232 | 233 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 234 | 235 | | Param | Type | Description | 236 | | --- | --- | --- | 237 | | newIndex | Number | The index in the array of images to set as the new current image. | 238 | 239 | 240 | #### Lightbox.firstImage : function 241 | Navigate to the first image. 242 | 243 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 244 | 245 | #### Lightbox.prevImage : function 246 | Navigate to the previous image. 247 | 248 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 249 | 250 | #### Lightbox.nextImage : function 251 | Navigate to the next image. 252 | 253 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 254 | 255 | #### Lightbox.lastImage : function 256 | Navigate to the last image. 257 | 258 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 259 | 260 | #### Lightbox.setImages : function 261 | Call this method to set both the array of images and the current image 262 | (based on the current index). A use case is when the image collection 263 | gets changed dynamically in some way while the lightbox is still 264 | open. 265 | 266 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox) 267 | 268 | | Param | Type | Description | 269 | | --- | --- | --- | 270 | | newImages | Array | The new array of images. | 271 | 272 | 273 | ### bootstrapLightbox.lightboxSrc 274 | This attribute directive is used in an `` element in the 275 | modal template in place of `src`. It handles resizing both the `` 276 | element and its relevant parent elements within the modal. 277 | 278 | **Kind**: static class of [bootstrapLightbox](#bootstrapLightbox) 279 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bootstrap-lightbox", 3 | "version": "0.12.0", 4 | "main": [ 5 | "dist/angular-bootstrap-lightbox.js", 6 | "dist/angular-bootstrap-lightbox.css" 7 | ], 8 | "ignore": [ 9 | "src", 10 | ".*", 11 | "Gruntfile.js", 12 | "package.json" 13 | ], 14 | "dependencies": { 15 | "angular": "^1.4.10", 16 | "angular-bootstrap": "^1.3.1" 17 | }, 18 | "devDependencies": { 19 | "bootstrap":"^3.3.6", 20 | "angular-loading-bar": "^0.8.0", 21 | "angular-touch": "^1.4.10", 22 | "ng-videosharing-embed": "^0.3.4" 23 | }, 24 | "resolutions": { 25 | "angular": "^1.4.10" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dist/angular-bootstrap-lightbox.css: -------------------------------------------------------------------------------- 1 | .lightbox-nav { 2 | position: relative; 3 | margin-bottom: 12px; /* the font-size of .btn-xs */ 4 | height: 22px; 5 | text-align: center; 6 | font-size: 0; /* prevent the otherwise inherited font-size and line-height from adding extra space to the bottom of this div */ 7 | } 8 | 9 | .lightbox-nav .btn-group { 10 | vertical-align: top; 11 | } 12 | 13 | .lightbox-nav .close { 14 | /* absolutely position this in order to center the nav buttons */ 15 | position: absolute; 16 | top: 0; 17 | right: 0; 18 | } 19 | 20 | .lightbox-image-container { 21 | position: relative; 22 | text-align: center; /* center the image */ 23 | } 24 | 25 | /* the caption overlays the top left corner of the image */ 26 | .lightbox-image-caption { 27 | position: absolute; 28 | top: 0; 29 | left: 0; 30 | margin: 0.5em 0.9em; /* the left and right margins are offset by 0.4em for the span box-shadow */ 31 | color: #000; 32 | font-size: 1.5em; 33 | font-weight: bold; 34 | text-align: left; 35 | text-shadow: 0.1em 0.1em 0.2em rgba(255, 255, 255, 0.5); 36 | } 37 | 38 | .lightbox-image-caption span { 39 | padding-top: 0.1em; 40 | padding-bottom: 0.1em; 41 | background-color: rgba(255, 255, 255, 0.75); 42 | /* pad the left and right of each line of text */ 43 | box-shadow: 0.4em 0 0 rgba(255, 255, 255, 0.75), 44 | -0.4em 0 0 rgba(255, 255, 255, 0.75); 45 | } 46 | -------------------------------------------------------------------------------- /dist/angular-bootstrap-lightbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace bootstrapLightbox 3 | */ 4 | angular.module('bootstrapLightbox', [ 5 | 'ui.bootstrap' 6 | ]); 7 | 8 | // optional dependencies 9 | try { 10 | angular.module('angular-loading-bar'); 11 | angular.module('bootstrapLightbox').requires.push('angular-loading-bar'); 12 | } catch (e) {} 13 | 14 | try { 15 | angular.module('ngTouch'); 16 | angular.module('bootstrapLightbox').requires.push('ngTouch'); 17 | } catch (e) {} 18 | 19 | try { 20 | angular.module('videosharing-embed'); 21 | angular.module('bootstrapLightbox').requires.push('videosharing-embed'); 22 | } catch (e) {} 23 | angular.module('bootstrapLightbox').run(['$templateCache', function($templateCache) { 24 | 'use strict'; 25 | 26 | $templateCache.put('lightbox.html', 27 | "" 28 | ); 29 | 30 | }]); 31 | /** 32 | * @class ImageLoader 33 | * @classdesc Service for loading an image. 34 | * @memberOf bootstrapLightbox 35 | */ 36 | angular.module('bootstrapLightbox').service('ImageLoader', ['$q', 37 | function ($q) { 38 | /** 39 | * Load the image at the given URL. 40 | * @param {String} url 41 | * @return {Promise} A $q promise that resolves when the image has loaded 42 | * successfully. 43 | * @type {Function} 44 | * @name load 45 | * @memberOf bootstrapLightbox.ImageLoader 46 | */ 47 | this.load = function (url) { 48 | var deferred = $q.defer(); 49 | 50 | var image = new Image(); 51 | 52 | // when the image has loaded 53 | image.onload = function () { 54 | // check image properties for possible errors 55 | if ((typeof this.complete === 'boolean' && this.complete === false) || 56 | (typeof this.naturalWidth === 'number' && this.naturalWidth === 0)) { 57 | deferred.reject(); 58 | } 59 | 60 | deferred.resolve(image); 61 | }; 62 | 63 | // when the image fails to load 64 | image.onerror = function () { 65 | deferred.reject(); 66 | }; 67 | 68 | // start loading the image 69 | image.src = url; 70 | 71 | return deferred.promise; 72 | }; 73 | }]); 74 | /** 75 | * @class Lightbox 76 | * @classdesc Lightbox service. 77 | * @memberOf bootstrapLightbox 78 | */ 79 | angular.module('bootstrapLightbox').provider('Lightbox', function () { 80 | /** 81 | * Template URL passed into `$uibModal.open()`. 82 | * @type {String} 83 | * @name templateUrl 84 | * @memberOf bootstrapLightbox.Lightbox 85 | */ 86 | this.templateUrl = 'lightbox.html'; 87 | 88 | /** 89 | * Whether images should be scaled to the maximum possible dimensions. 90 | * @type {Boolean} 91 | * @name fullScreenMode 92 | * @memberOf bootstrapLightbox.Lightbox 93 | */ 94 | this.fullScreenMode = false; 95 | 96 | /** 97 | * @param {*} image An element in the array of images. 98 | * @return {String} The URL of the given image. 99 | * @type {Function} 100 | * @name getImageUrl 101 | * @memberOf bootstrapLightbox.Lightbox 102 | */ 103 | this.getImageUrl = function (image) { 104 | return typeof image === 'string' ? image : image.url; 105 | }; 106 | 107 | /** 108 | * @param {*} image An element in the array of images. 109 | * @return {String} The caption of the given image. 110 | * @type {Function} 111 | * @name getImageCaption 112 | * @memberOf bootstrapLightbox.Lightbox 113 | */ 114 | this.getImageCaption = function (image) { 115 | return image.caption; 116 | }; 117 | 118 | /** 119 | * Calculate the max and min limits to the width and height of the displayed 120 | * image (all are optional). The max dimensions override the min 121 | * dimensions if they conflict. 122 | * @param {Object} dimensions Contains the properties `windowWidth`, 123 | * `windowHeight`, `imageWidth`, and `imageHeight`. 124 | * @return {Object} May optionally contain the properties `minWidth`, 125 | * `minHeight`, `maxWidth`, and `maxHeight`. 126 | * @type {Function} 127 | * @name calculateImageDimensionLimits 128 | * @memberOf bootstrapLightbox.Lightbox 129 | */ 130 | this.calculateImageDimensionLimits = function (dimensions) { 131 | if (dimensions.windowWidth >= 768) { 132 | return { 133 | // 92px = 2 * (30px margin of .modal-dialog 134 | // + 1px border of .modal-content 135 | // + 15px padding of .modal-body) 136 | // with the goal of 30px side margins; however, the actual side margins 137 | // will be slightly less (at 22.5px) due to the vertical scrollbar 138 | 'maxWidth': dimensions.windowWidth - 92, 139 | // 126px = 92px as above 140 | // + 34px outer height of .lightbox-nav 141 | 'maxHeight': dimensions.windowHeight - 126 142 | }; 143 | } else { 144 | return { 145 | // 52px = 2 * (10px margin of .modal-dialog 146 | // + 1px border of .modal-content 147 | // + 15px padding of .modal-body) 148 | 'maxWidth': dimensions.windowWidth - 52, 149 | // 86px = 52px as above 150 | // + 34px outer height of .lightbox-nav 151 | 'maxHeight': dimensions.windowHeight - 86 152 | }; 153 | } 154 | }; 155 | 156 | /** 157 | * Calculate the width and height of the modal. This method gets called 158 | * after the width and height of the image, as displayed inside the modal, 159 | * are calculated. 160 | * @param {Object} dimensions Contains the properties `windowWidth`, 161 | * `windowHeight`, `imageDisplayWidth`, and `imageDisplayHeight`. 162 | * @return {Object} Must contain the properties `width` and `height`. 163 | * @type {Function} 164 | * @name calculateModalDimensions 165 | * @memberOf bootstrapLightbox.Lightbox 166 | */ 167 | this.calculateModalDimensions = function (dimensions) { 168 | // 400px = arbitrary min width 169 | // 32px = 2 * (1px border of .modal-content 170 | // + 15px padding of .modal-body) 171 | var width = Math.max(400, dimensions.imageDisplayWidth + 32); 172 | 173 | // 200px = arbitrary min height 174 | // 66px = 32px as above 175 | // + 34px outer height of .lightbox-nav 176 | var height = Math.max(200, dimensions.imageDisplayHeight + 66); 177 | 178 | // first case: the modal width cannot be larger than the window width 179 | // 20px = arbitrary value larger than the vertical scrollbar 180 | // width in order to avoid having a horizontal scrollbar 181 | // second case: Bootstrap modals are not centered below 768px 182 | if (width >= dimensions.windowWidth - 20 || dimensions.windowWidth < 768) { 183 | width = 'auto'; 184 | } 185 | 186 | // the modal height cannot be larger than the window height 187 | if (height >= dimensions.windowHeight) { 188 | height = 'auto'; 189 | } 190 | 191 | return { 192 | 'width': width, 193 | 'height': height 194 | }; 195 | }; 196 | 197 | /** 198 | * @param {*} image An element in the array of images. 199 | * @return {Boolean} Whether the provided element is a video. 200 | * @type {Function} 201 | * @name isVideo 202 | * @memberOf bootstrapLightbox.Lightbox 203 | */ 204 | this.isVideo = function (image) { 205 | if (typeof image === 'object' && image && image.type) { 206 | return image.type === 'video'; 207 | } 208 | 209 | return false; 210 | }; 211 | 212 | /** 213 | * @param {*} image An element in the array of images. 214 | * @return {Boolean} Whether the provided element is a video that is to be 215 | * embedded with an external service like YouTube. By default, this is 216 | * determined by the url not ending in `.mp4`, `.ogg`, or `.webm`. 217 | * @type {Function} 218 | * @name isSharedVideo 219 | * @memberOf bootstrapLightbox.Lightbox 220 | */ 221 | this.isSharedVideo = function (image) { 222 | return this.isVideo(image) && 223 | !this.getImageUrl(image).match(/\.(mp4|ogg|webm)$/); 224 | }; 225 | 226 | this.$get = ['$document', '$injector', '$uibModal', '$timeout', 'ImageLoader', 227 | function ($document, $injector, $uibModal, $timeout, ImageLoader) { 228 | // optional dependency 229 | var cfpLoadingBar = $injector.has('cfpLoadingBar') ? 230 | $injector.get('cfpLoadingBar'): null; 231 | 232 | var Lightbox = {}; 233 | 234 | /** 235 | * Array of all images to be shown in the lightbox (not `Image` objects). 236 | * @type {Array} 237 | * @name images 238 | * @memberOf bootstrapLightbox.Lightbox 239 | */ 240 | Lightbox.images = []; 241 | 242 | /** 243 | * The index in the `Lightbox.images` aray of the image that is currently 244 | * shown in the lightbox. 245 | * @type {Number} 246 | * @name index 247 | * @memberOf bootstrapLightbox.Lightbox 248 | */ 249 | Lightbox.index = -1; 250 | 251 | // set the configurable properties and methods, the defaults of which are 252 | // defined above 253 | Lightbox.templateUrl = this.templateUrl; 254 | Lightbox.fullScreenMode = this.fullScreenMode; 255 | Lightbox.getImageUrl = this.getImageUrl; 256 | Lightbox.getImageCaption = this.getImageCaption; 257 | Lightbox.calculateImageDimensionLimits = this.calculateImageDimensionLimits; 258 | Lightbox.calculateModalDimensions = this.calculateModalDimensions; 259 | Lightbox.isVideo = this.isVideo; 260 | Lightbox.isSharedVideo = this.isSharedVideo; 261 | 262 | /** 263 | * Whether keyboard navigation is currently enabled for navigating through 264 | * images in the lightbox. 265 | * @type {Boolean} 266 | * @name keyboardNavEnabled 267 | * @memberOf bootstrapLightbox.Lightbox 268 | */ 269 | Lightbox.keyboardNavEnabled = false; 270 | 271 | /** 272 | * The image currently shown in the lightbox. 273 | * @type {*} 274 | * @name image 275 | * @memberOf bootstrapLightbox.Lightbox 276 | */ 277 | Lightbox.image = {}; 278 | 279 | /** 280 | * The UI Bootstrap modal instance. See {@link 281 | * http://angular-ui.github.io/bootstrap/#/modal}. 282 | * @type {Object} 283 | * @name modalInstance 284 | * @memberOf bootstrapLightbox.Lightbox 285 | */ 286 | Lightbox.modalInstance = null; 287 | 288 | /** 289 | * The URL of the current image. This is a property of the service rather 290 | * than of `Lightbox.image` because `Lightbox.image` need not be an 291 | * object, and besides it would be poor practice to alter the given 292 | * objects. 293 | * @type {String} 294 | * @name imageUrl 295 | * @memberOf bootstrapLightbox.Lightbox 296 | */ 297 | 298 | /** 299 | * The optional caption of the current image. 300 | * @type {String} 301 | * @name imageCaption 302 | * @memberOf bootstrapLightbox.Lightbox 303 | */ 304 | 305 | /** 306 | * Whether an image is currently being loaded. 307 | * @type {Boolean} 308 | * @name loading 309 | * @memberOf bootstrapLightbox.Lightbox 310 | */ 311 | Lightbox.loading = false; 312 | 313 | /** 314 | * Open the lightbox modal. 315 | * @param {Array} newImages An array of images. Each image may be of 316 | * any type. 317 | * @param {Number} newIndex The index in `newImages` to set as the 318 | * current image. 319 | * @param {Object} modalParams Custom params for the angular UI 320 | * bootstrap modal (in $uibModal.open()). 321 | * @return {Object} The created UI Bootstrap modal instance. 322 | * @type {Function} 323 | * @name openModal 324 | * @memberOf bootstrapLightbox.Lightbox 325 | */ 326 | Lightbox.openModal = function (newImages, newIndex, modalParams) { 327 | Lightbox.images = newImages; 328 | Lightbox.setImage(newIndex); 329 | 330 | // store the modal instance so we can close it manually if we need to 331 | Lightbox.modalInstance = $uibModal.open(angular.extend({ 332 | 'templateUrl': Lightbox.templateUrl, 333 | 'controller': ['$scope', function ($scope) { 334 | // $scope is the modal scope, a child of $rootScope 335 | $scope.Lightbox = Lightbox; 336 | 337 | Lightbox.keyboardNavEnabled = true; 338 | }], 339 | 'windowClass': 'lightbox-modal' 340 | }, modalParams || {})); 341 | 342 | // modal close handler 343 | Lightbox.modalInstance.result['finally'](function () { 344 | // prevent the lightbox from flickering from the old image when it gets 345 | // opened again 346 | Lightbox.images = []; 347 | Lightbox.index = 1; 348 | Lightbox.image = {}; 349 | Lightbox.imageUrl = null; 350 | Lightbox.imageCaption = null; 351 | 352 | Lightbox.keyboardNavEnabled = false; 353 | 354 | // complete any lingering loading bar progress 355 | if (cfpLoadingBar) { 356 | cfpLoadingBar.complete(); 357 | } 358 | }); 359 | 360 | return Lightbox.modalInstance; 361 | }; 362 | 363 | /** 364 | * Close the lightbox modal. 365 | * @param {*} result This argument can be useful if the modal promise 366 | * gets handler(s) attached to it. 367 | * @type {Function} 368 | * @name closeModal 369 | * @memberOf bootstrapLightbox.Lightbox 370 | */ 371 | Lightbox.closeModal = function (result) { 372 | return Lightbox.modalInstance.close(result); 373 | }; 374 | 375 | /** 376 | * This method can be used in all methods which navigate/change the 377 | * current image. 378 | * @param {Number} newIndex The index in the array of images to set as 379 | * the new current image. 380 | * @type {Function} 381 | * @name setImage 382 | * @memberOf bootstrapLightbox.Lightbox 383 | */ 384 | Lightbox.setImage = function (newIndex) { 385 | if (!(newIndex in Lightbox.images)) { 386 | throw 'Invalid image.'; 387 | } 388 | 389 | // update the loading flag and start the loading bar 390 | Lightbox.loading = true; 391 | if (cfpLoadingBar) { 392 | cfpLoadingBar.start(); 393 | } 394 | 395 | var image = Lightbox.images[newIndex]; 396 | var imageUrl = Lightbox.getImageUrl(image); 397 | 398 | var success = function (properties) { 399 | // update service properties for the image 400 | properties = properties || {}; 401 | Lightbox.index = properties.index || newIndex; 402 | Lightbox.image = properties.image || image; 403 | Lightbox.imageUrl = properties.imageUrl || imageUrl; 404 | Lightbox.imageCaption = properties.imageCaption || 405 | Lightbox.getImageCaption(image); 406 | 407 | // restore the loading flag and complete the loading bar 408 | Lightbox.loading = false; 409 | if (cfpLoadingBar) { 410 | cfpLoadingBar.complete(); 411 | } 412 | }; 413 | 414 | if (!Lightbox.isVideo(image)) { 415 | // load the image before setting it, so everything in the view is 416 | // updated at the same time; otherwise, the previous image remains while 417 | // the current image is loading 418 | ImageLoader.load(imageUrl).then(function () { 419 | success(); 420 | }, function () { 421 | success({ 422 | 'imageUrl': '#', // blank image 423 | // use the caption to show the user an error 424 | 'imageCaption': 'Failed to load image' 425 | }); 426 | }); 427 | } else { 428 | success(); 429 | } 430 | }; 431 | 432 | /** 433 | * Navigate to the first image. 434 | * @type {Function} 435 | * @name firstImage 436 | * @memberOf bootstrapLightbox.Lightbox 437 | */ 438 | Lightbox.firstImage = function () { 439 | Lightbox.setImage(0); 440 | }; 441 | 442 | /** 443 | * Navigate to the previous image. 444 | * @type {Function} 445 | * @name prevImage 446 | * @memberOf bootstrapLightbox.Lightbox 447 | */ 448 | Lightbox.prevImage = function () { 449 | Lightbox.setImage((Lightbox.index - 1 + Lightbox.images.length) % 450 | Lightbox.images.length); 451 | }; 452 | 453 | /** 454 | * Navigate to the next image. 455 | * @type {Function} 456 | * @name nextImage 457 | * @memberOf bootstrapLightbox.Lightbox 458 | */ 459 | Lightbox.nextImage = function () { 460 | Lightbox.setImage((Lightbox.index + 1) % Lightbox.images.length); 461 | }; 462 | 463 | /** 464 | * Navigate to the last image. 465 | * @type {Function} 466 | * @name lastImage 467 | * @memberOf bootstrapLightbox.Lightbox 468 | */ 469 | Lightbox.lastImage = function () { 470 | Lightbox.setImage(Lightbox.images.length - 1); 471 | }; 472 | 473 | /** 474 | * Call this method to set both the array of images and the current image 475 | * (based on the current index). A use case is when the image collection 476 | * gets changed dynamically in some way while the lightbox is still 477 | * open. 478 | * @param {Array} newImages The new array of images. 479 | * @type {Function} 480 | * @name setImages 481 | * @memberOf bootstrapLightbox.Lightbox 482 | */ 483 | Lightbox.setImages = function (newImages) { 484 | Lightbox.images = newImages; 485 | Lightbox.setImage(Lightbox.index); 486 | }; 487 | 488 | // Bind the left and right arrow keys for image navigation. This event 489 | // handler never gets unbinded. Disable this using the `keyboardNavEnabled` 490 | // flag. It is automatically disabled when the target is an input and or a 491 | // textarea. TODO: Move this to a directive. 492 | $document.bind('keydown', function (event) { 493 | if (!Lightbox.keyboardNavEnabled) { 494 | return; 495 | } 496 | 497 | // method of Lightbox to call 498 | var method = null; 499 | 500 | switch (event.which) { 501 | case 39: // right arrow key 502 | method = 'nextImage'; 503 | break; 504 | case 37: // left arrow key 505 | method = 'prevImage'; 506 | break; 507 | } 508 | 509 | if (method !== null && ['input', 'textarea'].indexOf( 510 | event.target.tagName.toLowerCase()) === -1) { 511 | // the view doesn't update without a manual digest 512 | $timeout(function () { 513 | Lightbox[method](); 514 | }); 515 | 516 | event.preventDefault(); 517 | } 518 | }); 519 | 520 | return Lightbox; 521 | }]; 522 | }); 523 | /** 524 | * @class lightboxSrc 525 | * @classdesc This attribute directive is used in an `` element in the 526 | * modal template in place of `src`. It handles resizing both the `` 527 | * element and its relevant parent elements within the modal. 528 | * @memberOf bootstrapLightbox 529 | */ 530 | angular.module('bootstrapLightbox').directive('lightboxSrc', ['$window', 531 | 'ImageLoader', 'Lightbox', function ($window, ImageLoader, Lightbox) { 532 | // Calculate the dimensions to display the image. The max dimensions override 533 | // the min dimensions if they conflict. 534 | var calculateImageDisplayDimensions = function (dimensions, fullScreenMode) { 535 | var w = dimensions.width; 536 | var h = dimensions.height; 537 | var minW = dimensions.minWidth; 538 | var minH = dimensions.minHeight; 539 | var maxW = dimensions.maxWidth; 540 | var maxH = dimensions.maxHeight; 541 | 542 | var displayW = w; 543 | var displayH = h; 544 | 545 | if (!fullScreenMode) { 546 | // resize the image if it is too small 547 | if (w < minW && h < minH) { 548 | // the image is both too thin and short, so compare the aspect ratios to 549 | // determine whether to min the width or height 550 | if (w / h > maxW / maxH) { 551 | displayH = minH; 552 | displayW = Math.round(w * minH / h); 553 | } else { 554 | displayW = minW; 555 | displayH = Math.round(h * minW / w); 556 | } 557 | } else if (w < minW) { 558 | // the image is too thin 559 | displayW = minW; 560 | displayH = Math.round(h * minW / w); 561 | } else if (h < minH) { 562 | // the image is too short 563 | displayH = minH; 564 | displayW = Math.round(w * minH / h); 565 | } 566 | 567 | // resize the image if it is too large 568 | if (w > maxW && h > maxH) { 569 | // the image is both too tall and wide, so compare the aspect ratios 570 | // to determine whether to max the width or height 571 | if (w / h > maxW / maxH) { 572 | displayW = maxW; 573 | displayH = Math.round(h * maxW / w); 574 | } else { 575 | displayH = maxH; 576 | displayW = Math.round(w * maxH / h); 577 | } 578 | } else if (w > maxW) { 579 | // the image is too wide 580 | displayW = maxW; 581 | displayH = Math.round(h * maxW / w); 582 | } else if (h > maxH) { 583 | // the image is too tall 584 | displayH = maxH; 585 | displayW = Math.round(w * maxH / h); 586 | } 587 | } else { 588 | // full screen mode 589 | var ratio = Math.min(maxW / w, maxH / h); 590 | 591 | var zoomedW = Math.round(w * ratio); 592 | var zoomedH = Math.round(h * ratio); 593 | 594 | displayW = Math.max(minW, zoomedW); 595 | displayH = Math.max(minH, zoomedH); 596 | } 597 | 598 | return { 599 | 'width': displayW || 0, 600 | 'height': displayH || 0 // NaN is possible when dimensions.width is 0 601 | }; 602 | }; 603 | 604 | // format the given dimension for passing into the `css()` method of `jqLite` 605 | var formatDimension = function (dimension) { 606 | return typeof dimension === 'number' ? dimension + 'px' : dimension; 607 | }; 608 | 609 | // the dimensions of the image 610 | var imageWidth = 0; 611 | var imageHeight = 0; 612 | 613 | return { 614 | 'link': function (scope, element, attrs) { 615 | // resize the img element and the containing modal 616 | var resize = function () { 617 | // get the window dimensions 618 | var windowWidth = $window.innerWidth; 619 | var windowHeight = $window.innerHeight; 620 | 621 | // calculate the max/min dimensions for the image 622 | var imageDimensionLimits = Lightbox.calculateImageDimensionLimits({ 623 | 'windowWidth': windowWidth, 624 | 'windowHeight': windowHeight, 625 | 'imageWidth': imageWidth, 626 | 'imageHeight': imageHeight 627 | }); 628 | 629 | // calculate the dimensions to display the image 630 | var imageDisplayDimensions = calculateImageDisplayDimensions( 631 | angular.extend({ 632 | 'width': imageWidth, 633 | 'height': imageHeight, 634 | 'minWidth': 1, 635 | 'minHeight': 1, 636 | 'maxWidth': 3000, 637 | 'maxHeight': 3000, 638 | }, imageDimensionLimits), 639 | Lightbox.fullScreenMode 640 | ); 641 | 642 | // calculate the dimensions of the modal container 643 | var modalDimensions = Lightbox.calculateModalDimensions({ 644 | 'windowWidth': windowWidth, 645 | 'windowHeight': windowHeight, 646 | 'imageDisplayWidth': imageDisplayDimensions.width, 647 | 'imageDisplayHeight': imageDisplayDimensions.height 648 | }); 649 | 650 | // resize the image 651 | element.css({ 652 | 'width': imageDisplayDimensions.width + 'px', 653 | 'height': imageDisplayDimensions.height + 'px' 654 | }); 655 | 656 | // setting the height on .modal-dialog does not expand the div with the 657 | // background, which is .modal-content 658 | angular.element( 659 | document.querySelector('.lightbox-modal .modal-dialog') 660 | ).css({ 661 | 'width': formatDimension(modalDimensions.width) 662 | }); 663 | 664 | // .modal-content has no width specified; if we set the width on 665 | // .modal-content and not on .modal-dialog, .modal-dialog retains its 666 | // default width of 600px and that places .modal-content off center 667 | angular.element( 668 | document.querySelector('.lightbox-modal .modal-content') 669 | ).css({ 670 | 'height': formatDimension(modalDimensions.height) 671 | }); 672 | }; 673 | 674 | // load the new image and/or resize the video whenever the attr changes 675 | scope.$watch(function () { 676 | return attrs.lightboxSrc; 677 | }, function (src) { 678 | // do nothing if there's no image 679 | if (!Lightbox.image) { 680 | return; 681 | } 682 | 683 | if (!Lightbox.isVideo(Lightbox.image)) { // image 684 | // blank the image before resizing the element 685 | element[0].src = '#'; 686 | 687 | // handle failure to load the image 688 | var failure = function () { 689 | imageWidth = 0; 690 | imageHeight = 0; 691 | 692 | resize(); 693 | }; 694 | 695 | if (src) { 696 | ImageLoader.load(src).then(function (image) { 697 | // these variables must be set before resize(), as they are used 698 | // in it 699 | imageWidth = image.naturalWidth; 700 | imageHeight = image.naturalHeight; 701 | 702 | // resize the img element and the containing modal 703 | resize(); 704 | 705 | // show the image 706 | element[0].src = src; 707 | }, failure); 708 | } else { 709 | failure(); 710 | } 711 | } else { // video 712 | // default dimensions 713 | imageWidth = 1280; 714 | imageHeight = 720; 715 | 716 | // resize the video element and the containing modal 717 | resize(); 718 | 719 | // the src attribute applies to `