├── .gitignore ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── bower.json ├── boxer.jquery.json ├── demo ├── iframe.html └── index.html ├── jquery.fs.boxer.css ├── jquery.fs.boxer.js ├── jquery.fs.boxer.min.css ├── jquery.fs.boxer.min.js ├── package.json └── src ├── jquery.fs.boxer-config.less ├── jquery.fs.boxer-styles.less ├── jquery.fs.boxer.js └── jquery.fs.boxer.less /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | 3 | // Less 4 | 5 | module.exports = function(grunt) { 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | meta: { 10 | banner: '/* \n' + 11 | ' * <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> \n' + 12 | ' * <%= pkg.description %> \n' + 13 | ' * <%= pkg.homepage %> \n' + 14 | ' * \n' + 15 | ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>; <%= pkg.license %> Licensed \n' + 16 | ' */\n' 17 | }, 18 | // JS Hint 19 | jshint: { 20 | options: { 21 | globals: { 22 | 'jQuery': true, 23 | '$' : true 24 | }, 25 | browser: true, 26 | curly: true, 27 | eqeqeq: true, 28 | forin: true, 29 | freeze: true, 30 | immed: true, 31 | latedef: true, 32 | newcap: true, 33 | noarg: true, 34 | nonew: true, 35 | smarttabs: true, 36 | sub: true, 37 | undef: true, 38 | validthis: true 39 | }, 40 | files: [ 41 | 'src/<%= pkg.codename %>.js' 42 | ] 43 | }, 44 | // Copy 45 | copy: { 46 | main: { 47 | files: [ 48 | { 49 | src: 'src/<%= pkg.codename %>.js', 50 | dest: '<%= pkg.codename %>.js' 51 | } 52 | ] 53 | } 54 | }, 55 | // Uglify 56 | uglify: { 57 | options: { 58 | report: 'min' 59 | }, 60 | target: { 61 | files: { 62 | '<%= pkg.codename %>.min.js': [ '<%= pkg.codename %>.js' ] 63 | } 64 | } 65 | }, 66 | // jQuery Manifest 67 | jquerymanifest: { 68 | options: { 69 | source: grunt.file.readJSON('package.json'), 70 | overrides: { 71 | name: '<%= pkg.id %>', 72 | keywords: '<%= pkg.keywords %>', 73 | homepage: '<%= pkg.homepage %>', 74 | docs: '<%= pkg.homepage %>', 75 | demo: '<%= pkg.homepage %>', 76 | download: '<%= pkg.repository.url %>', 77 | bugs: '<%= pkg.repository.url %>/issues', 78 | dependencies: { 79 | jquery: '>=1.7' 80 | } 81 | } 82 | } 83 | }, 84 | // LESS 85 | less: { 86 | main: { 87 | files: { 88 | '<%= pkg.codename %>.css': [ 89 | 'src/<%= pkg.codename %>-config.less', 90 | 'src/<%= pkg.codename %>.less' 91 | ] 92 | } 93 | }, 94 | min: { 95 | options: { 96 | report: 'min', 97 | cleancss: true 98 | }, 99 | files: { 100 | '<%= pkg.codename %>.min.css': [ 101 | 'src/<%= pkg.codename %>-config.less', 102 | 'src/<%= pkg.codename %>.less' 103 | ] 104 | } 105 | } 106 | }, 107 | // Auto Prefixer 108 | autoprefixer: { 109 | options: { 110 | browsers: [ '> 1%', 'last 5 versions', 'Firefox ESR', 'Opera 12.1', 'IE 8', 'IE 9' ] 111 | }, 112 | no_dest: { 113 | src: '*.css' 114 | } 115 | }, 116 | // Banner 117 | usebanner: { 118 | options: { 119 | position: 'top', 120 | banner: '<%= meta.banner %>' 121 | }, 122 | files: { 123 | src: [ 124 | '<%= pkg.codename %>.css', 125 | '<%= pkg.codename %>.js', 126 | '<%= pkg.codename %>.min.css', 127 | '<%= pkg.codename %>.min.js' 128 | ] 129 | } 130 | }, 131 | // Bower sync 132 | sync: { 133 | all: { 134 | options: { 135 | sync: [ 'name', 'version', 'description', 'author', 'license', 'homepage' ], 136 | overrides: { 137 | main: [ 138 | '<%= pkg.codename %>.js', 139 | '<%= pkg.codename %>.css' 140 | ], 141 | ignore: [ "*.jquery.json" ] 142 | } 143 | } 144 | } 145 | }, 146 | // Watcher - For dev only!! 147 | watch: { 148 | scripts: { 149 | files: [ 150 | 'src/**.js' 151 | ], 152 | tasks: [ 153 | 'jshint', 154 | 'copy' 155 | ] 156 | }, 157 | styles: { 158 | files: [ 159 | 'src/**.less' 160 | ], 161 | tasks: [ 162 | 'less', 163 | 'autoprefixer' 164 | ] 165 | } 166 | } 167 | }); 168 | 169 | // Load tasks 170 | grunt.loadNpmTasks('grunt-contrib-watch'); 171 | grunt.loadNpmTasks('grunt-contrib-jshint'); 172 | grunt.loadNpmTasks('grunt-contrib-copy'); 173 | grunt.loadNpmTasks('grunt-contrib-uglify'); 174 | grunt.loadNpmTasks('grunt-jquerymanifest'); 175 | grunt.loadNpmTasks('grunt-contrib-less'); 176 | grunt.loadNpmTasks('grunt-autoprefixer'); 177 | grunt.loadNpmTasks('grunt-banner'); 178 | grunt.loadNpmTasks('grunt-npm2bower-sync'); 179 | 180 | // Readme 181 | grunt.registerTask('buildReadme', 'Build Formstone README.md file.', function () { 182 | var pkg = grunt.file.readJSON('package.json'), 183 | extra = grunt.file.exists('src/README.md') ? '\n\n---\n\n' + grunt.file.read('src/README.md') : ''; 184 | destination = "README.md", 185 | markdown = '

Development of this plugin has ended. Please upgrade to the new Formstone.


\n\n' + 186 | 'Built with Grunt \n' + 187 | '# ' + pkg.name + ' \n\n' + 188 | pkg.description + ' \n\n' + 189 | '- [Demo](' + pkg.demo + ') \n' + 190 | '- [Documentation](' + pkg.homepage + ') \n\n' + 191 | '#### Bower Support \n' + 192 | '`bower install ' + pkg.name + '` ' + 193 | extra; 194 | 195 | grunt.file.write(destination, markdown); 196 | grunt.log.writeln('File "' + destination + '" created.'); 197 | }); 198 | 199 | grunt.registerTask('buildLicense', 'Build Formstone LICENSE.md file.', function () { 200 | var pkg = grunt.file.readJSON('package.json'), 201 | destination = "LICENSE.md", 202 | markdown = 'The MIT License (MIT) \n\n' + 203 | 'Copyright ' + grunt.template.today("yyyy") + ' ' + pkg.author.name + ' \n\n' + 204 | 'Permission is hereby granted, free of charge, to any person obtaining a copy \n' + 205 | 'of this software and associated documentation files (the "Software"), to deal \n' + 206 | 'in the Software without restriction, including without limitation the rights \n' + 207 | 'to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \n' + 208 | 'copies of the Software, and to permit persons to whom the Software is \n' + 209 | 'furnished to do so, subject to the following conditions: \n\n' + 210 | 'The above copyright notice and this permission notice shall be included in \n' + 211 | 'all copies or substantial portions of the Software. \n\n' + 212 | 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \n' + 213 | 'IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \n' + 214 | 'FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \n' + 215 | 'AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \n' + 216 | 'LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, \n' + 217 | 'OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN \n' + 218 | 'THE SOFTWARE.'; 219 | 220 | grunt.file.write(destination, markdown); 221 | grunt.log.writeln('File "' + destination + '" created.'); 222 | }); 223 | 224 | 225 | 226 | 227 | // Default task. 228 | grunt.registerTask('default', [ 'jshint', 'copy', 'uglify', 'jquerymanifest', 'less', 'autoprefixer', 'usebanner', 'sync', 'buildReadme', 'buildLicense' ]); 229 | 230 | }; -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2015 Ben Plum 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Development of this plugin has ended. Please upgrade to the new Formstone.


2 | 3 | Built with Grunt 4 | # Boxer 5 | 6 | A jQuery plugin for displaying images, videos or content in a modal overlay. Part of the Formstone Library. 7 | 8 | - [Demo](http://classic.formstone.it/components/Boxer/demo/index.html) 9 | - [Documentation](http://classic.formstone.it/boxer/) 10 | 11 | #### Bower Support 12 | `bower install Boxer` -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Boxer", 3 | "version": "3.3.0", 4 | "description": "A jQuery plugin for displaying images, videos or content in a modal overlay. Part of the Formstone Library.", 5 | "author": { 6 | "name": "Ben Plum", 7 | "email": "mr@benplum.com", 8 | "url": "http://www.benplum.com" 9 | }, 10 | "license": "MIT", 11 | "homepage": "http://classic.formstone.it/boxer/", 12 | "main": [ 13 | "jquery.fs.boxer.js", 14 | "jquery.fs.boxer.css" 15 | ], 16 | "ignore": [ 17 | "*.jquery.json" 18 | ] 19 | } -------------------------------------------------------------------------------- /boxer.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boxer", 3 | "version": "3.3.0", 4 | "title": "Boxer", 5 | "author": { 6 | "name": "Ben Plum", 7 | "email": "mr@benplum.com", 8 | "url": "http://www.benplum.com" 9 | }, 10 | "licenses": [ 11 | { 12 | "type": "MIT", 13 | "url": "http://opensource.org/licenses/MIT" 14 | } 15 | ], 16 | "dependencies": { 17 | "jquery": ">=1.7" 18 | }, 19 | "description": "A jQuery plugin for displaying images, videos or content in a modal overlay. Part of the Formstone Library.", 20 | "keywords": [ 21 | "modal", 22 | "slideshow", 23 | "ui", 24 | "javascript", 25 | "jquery", 26 | "formstone" 27 | ], 28 | "docs": "http://classic.formstone.it/boxer/", 29 | "demo": "http://classic.formstone.it/boxer/", 30 | "download": "https://github.com/FormstoneClassic/Boxer", 31 | "bugs": "https://github.com/FormstoneClassic/Boxer/issues", 32 | "homepage": "http://classic.formstone.it/boxer/" 33 | } -------------------------------------------------------------------------------- /demo/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | iFrame Content 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 |

iFrame Content

21 |

Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean lacinia bibendum nulla sed consectetur. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.

22 |

Aenean lacinia bibendum nulla sed consectetur. Maecenas faucibus mollis interdum. Aenean lacinia bibendum nulla sed consectetur. Donec id elit non mi porta gravida at eget metus. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Nullam quis risus eget urna mollis ornare vel eu leo.

23 |

Aenean lacinia bibendum nulla sed consectetur. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Sed posuere consectetur est at lobortis. Maecenas faucibus mollis interdum. Donec id elit non mi porta gravida at eget metus. Vestibulum id ligula porta felis euismod semper.

24 |

Nullam id dolor id nibh ultricies vehicula ut id elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam quis risus eget urna mollis ornare vel eu leo. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

25 | 26 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Boxer Demo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 39 | 40 | 79 | 80 | 81 | 82 | 83 | 84 | 91 |
92 |
93 |
94 |

Boxer Demo

95 |
96 | View Documentation 97 |
98 | 99 | 100 | 101 | 102 |

Basic

103 |

To display a single image simply link to the source. Also, 'title' attributes will automatically populate the caption field:

104 | 105 |
$(".boxer").boxer();
106 |
<a href="image.jpg" class="boxer" title="Caption">
107 | 	<img src="thumbnail.jpg" alt="Thumbnail" />
108 | </a>
109 | 110 |
Demo
111 |
112 | 113 | 114 | 115 |
116 | 117 |
118 | 119 | 120 |

Gallery

121 |

To display a gallery of images attach a common 'data-gallery' attribute to each item:

122 | 123 |
<a href="image_1.jpg" class="boxer" title="Caption One" data-gallery="gallery">
124 |     <img src="thumbnail_1.jpg" alt="Thumbnail One" />
125 | </a>
126 | <a href="image_2.jpg" class="boxer" title="Caption Two" data-gallery="gallery">
127 |     <img src="thumbnail_2.jpg" alt="Thumbnail Two" />
128 | </a>
129 | 130 |
Demo
131 | 142 | 143 |
144 | 145 | 146 |

YouTube & Vimeo Videos

147 |

Boxer will auto-detect a YouTube or Vimeo embed URL and render accordingly. Videos can also be included in galleries alongside image.

148 | 149 |
<a href="http://www.youtube.com/embed/VIDEO_ID" data-gallery="videos" title="">YouTube Video</a>
150 | // OR
151 | <a href="http://player.vimeo.com/video/VIDEO_ID" data-gallery="videos" title="">Vimeo Video</a>
152 | 153 |
Demo
154 | 162 | 163 |
164 | 165 | 166 |

Mobile

167 |

Boxer will automatically render a touch-friendly modal on mobile devices, but you can also force it to always render as mobile:

168 | 169 |
$(".boxer").boxer({
170 |     mobile: true
171 | });
172 | 173 |
Demo
174 | 185 | 186 |
187 | 188 | 189 |

Fixed Positioning

190 |

To display a more traditional lightbox that locks the window while open:

191 | 192 |
$(".boxer").boxer({
193 |     fixed: true
194 | });
195 | 196 |
Demo
197 | 208 | 209 |
210 | 211 | 212 |

Top Positioning

213 |

To force the modal to always animate from a standard top position:

214 | 215 |
$(".boxer").boxer({
216 |     top: 50
217 | });
218 | 219 |
Demo
220 | 231 | 232 |
233 | 234 | 235 |

In-Line Content

236 |

To display a section of inline markup link to the parent element's 'id'. It's important to note that a copy of the matching element's inner markup will be used, so try to avoid using decent elements with id selectors:

237 | 238 |
<a href="#hidden" class="boxer">Show Content</a>
239 | <div id="hidden" style="display: none;">
240 |     <div class="content">
241 |         ...
242 |     </div>
243 | </div>
244 | 245 |
Demo
246 |
247 | Show Hidden Content 248 |
249 | 259 | 260 |
261 | 262 | 263 |

jQuery Objects

264 |

You can also display compiled jQuery objects, this is especially usefull when loading partial content or using a templating system. To display a pre-compiled jQuery object, simply pass the new object when calling the plugin.

265 | 266 |
$obj = $("

Content!

"); 267 | // OR 268 | $obj = $("<div />").load("http://www.url.com/partial-markup/"); 269 | 270 | $.boxer($obj);
271 | 272 |
Demo
273 |
274 | Show jQuery Object 275 |
276 | 277 |
278 | 279 | 280 |

iFrame

281 |

To display a valid URL in an iFrame simply link to the page:

282 | 283 |
<a href="http://www.example.com/" class="boxer">Example</a>
284 | 285 |
Demo
286 |
287 | View CNN.com 288 |
289 | 290 |
291 | 292 | 293 | 294 |

Local iFrame

295 |

You can also link to local iFrames:

296 | 297 |
<a href="iframe.html" class="boxer">Example</a>
298 | 299 |
Demo
300 |
301 | View iFrame 302 |
303 | 304 |
305 | 306 | 307 | 308 |

Sizing

309 |

When loading inline content or an iFramed URL you can specify the desired dimensions using html data attributes. If no dimensions are specified, or mobile styles are being rendered, the plugin will calculate them based on the current window and content size:

310 | 311 |
<a href="http://www.example.com/" class="boxer" data-boxer-height="500" data-boxer-width="500">Example</a>
312 | 313 |
Demo
314 |
315 | View CNN.com 316 |
317 | 318 |
319 | 320 | 321 |

Caption Formating

322 |

To customize the caption markup:

323 | 324 |
$(".boxer").boxer({
325 |     formatter: formatCaptions
326 | });
327 | 
328 | function formatCaptions($target) {
329 | 	return '<h3>' + $target.attr("title") + '</h3>';
330 | }
331 | 332 |
Demo
333 |
334 | 335 | 336 | 337 |
338 | 339 |
340 | 341 | 342 |

Retina Support

343 |

To display images at "retina" quality the original image dimentions of halved, sizing the images to at least 2x pixel-density and ensuring crisp images on your fancy new display.

344 | 345 |
$(".boxer").boxer({
346 |     retina: true
347 | });
348 | 349 |
<a href="image-2x.jpg" class="boxer" title="Caption - Retina">
350 |     <img src="thumbnail-2x.jpg" alt="Thumbnail - Retina" />
351 | </a>
352 | 353 |
Demo
354 |
355 | 356 | 357 | 358 |
359 | 360 | 361 | 362 |
363 |
364 | 365 | 370 | 371 | -------------------------------------------------------------------------------- /jquery.fs.boxer.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Boxer v3.3.0 - 2015-04-04 3 | * A jQuery plugin for displaying images, videos or content in a modal overlay. Part of the Formstone Library. 4 | * http://classic.formstone.it/boxer/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | 10 | .boxer-lock { 11 | overflow: hidden !important; 12 | } 13 | #boxer-overlay { 14 | width: 100%; 15 | height: 100%; 16 | position: fixed; 17 | top: 0; 18 | right: 0; 19 | bottom: 0; 20 | left: 0; 21 | z-index: 100; 22 | background: #000000; 23 | opacity: 0; 24 | -webkit-transition: opacity 0.25s linear; 25 | transition: opacity 0.25s linear; 26 | } 27 | .boxer-open #boxer-overlay { 28 | opacity: 0.75; 29 | } 30 | #boxer { 31 | width: 200px; 32 | height: 200px; 33 | position: absolute; 34 | right: 0; 35 | left: 0; 36 | z-index: 101; 37 | background: #ffffff; 38 | border-radius: 3px; 39 | box-shadow: 0 0 25px #000000; 40 | opacity: 0; 41 | margin: 0 auto; 42 | padding: 10px; 43 | } 44 | #boxer * { 45 | -webkit-transition: none; 46 | transition: none; 47 | } 48 | #boxer, 49 | #boxer * { 50 | -webkit-user-select: none !important; 51 | -moz-user-select: none !important; 52 | -ms-user-select: none !important; 53 | user-select: none !important; 54 | } 55 | #boxer, 56 | #boxer *, 57 | #boxer *:before, 58 | #boxer *:after { 59 | box-sizing: border-box; 60 | } 61 | #boxer.fixed { 62 | position: fixed; 63 | top: 0; 64 | bottom: 0; 65 | margin: auto; 66 | } 67 | #boxer.inline { 68 | padding: 30px; 69 | } 70 | #boxer.animating { 71 | -webkit-transition: height 0.25s ease, width 0.25s ease, opacity 0.25s linear, top 0.25s ease; 72 | transition: height 0.25s ease, width 0.25s ease, opacity 0.25s linear, top 0.25s ease; 73 | } 74 | #boxer.animating .boxer-container { 75 | -webkit-transition: opacity 0.25s linear 0.25s; 76 | transition: opacity 0.25s linear 0.25s; 77 | } 78 | .boxer-open #boxer { 79 | opacity: 1; 80 | } 81 | #boxer.loading .boxer-container { 82 | opacity: 0; 83 | -webkit-transition: opacity 0.25s linear; 84 | transition: opacity 0.25s linear; 85 | } 86 | #boxer .boxer-close { 87 | width: 30px; 88 | height: 30px; 89 | position: absolute; 90 | top: -7.5px; 91 | right: -7.5px; 92 | z-index: 105; 93 | background: #ffffff; 94 | border-radius: 100%; 95 | cursor: pointer; 96 | display: block; 97 | overflow: hidden; 98 | padding: 0; 99 | text-indent: 200%; 100 | white-space: nowrap; 101 | } 102 | #boxer .boxer-close:before { 103 | position: absolute; 104 | top: 0; 105 | right: 0; 106 | bottom: 0; 107 | left: 0; 108 | color: #333333; 109 | content: "\00d7"; 110 | display: block; 111 | font-size: 22px; 112 | font-weight: 700; 113 | line-height: 30px; 114 | margin: auto; 115 | text-align: center; 116 | text-indent: 0; 117 | -webkit-transition: color 0.15s linear; 118 | transition: color 0.15s linear; 119 | } 120 | .no-opacity #boxer .boxer-close { 121 | text-indent: -999px; 122 | } 123 | #boxer .boxer-loading { 124 | width: 50px; 125 | height: 50px; 126 | position: absolute; 127 | top: 0; 128 | right: 0; 129 | bottom: 0; 130 | left: 0; 131 | z-index: 105; 132 | display: block; 133 | margin: auto; 134 | opacity: 0; 135 | -webkit-transition: opacity 0.25s linear; 136 | transition: opacity 0.25s linear; 137 | } 138 | #boxer .boxer-loading:before, 139 | #boxer .boxer-loading:after { 140 | width: 100%; 141 | height: 100%; 142 | position: absolute; 143 | top: 0; 144 | right: 0; 145 | bottom: 0; 146 | left: 0; 147 | border-radius: 110%; 148 | content: ''; 149 | display: block; 150 | } 151 | #boxer .boxer-loading:before { 152 | border: 5px solid rgba(51, 51, 51, 0.25); 153 | } 154 | #boxer .boxer-loading:after { 155 | -webkit-animation: boxer-loading-spin 0.75s linear infinite; 156 | animation: boxer-loading-spin 0.75s linear infinite; 157 | border: 5px solid transparent; 158 | border-top-color: #333333; 159 | } 160 | #boxer.loading .boxer-loading { 161 | opacity: 1; 162 | } 163 | @-webkit-keyframes boxer-loading-spin { 164 | from { 165 | -webkit-transform: rotate(0deg); 166 | transform: rotate(0deg); 167 | } 168 | to { 169 | -webkit-transform: rotate(360deg); 170 | transform: rotate(360deg); 171 | } 172 | } 173 | @keyframes boxer-loading-spin { 174 | from { 175 | -webkit-transform: rotate(0deg); 176 | transform: rotate(0deg); 177 | } 178 | to { 179 | -webkit-transform: rotate(360deg); 180 | transform: rotate(360deg); 181 | } 182 | } 183 | #boxer .boxer-container { 184 | width: 100%; 185 | height: 100%; 186 | position: relative; 187 | z-index: 103; 188 | background: #ffffff; 189 | overflow: hidden; 190 | } 191 | #boxer .boxer-content { 192 | width: 100%; 193 | background: #ffffff; 194 | opacity: 1; 195 | overflow: hidden; 196 | padding: 0; 197 | } 198 | #boxer.inline .boxer-content, 199 | #boxer.iframe .boxer-content { 200 | width: auto; 201 | } 202 | #boxer .boxer-image { 203 | float: left; 204 | } 205 | #boxer .boxer-video { 206 | width: 100%; 207 | height: 100%; 208 | } 209 | #boxer .boxer-iframe { 210 | width: 100%; 211 | height: 100%; 212 | border: none; 213 | float: left; 214 | overflow: auto; 215 | } 216 | #boxer .boxer-meta { 217 | clear: both; 218 | } 219 | #boxer .boxer-control { 220 | width: 40px; 221 | height: 40px; 222 | position: absolute; 223 | top: 0; 224 | background: #ffffff; 225 | border-radius: 100%; 226 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.25); 227 | cursor: pointer; 228 | display: block; 229 | margin-right: auto; 230 | margin-left: auto; 231 | opacity: 1; 232 | overflow: hidden; 233 | text-indent: 200%; 234 | -webkit-transition: opacity 0.15s linear; 235 | transition: opacity 0.15s linear; 236 | white-space: nowrap; 237 | } 238 | #boxer .boxer-control:before { 239 | width: 0; 240 | height: 0; 241 | position: absolute; 242 | top: 0; 243 | right: 0; 244 | bottom: 0; 245 | left: 0; 246 | content: ''; 247 | margin: auto; 248 | } 249 | #boxer .boxer-control.previous { 250 | left: 20px; 251 | } 252 | #boxer .boxer-control.previous:before { 253 | border-top: 8px solid transparent; 254 | border-bottom: 8px solid transparent; 255 | border-right: 10.4px solid #333333; 256 | margin-left: 14px; 257 | } 258 | #boxer .boxer-control.next { 259 | right: 20px; 260 | } 261 | #boxer .boxer-control.next:before { 262 | border-top: 8px solid transparent; 263 | border-bottom: 8px solid transparent; 264 | border-left: 10.4px solid #333333; 265 | margin-right: 14px; 266 | } 267 | #boxer .boxer-control.disabled { 268 | opacity: 0; 269 | } 270 | .no-opacity #boxer .boxer-control { 271 | text-indent: -999px; 272 | } 273 | .no-touch #boxer .boxer-control { 274 | opacity: 0; 275 | } 276 | .no-touch #boxer:hover .boxer-control { 277 | opacity: 1; 278 | } 279 | .no-touch #boxer:hover .boxer-control.disabled { 280 | opacity: 0; 281 | cursor: default !important; 282 | } 283 | #boxer .boxer-meta { 284 | padding: 10px 0 0 0; 285 | } 286 | #boxer .boxer-position { 287 | color: #999999; 288 | font-size: 12px; 289 | margin: 0; 290 | padding: 15px 15px 0 15px; 291 | } 292 | #boxer .boxer-caption p { 293 | color: #666666; 294 | font-size: 14px; 295 | margin: 0; 296 | padding: 15px; 297 | } 298 | #boxer .boxer-caption.gallery p { 299 | padding-top: 0; 300 | } 301 | #boxer .boxer-error { 302 | width: 250px; 303 | } 304 | #boxer .boxer-error p { 305 | color: #990000; 306 | font-size: 14px; 307 | margin: 0; 308 | padding: 25px; 309 | text-align: center; 310 | text-transform: uppercase; 311 | } 312 | #boxer.mobile { 313 | width: 100%; 314 | height: 100%; 315 | position: fixed; 316 | top: 0; 317 | right: 0; 318 | bottom: 0; 319 | left: 0; 320 | background: #111111; 321 | border-radius: 0; 322 | padding: 40px 0 0; 323 | } 324 | #boxer.mobile .boxer-close, 325 | #boxer.mobile .boxer-close:hover { 326 | height: 40px; 327 | width: 40px; 328 | top: 0; 329 | right: 0; 330 | background: #111111; 331 | border-radius: 0; 332 | } 333 | #boxer.mobile .boxer-close:before, 334 | #boxer.mobile .boxer-close:hover:before { 335 | color: #cccccc; 336 | font-size: 28px; 337 | font-weight: 700; 338 | line-height: 40px; 339 | } 340 | #boxer.mobile .boxer-loading:before { 341 | border-color: rgba(153, 153, 153, 0.25); 342 | } 343 | #boxer.mobile .boxer-loading:after { 344 | border-top-color: #999999; 345 | } 346 | #boxer.mobile .boxer-container { 347 | background: #111111; 348 | } 349 | #boxer.mobile .boxer-content { 350 | background-color: #111111; 351 | } 352 | #boxer.mobile .boxer-control { 353 | width: 50px; 354 | height: 100%; 355 | background: #111111; 356 | border-radius: 0; 357 | box-shadow: none; 358 | opacity: 1; 359 | } 360 | #boxer.mobile .boxer-control.previous { 361 | left: 0; 362 | } 363 | #boxer.mobile .boxer-control.previous:before { 364 | border-right-color: #eeeeee; 365 | margin-left: 19px; 366 | } 367 | #boxer.mobile .boxer-control.next { 368 | right: 0; 369 | } 370 | #boxer.mobile .boxer-control.next:before { 371 | border-left-color: #eeeeee; 372 | margin-right: 19px; 373 | } 374 | .no-touch #boxer.mobile .boxer-control, 375 | .no-touch #boxer.mobile:hover .boxer-control { 376 | opacity: 1; 377 | } 378 | .no-touch #boxer.mobile .boxer-control.disabled, 379 | .no-touch #boxer.mobile:hover .boxer-control.disabled { 380 | opacity: 0; 381 | cursor: default !important; 382 | } 383 | #boxer.mobile .boxer-meta { 384 | width: 100%; 385 | position: absolute; 386 | right: 0; 387 | bottom: 0; 388 | left: 0; 389 | background-color: #111111; 390 | padding: 15px 65px; 391 | } 392 | #boxer.mobile .boxer-position { 393 | color: #999999; 394 | font-size: 12px; 395 | margin: 0; 396 | padding: 0 15px 0 0; 397 | } 398 | #boxer.mobile .boxer-caption p { 399 | color: #eeeeee; 400 | font-size: 14px; 401 | margin: 0; 402 | padding: 0; 403 | } 404 | #boxer.mobile .boxer-image { 405 | -webkit-transition: none !important; 406 | transition: none !important; 407 | -webkit-transform: translate(0, 0); 408 | -ms-transform: translate(0, 0); 409 | transform: translate(0, 0); 410 | } 411 | #boxer.mobile.animated .boxer-image { 412 | -webkit-transition: -webkit-transform 0.25s ease-out !important; 413 | transition: transform 0.25s ease-out !important; 414 | } 415 | #boxer.mobile.inline .boxer-content, 416 | #boxer.mobile.iframe .boxer-content { 417 | overflow-x: hidden; 418 | overflow-y: scroll; 419 | -webkit-overflow-scrolling: touch; 420 | } 421 | -------------------------------------------------------------------------------- /jquery.fs.boxer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Boxer v3.3.0 - 2015-04-04 3 | * A jQuery plugin for displaying images, videos or content in a modal overlay. Part of the Formstone Library. 4 | * http://classic.formstone.it/boxer/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | ;(function ($, window) { 10 | "use strict"; 11 | 12 | var $body = null, 13 | data = {}, 14 | trueMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test((window.navigator.userAgent||window.navigator.vendor||window.opera)), 15 | transitionEvent, 16 | transitionSupported; 17 | 18 | /** 19 | * @options 20 | * @param callback [function] <$.noop> "Funciton called after opening instance" 21 | * @param customClass [string] <''> "Class applied to instance" 22 | * @param extensions [array] <"jpg", "sjpg", "jpeg", "png", "gif"> "Image type extensions" 23 | * @param fixed [boolean] "Flag for fixed positioning" 24 | * @param formatter [function] <$.noop> "Caption format function" 25 | * @param labels.close [string] <'Close'> "Close button text" 26 | * @param labels.count [string] <'of'> "Gallery count separator text" 27 | * @param labels.next [string] <'Next'> "Gallery control text" 28 | * @param labels.previous [string] <'Previous'> "Gallery control text" 29 | * @param margin [int] <50> "Margin used when sizing (single side)" 30 | * @param minHeight [int] <100> "Minimum height of modal" 31 | * @param minWidth [int] <100> "Minimum width of modal" 32 | * @param mobile [boolean] "Flag to force 'mobile' rendering" 33 | * @param opacity [number] <0.75> "Overlay target opacity" 34 | * @param retina [boolean] "Use 'retina' sizing (half's natural sizes)" 35 | * @param requestKey [string] <'boxer'> "GET variable for ajax / iframe requests" 36 | * @param top [int] <0> "Target top position; over-rides centering" 37 | * @param videoRadio [number] <0.5625> "Video height / width ratio (9 / 16 = 0.5625)" 38 | * @param videoWidth [int] <600> "Video target width" 39 | */ 40 | var options = { 41 | callback: $.noop, 42 | customClass: "", 43 | extensions: [ "jpg", "sjpg", "jpeg", "png", "gif" ], 44 | fixed: false, 45 | formatter: $.noop, 46 | labels: { 47 | close: "Close", 48 | count: "of", 49 | next: "Next", 50 | previous: "Previous" 51 | }, 52 | margin: 50, 53 | minHeight: 100, 54 | minWidth: 100, 55 | mobile: false, 56 | opacity: 0.75, 57 | retina: false, 58 | requestKey: "boxer", 59 | top: 0, 60 | videoRatio: 0.5625, 61 | videoWidth: 600 62 | }; 63 | 64 | /** 65 | * @events 66 | * @event open.boxer "Modal opened; triggered on window" 67 | * @event close.boxer "Modal closed; triggered on window" 68 | */ 69 | 70 | var pub = { 71 | 72 | /** 73 | * @method 74 | * @name close 75 | * @description Closes active instance of plugin 76 | * @example $.boxer("close"); 77 | */ 78 | close: function() { 79 | if (typeof data.$boxer !== "undefined") { 80 | data.$boxer.off(".boxer"); 81 | data.$overlay.trigger("click"); 82 | } 83 | }, 84 | 85 | /** 86 | * @method 87 | * @name defaults 88 | * @description Sets default plugin options 89 | * @param opts [object] <{}> "Options object" 90 | * @example $.boxer("defaults", opts); 91 | */ 92 | defaults: function(opts) { 93 | options = $.extend(options, opts || {}); 94 | return (typeof this === 'object') ? $(this) : false; 95 | }, 96 | 97 | /** 98 | * @method 99 | * @name destroy 100 | * @description Removes plugin bindings 101 | * @example $(".target").boxer("destroy"); 102 | */ 103 | destroy: function() { 104 | return $(this).off(".boxer"); 105 | }, 106 | 107 | /** 108 | * @method 109 | * @name resize 110 | * @description Triggers resize of instance 111 | * @example $.boxer("resize"); 112 | * @param height [int | false] "Target height or false to auto size" 113 | * @param width [int | false] "Target width or false to auto size" 114 | */ 115 | resize: function(e) { 116 | if (typeof data.$boxer !== "undefined") { 117 | if (typeof e !== "object") { 118 | data.targetHeight = arguments[0]; 119 | data.targetWidth = arguments[1]; 120 | } 121 | 122 | if (data.type === "element") { 123 | sizeContent(data.$content.find(">:first-child")); 124 | } else if (data.type === "image") { 125 | sizeImage(); 126 | } else if (data.type === "video") { 127 | sizeVideo(); 128 | } 129 | size(); 130 | } 131 | 132 | return $(this); 133 | } 134 | }; 135 | 136 | /** 137 | * @method private 138 | * @name init 139 | * @description Initializes plugin 140 | * @param opts [object] "Initialization options" 141 | */ 142 | function init(opts) { 143 | options.formatter = formatCaption; 144 | 145 | $body = $("body"); 146 | transitionEvent = getTransitionEvent(); 147 | transitionSupported = (transitionEvent !== false); 148 | 149 | // no transitions :( 150 | if (!transitionSupported) { 151 | transitionEvent = "transitionend.boxer"; 152 | } 153 | 154 | return $(this).on("click.boxer", $.extend({}, options, opts || {}), build); 155 | } 156 | 157 | /** 158 | * @method private 159 | * @name build 160 | * @description Builds target instance 161 | * @param e [object] "Event data" 162 | */ 163 | function build(e) { 164 | if (typeof data.$boxer === "undefined") { 165 | // Check target type 166 | var $target = $(this), 167 | $object = e.data.$object, 168 | source = ($target[0].href) ? $target[0].href || "" : "", 169 | hash = ($target[0].hash) ? $target[0].hash || "" : "", 170 | sourceParts = source.toLowerCase().split(".").pop().split(/\#|\?/), 171 | extension = sourceParts[0], 172 | type = $target.data("boxer-type") || "", 173 | isImage = ( (type === "image") || ($.inArray(extension, e.data.extensions) > -1 || source.substr(0, 10) === "data:image") ), 174 | isVideo = ( source.indexOf("youtube.com/embed") > -1 || source.indexOf("player.vimeo.com/video") > -1 ), 175 | isUrl = ( (type === "url") || (!isImage && !isVideo && source.substr(0, 4) === "http" && !hash) ), 176 | isElement = ( (type === "element") || (!isImage && !isVideo && !isUrl && (hash.substr(0, 1) === "#")) ), 177 | isObject = ( (typeof $object !== "undefined") ); 178 | 179 | if (isElement) { 180 | source = hash; 181 | } 182 | 183 | // Check if boxer is already active, retain default click 184 | if ($("#boxer").length > 1 || !(isImage || isVideo || isUrl || isElement || isObject)) { 185 | return; 186 | } 187 | 188 | // Kill event 189 | killEvent(e); 190 | 191 | // Cache internal data 192 | data = $.extend({}, { 193 | $window: $(window), 194 | $body: $("body"), 195 | $target: $target, 196 | $object: $object, 197 | visible: false, 198 | resizeTimer: null, 199 | touchTimer: null, 200 | gallery: { 201 | active: false 202 | }, 203 | isMobile: (trueMobile || e.data.mobile), 204 | isAnimating: true, 205 | oldContentHeight: 0, 206 | oldContentWidth: 0 207 | }, e.data); 208 | 209 | // Double the margin 210 | data.margin *= 2; 211 | 212 | if (isImage) { 213 | data.type = "image"; 214 | } else if (isVideo) { 215 | data.type = "video"; 216 | } else { 217 | data.type = "element"; 218 | } 219 | 220 | if (isImage || isVideo) { 221 | // Check for gallery 222 | var id = data.$target.data("gallery") || data.$target.attr("rel"); // backwards compatibility 223 | 224 | if (typeof id !== "undefined" && id !== false) { 225 | data.gallery.active = true; 226 | data.gallery.id = id; 227 | data.gallery.$items = $("a[data-gallery= " + data.gallery.id + "], a[rel= " + data.gallery.id + "]"); // backwards compatibility 228 | data.gallery.index = data.gallery.$items.index(data.$target); 229 | data.gallery.total = data.gallery.$items.length - 1; 230 | } 231 | } 232 | 233 | // Assemble HTML 234 | var html = ''; 235 | if (!data.isMobile) { 236 | html += '
'; 237 | } 238 | html += '
'; 252 | html += '' + data.labels.close + ''; 253 | html += ''; 254 | html += '
'; 255 | html += '
'; 256 | if (isImage || isVideo) { 257 | html += '
'; 258 | 259 | if (data.gallery.active) { 260 | html += ''; 261 | html += ''; 262 | html += '

' + data.labels.count + ' ' + (data.gallery.total + 1) + ''; 268 | html += '

'; 269 | html += ''; // caption, meta 276 | } 277 | html += '
'; //container, content, boxer 278 | 279 | // Modify Dom 280 | data.$body.append(html); 281 | 282 | // Cache jquery objects 283 | data.$overlay = $("#boxer-overlay"); 284 | data.$boxer = $("#boxer"); 285 | data.$container = data.$boxer.find(".boxer-container"); 286 | data.$content = data.$boxer.find(".boxer-content"); 287 | data.$meta = data.$boxer.find(".boxer-meta"); 288 | data.$position = data.$boxer.find(".boxer-position"); 289 | data.$caption = data.$boxer.find(".boxer-caption"); 290 | data.$controls = data.$boxer.find(".boxer-control"); 291 | 292 | data.paddingVertical = (!data.isMobile) ? (parseInt(data.$boxer.css("paddingTop"), 10) + parseInt(data.$boxer.css("paddingBottom"), 10)) : (data.$boxer.find(".boxer-close").outerHeight() / 2); 293 | data.paddingHorizontal = (!data.isMobile) ? (parseInt(data.$boxer.css("paddingLeft"), 10) + parseInt(data.$boxer.css("paddingRight"), 10)) : 0; 294 | data.contentHeight = data.$boxer.outerHeight() - data.paddingVertical; 295 | data.contentWidth = data.$boxer.outerWidth() - data.paddingHorizontal; 296 | data.controlHeight = data.$controls.outerHeight(); 297 | 298 | // Center 299 | center(); 300 | 301 | // Update gallery 302 | if (data.gallery.active) { 303 | updateControls(); 304 | } 305 | 306 | // Bind events 307 | data.$window.on("resize.boxer", pub.resize) 308 | .on("keydown.boxer", onKeypress); 309 | 310 | data.$body.on("touchstart.boxer click.boxer", "#boxer-overlay, #boxer .boxer-close", onClose) 311 | .on("touchmove.boxer", killEvent); 312 | 313 | if (data.gallery.active) { 314 | data.$boxer.on("touchstart.boxer click.boxer", ".boxer-control", advanceGallery); 315 | } 316 | 317 | data.$boxer.on(transitionEvent, function(e) { 318 | killEvent(e); 319 | 320 | if ($(e.target).is(data.$boxer)) { 321 | data.$boxer.off(transitionEvent); 322 | 323 | if (isImage) { 324 | loadImage(source); 325 | } else if (isVideo) { 326 | loadVideo(source); 327 | } else if (isUrl) { 328 | loadURL(source); 329 | } else if (isElement) { 330 | cloneElement(source); 331 | } else if (isObject) { 332 | appendObject(data.$object); 333 | } else { 334 | $.error("BOXER: '" + source + "' is not valid."); 335 | } 336 | } 337 | }); 338 | 339 | $body.addClass("boxer-open"); 340 | 341 | if (!transitionSupported) { 342 | data.$boxer.trigger(transitionEvent); 343 | } 344 | 345 | if (isObject) { 346 | return data.$boxer; 347 | } 348 | } 349 | } 350 | 351 | /** 352 | * @method private 353 | * @name onClose 354 | * @description Closes active instance 355 | * @param e [object] "Event data" 356 | */ 357 | function onClose(e) { 358 | killEvent(e); 359 | 360 | if (typeof data.$boxer !== "undefined") { 361 | data.$boxer.on(transitionEvent, function(e) { 362 | killEvent(e); 363 | 364 | if ($(e.target).is(data.$boxer)) { 365 | data.$boxer.off(transitionEvent); 366 | 367 | data.$overlay.remove(); 368 | data.$boxer.remove(); 369 | 370 | // reset data 371 | data = {}; 372 | } 373 | }).addClass("animating"); 374 | 375 | $body.removeClass("boxer-open"); 376 | 377 | if (!transitionSupported) { 378 | data.$boxer.trigger(transitionEvent); 379 | } 380 | 381 | clearTimer(data.resizeTimer); 382 | 383 | // Clean up 384 | data.$window.off("resize.boxer") 385 | .off("keydown.boxer"); 386 | 387 | data.$body.off(".boxer") 388 | .removeClass("boxer-open"); 389 | 390 | if (data.gallery.active) { 391 | data.$boxer.off(".boxer"); 392 | } 393 | 394 | if (data.isMobile) { 395 | if (data.type === "image" && data.gallery.active) { 396 | data.$container.off(".boxer"); 397 | } 398 | } 399 | 400 | data.$window.trigger("close.boxer"); 401 | } 402 | } 403 | 404 | /** 405 | * @method private 406 | * @name open 407 | * @description Opens active instance 408 | */ 409 | function open() { 410 | var position = calculatePosition(), 411 | durration = data.isMobile ? 0 : data.duration; 412 | 413 | if (!data.isMobile) { 414 | data.$controls.css({ 415 | marginTop: ((data.contentHeight - data.controlHeight - data.metaHeight) / 2) 416 | }); 417 | } 418 | 419 | if (!data.visible && data.isMobile && data.gallery.active) { 420 | data.$content.on("touchstart.boxer", ".boxer-image", onTouchStart); 421 | } 422 | 423 | if (data.isMobile || data.fixed) { 424 | data.$body.addClass("boxer-open"); 425 | } 426 | 427 | data.$boxer.on(transitionEvent, function(e) { 428 | killEvent(e); 429 | 430 | if ($(e.target).is(data.$boxer)) { 431 | data.$boxer.off(transitionEvent); 432 | 433 | data.$container.on(transitionEvent, function(e) { 434 | killEvent(e); 435 | 436 | if ($(e.target).is(data.$container)) { 437 | data.$container.off(transitionEvent); 438 | 439 | data.$boxer.removeClass("animating"); 440 | 441 | data.isAnimating = false; 442 | } 443 | }); 444 | 445 | data.$boxer.removeClass("loading"); 446 | 447 | if (!transitionSupported) { 448 | data.$content.trigger(transitionEvent); 449 | } 450 | 451 | data.visible = true; 452 | 453 | // Fire callback + event 454 | data.callback.apply(data.$boxer); 455 | data.$window.trigger("open.boxer"); 456 | 457 | // Start preloading 458 | if (data.gallery.active) { 459 | preloadGallery(); 460 | } 461 | } 462 | }); 463 | 464 | if (!data.isMobile) { 465 | data.$boxer.css({ 466 | height: data.contentHeight + data.paddingVertical, 467 | width: data.contentWidth + data.paddingHorizontal, 468 | top: (!data.fixed) ? position.top : 0 469 | }); 470 | } 471 | 472 | // Trigger event in case the content size hasn't changed 473 | var contentHasChanged = (data.oldContentHeight !== data.contentHeight || data.oldContentWidth !== data.contentWidth); 474 | 475 | if (data.isMobile || !transitionSupported || !contentHasChanged) { 476 | data.$boxer.trigger(transitionEvent); 477 | } 478 | 479 | // Track content size changes 480 | data.oldContentHeight = data.contentHeight; 481 | data.oldContentWidth = data.contentWidth; 482 | } 483 | 484 | /** 485 | * @method private 486 | * @name size 487 | * @description Sizes active instance 488 | */ 489 | function size() { 490 | if (data.visible && !data.isMobile) { 491 | var position = calculatePosition(); 492 | 493 | data.$controls.css({ 494 | marginTop: ((data.contentHeight - data.controlHeight - data.metaHeight) / 2) 495 | }); 496 | 497 | data.$boxer.css({ 498 | height: data.contentHeight + data.paddingVertical, 499 | width: data.contentWidth + data.paddingHorizontal, 500 | top: (!data.fixed) ? position.top : 0 501 | }); 502 | } 503 | } 504 | 505 | /** 506 | * @method private 507 | * @name center 508 | * @description Centers instance 509 | */ 510 | function center() { 511 | var position = calculatePosition(); 512 | 513 | data.$boxer.css({ 514 | top: (!data.fixed) ? position.top : 0 515 | }); 516 | } 517 | 518 | /** 519 | * @method private 520 | * @name calculatePosition 521 | * @description Calculates positions 522 | * @return [object] "Object containing top and left positions" 523 | */ 524 | function calculatePosition() { 525 | if (data.isMobile) { 526 | return { 527 | left: 0, 528 | top: 0 529 | }; 530 | } 531 | 532 | var pos = { 533 | left: (data.$window.width() - data.contentWidth - data.paddingHorizontal) / 2, 534 | top: (data.top <= 0) ? ((data.$window.height() - data.contentHeight - data.paddingVertical) / 2) : data.top 535 | }; 536 | 537 | if (data.fixed !== true) { 538 | pos.top += data.$window.scrollTop(); 539 | } 540 | 541 | return pos; 542 | } 543 | 544 | /** 545 | * @method private 546 | * @name formatCaption 547 | * @description Formats caption 548 | * @param $target [jQuery object] "Target element" 549 | */ 550 | function formatCaption($target) { 551 | var title = $target.attr("title"); 552 | return (title !== undefined && title.trim() !== "") ? '

' + title.trim() + '

' : ""; 553 | } 554 | 555 | /** 556 | * @method private 557 | * @name loadImage 558 | * @description Loads source image 559 | * @param source [string] "Source image URL" 560 | */ 561 | function loadImage(source) { 562 | // Cache current image 563 | data.$image = $(""); 564 | 565 | data.$image.load(function() { 566 | data.$image.off("load, error"); 567 | 568 | var naturalSize = calculateNaturalSize(data.$image); 569 | 570 | data.naturalHeight = naturalSize.naturalHeight; 571 | data.naturalWidth = naturalSize.naturalWidth; 572 | 573 | if (data.retina) { 574 | data.naturalHeight /= 2; 575 | data.naturalWidth /= 2; 576 | } 577 | 578 | data.$content.prepend(data.$image); 579 | 580 | if (data.$caption.html() === "") { 581 | data.$caption.hide(); 582 | } else { 583 | data.$caption.show(); 584 | } 585 | 586 | // Size content to be sure it fits the viewport 587 | sizeImage(); 588 | open(); 589 | }).error(loadError) 590 | .attr("src", source) 591 | .addClass("boxer-image"); 592 | 593 | // If image has already loaded into cache, trigger load event 594 | if (data.$image[0].complete || data.$image[0].readyState === 4) { 595 | data.$image.trigger("load"); 596 | } 597 | } 598 | 599 | /** 600 | * @method private 601 | * @name sizeImage 602 | * @description Sizes image to fit in viewport 603 | * @param count [int] "Number of resize attempts" 604 | */ 605 | function sizeImage() { 606 | var count = 0; 607 | 608 | data.windowHeight = data.viewportHeight = data.$window.height() - data.paddingVertical; 609 | data.windowWidth = data.viewportWidth = data.$window.width() - data.paddingHorizontal; 610 | 611 | data.contentHeight = Infinity; 612 | data.contentWidth = Infinity; 613 | 614 | data.imageMarginTop = 0; 615 | data.imageMarginLeft = 0; 616 | 617 | while (data.contentHeight > data.viewportHeight && count < 2) { 618 | data.imageHeight = (count === 0) ? data.naturalHeight : data.$image.outerHeight(); 619 | data.imageWidth = (count === 0) ? data.naturalWidth : data.$image.outerWidth(); 620 | data.metaHeight = (count === 0) ? 0 : data.metaHeight; 621 | 622 | if (count === 0) { 623 | data.ratioHorizontal = data.imageHeight / data.imageWidth; 624 | data.ratioVertical = data.imageWidth / data.imageHeight; 625 | 626 | data.isWide = (data.imageWidth > data.imageHeight); 627 | } 628 | 629 | // Double check min and max 630 | if (data.imageHeight < data.minHeight) { 631 | data.minHeight = data.imageHeight; 632 | } 633 | if (data.imageWidth < data.minWidth) { 634 | data.minWidth = data.imageWidth; 635 | } 636 | 637 | if (data.isMobile) { 638 | // Get meta height before sizing 639 | data.$meta.css({ 640 | width: data.windowWidth 641 | }); 642 | data.metaHeight = data.$meta.outerHeight(true); 643 | 644 | // Content match viewport 645 | data.contentHeight = data.viewportHeight - data.paddingVertical; 646 | data.contentWidth = data.viewportWidth - data.paddingHorizontal; 647 | 648 | fitImage(); 649 | 650 | data.imageMarginTop = (data.contentHeight - data.targetImageHeight - data.metaHeight) / 2; 651 | data.imageMarginLeft = (data.contentWidth - data.targetImageWidth) / 2; 652 | } else { 653 | // Viewport should match window, less margin, padding and meta 654 | if (count === 0) { 655 | data.viewportHeight -= (data.margin + data.paddingVertical); 656 | data.viewportWidth -= (data.margin + data.paddingHorizontal); 657 | } 658 | data.viewportHeight -= data.metaHeight; 659 | 660 | fitImage(); 661 | 662 | data.contentHeight = data.targetImageHeight; 663 | data.contentWidth = data.targetImageWidth; 664 | } 665 | 666 | // Modify DOM 667 | 668 | data.$meta.css({ 669 | width: data.contentWidth 670 | }); 671 | 672 | data.$image.css({ 673 | height: data.targetImageHeight, 674 | width: data.targetImageWidth, 675 | marginTop: data.imageMarginTop, 676 | marginLeft: data.imageMarginLeft 677 | }); 678 | 679 | if (!data.isMobile) { 680 | data.metaHeight = data.$meta.outerHeight(true); 681 | data.contentHeight += data.metaHeight; 682 | } 683 | 684 | count ++; 685 | } 686 | } 687 | 688 | /** 689 | * @method private 690 | * @name fitImage 691 | * @description Calculates target image size 692 | */ 693 | function fitImage() { 694 | var height = (!data.isMobile) ? data.viewportHeight : data.contentHeight - data.metaHeight, 695 | width = (!data.isMobile) ? data.viewportWidth : data.contentWidth; 696 | 697 | if (data.isWide) { 698 | //WIDE 699 | data.targetImageWidth = width; 700 | data.targetImageHeight = data.targetImageWidth * data.ratioHorizontal; 701 | 702 | if (data.targetImageHeight > height) { 703 | data.targetImageHeight = height; 704 | data.targetImageWidth = data.targetImageHeight * data.ratioVertical; 705 | } 706 | } else { 707 | //TALL 708 | data.targetImageHeight = height; 709 | data.targetImageWidth = data.targetImageHeight * data.ratioVertical; 710 | 711 | if (data.targetImageWidth > width) { 712 | data.targetImageWidth = width; 713 | data.targetImageHeight = data.targetImageWidth * data.ratioHorizontal; 714 | } 715 | } 716 | 717 | // MAX 718 | if (data.targetImageWidth > data.imageWidth || data.targetImageHeight > data.imageHeight) { 719 | data.targetImageHeight = data.imageHeight; 720 | data.targetImageWidth = data.imageWidth; 721 | } 722 | 723 | // MIN 724 | if (data.targetImageWidth < data.minWidth || data.targetImageHeight < data.minHeight) { 725 | if (data.targetImageWidth < data.minWidth) { 726 | data.targetImageWidth = data.minWidth; 727 | data.targetImageHeight = data.targetImageWidth * data.ratioHorizontal; 728 | } else { 729 | data.targetImageHeight = data.minHeight; 730 | data.targetImageWidth = data.targetImageHeight * data.ratioVertical; 731 | } 732 | } 733 | } 734 | 735 | /** 736 | * @method private 737 | * @name loadVideo 738 | * @description Loads source video 739 | * @param source [string] "Source video URL" 740 | */ 741 | function loadVideo(source) { 742 | data.$videoWrapper = $('
'); 743 | data.$video = $('