PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 3.34
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v3.34
3.37 3.36 3.35 3.34 3.33 2.19 2.20 2.21 2.22 2.23 2.24 2.25 2.26 2.27 2.28 2.29 2.30 2.31 2.32 2.33 2.34 2.40 2.41 2.42 2.43 All 142 releases
photonic / include / ext / bigpicture / bigpicture.js

bigpicture.js in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 3.34, at include/ext/bigpicture/bigpicture.js

760 lines 24.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 var BigPicture = (function () {
2 // BigPicture.js | license MIT | henrygd.me/bigpicture
3
4 // trigger element used to open popup
5 var el;
6
7 // set to true after first interaction
8 var initialized;
9
10 // container element holding html needed for script
11 var container;
12
13 // currently active display element (image, video, youtube / vimeo iframe container)
14 var displayElement;
15
16 // popup image element
17 var displayImage;
18
19 // popup video element
20 var displayVideo;
21
22 // popup audio element
23 var displayAudio;
24
25 // container element to hold youtube / vimeo iframe
26 var iframeContainer;
27
28 // iframe to hold youtube / vimeo player
29 var iframeSiteVid;
30
31 // store requested image source
32 var imgSrc;
33
34 // button that closes the container
35 var closeButton;
36
37 // youtube / vimeo video id
38 var siteVidID;
39
40 // keeps track of loading icon display state
41 var isLoading;
42
43 // timeout to check video status while loading
44 var checkMediaTimeout;
45
46 // loading icon element
47 var loadingIcon;
48
49 // caption element
50 var caption;
51
52 // caption content element
53 var captionText;
54
55 // store caption content
56 var captionContent;
57
58 // hide caption button element
59 var captionHideButton;
60
61 // open state for container element
62 var isOpen;
63
64 // gallery open state
65 var galleryOpen;
66
67 // used during close animation to avoid triggering timeout twice
68 var isClosing;
69
70 // array of prev viewed image urls to check if cached before showing loading icon
71 var imgCache = [];
72
73 // store whether image requested is remote or local
74 var remoteImage;
75
76 // store animation opening callbacks
77 var animationStart;
78 var animationEnd;
79
80 // store changeGalleryImage callback
81 var onChangeImage;
82
83 // gallery left / right icons
84 var rightArrowBtn;
85
86 var leftArrowBtn;
87
88 // position of gallery
89 var galleryPosition;
90
91 // hold active gallery els / image src
92 var galleryEls;
93
94 // counter element
95 var galleryCounter;
96
97 // store images in gallery that are being loaded
98 var preloadedImages = {};
99
100 // whether device supports touch events
101 var supportsTouch;
102
103 // options object
104 var opts;
105
106 // Save bytes in the minified version
107 var appendEl = 'appendChild';
108 var createEl = 'createElement';
109 var removeEl = 'removeChild';
110
111 function BigPicture (options) {
112 // initialize called on initial open to create elements / style / event handlers
113 initialized || initialize(options);
114
115 // clear currently loading stuff
116 if (isLoading) {
117 clearTimeout(checkMediaTimeout);
118 removeContainer();
119 }
120
121 opts = options;
122
123 // store video id if youtube / vimeo video is requested
124 siteVidID = options.ytSrc || options.vimeoSrc;
125
126 // store optional callbacks
127 animationStart = options.animationStart;
128 animationEnd = options.animationEnd;
129 onChangeImage = options.onChangeImage;
130
131 // set trigger element
132 el = options.el;
133
134 // wipe existing remoteImage state
135 remoteImage = false;
136
137 // set caption if provided
138 captionContent = el.getAttribute('data-caption');
139
140 if (options.gallery) {
141 makeGallery(options.gallery, options.position);
142 } else if (siteVidID || options.iframeSrc) {
143 // if vimeo, youtube, or iframe video
144 // toggleLoadingIcon(true)
145 displayElement = iframeContainer;
146 createIframe();
147 } else if (options.imgSrc) {
148 // if remote image
149 remoteImage = true;
150 imgSrc = options.imgSrc;
151 !~imgCache.indexOf(imgSrc) && toggleLoadingIcon(true);
152 displayElement = displayImage;
153 displayElement.src = imgSrc;
154 } else if (options.audio) {
155 // if direct video link
156 toggleLoadingIcon(true);
157 displayElement = displayAudio;
158 displayElement.src = options.audio;
159 checkMedia('audio file');
160 } else if (options.vidSrc) {
161 // if direct video link
162 toggleLoadingIcon(true);
163 if (options.dimensions) {
164 changeCSS(displayVideo, ("width:" + (options.dimensions[0]) + "px"));
165 }
166 makeVidSrc(options.vidSrc);
167 checkMedia('video');
168 } else {
169 // local image / background image already loaded on page
170 displayElement = displayImage;
171 // get img source or element background image
172 displayElement.src =
173 el.tagName === 'IMG'
174 ? el.src
175 : window
176 .getComputedStyle(el)
177 .backgroundImage.replace(/^url|[(|)|'|"]/g, '');
178 }
179
180 // add container to page
181 container[appendEl](displayElement);
182 document.body[appendEl](container);
183 return {
184 close: close,
185 opts: opts,
186 updateDimensions: updateDimensions,
187 display: displayElement,
188 next: function () { return updateGallery(1); },
189 prev: function () { return updateGallery(-1); },
190 }
191 }
192
193 // create all needed methods / store dom elements on first use
194 function initialize(options) {
195 var startX, isPinch;
196 // return close button elements
197 function createCloseButton(className) {
198 var el = document[createEl]('button');
199 el.className = className;
200 el.innerHTML =
201 '<svg viewBox="0 0 48 48"><path d="M28 24L47 5a3 3 0 1 0-4-4L24 20 5 1a3 3 0 1 0-4 4l19 19L1 43a3 3 0 1 0 4 4l19-19 19 19a3 3 0 0 0 4 0v-4L28 24z"/></svg>';
202 return el
203 }
204
205 function createArrowSymbol(direction, style) {
206 var el = document[createEl]('button');
207 el.className = 'bp-lr';
208 el.innerHTML =
209 '<svg viewBox="0 0 129 129" height="70" fill="#fff"><path d="M88.6 121.3c.8.8 1.8 1.2 2.9 1.2s2.1-.4 2.9-1.2a4.1 4.1 0 0 0 0-5.8l-51-51 51-51a4.1 4.1 0 0 0-5.8-5.8l-54 53.9a4.1 4.1 0 0 0 0 5.8l54 53.9z"/></svg>';
210 changeCSS(el, style);
211 el.onclick = function (e) {
212 e.stopPropagation();
213 updateGallery(direction);
214 };
215 return el
216 }
217
218 // add style - if you want to tweak, run through beautifier
219 var style = document[createEl]('STYLE');
220 var containerColor = (options && options.overlayColor) ? options.overlayColor : 'rgba(0,0,0,.7)';
221 style.innerHTML =
222 "#bp_caption,#bp_container{bottom:0;left:0;right:0;position:fixed;opacity:0}#bp_container>*,#bp_loader{position:absolute;right:0;z-index:10}#bp_container,#bp_caption,#bp_container svg{pointer-events:none}#bp_container{top:0;z-index:9999;background:" + containerColor + ";opacity:0;transition:opacity .35s}#bp_loader{top:0;left:0;bottom:0;display:flex;align-items:center;cursor:wait;background:0;z-index:9}#bp_loader svg{width:50%;max-width:300px;max-height:50%;margin:auto;animation:bpturn 1s infinite linear}#bp_aud,#bp_container img,#bp_sv,#bp_vid{user-select:none;max-height:96%;max-width:96%;top:0;bottom:0;left:0;margin:auto;box-shadow:0 0 3em rgba(0,0,0,.4);z-index:-1}#bp_sv{background:#111}#bp_sv svg{width:66px}#bp_caption{font-size:.9em;padding:1.3em;background:rgba(15,15,15,.94);color:#fff;text-align:center;transition:opacity .3s}#bp_aud{width:650px;top:calc(50% - 20px);bottom:auto;box-shadow:none}#bp_count{left:0;right:auto;padding:14px;color:rgba(255,255,255,.7);font-size:22px;cursor:default}#bp_container button{position:absolute;border:0;outline:0;background:0;cursor:pointer;transition:all .1s}#bp_container>.bp-x{padding:0;height:41px;width:41px;border-radius:100%;top:8px;right:14px;opacity:.8;line-height:1}#bp_container>.bp-x:focus,#bp_container>.bp-x:hover{background:rgba(255,255,255,.2)}.bp-x svg,.bp-xc svg{height:21px;width:20px;fill:#fff;vertical-align:top;}.bp-xc svg{width:16px}#bp_container .bp-xc{left:2%;bottom:100%;padding:9px 20px 7px;background:#d04444;border-radius:2px 2px 0 0;opacity:.85}#bp_container .bp-xc:focus,#bp_container .bp-xc:hover{opacity:1}.bp-lr{top:50%;top:calc(50% - 130px);padding:99px 0;width:6%;background:0;border:0;opacity:.4;transition:opacity .1s}.bp-lr:focus,.bp-lr:hover{opacity:.8}@keyframes bpf{50%{transform:translatex(15px)}100%{transform:none}}@keyframes bpl{50%{transform:translatex(-15px)}100%{transform:none}}@keyframes bpfl{0%{opacity:0;transform:translatex(70px)}100%{opacity:1;transform:none}}@keyframes bpfr{0%{opacity:0;transform:translatex(-70px)}100%{opacity:1;transform:none}}@keyframes bpfol{0%{opacity:1;transform:none}100%{opacity:0;transform:translatex(-70px)}}@keyframes bpfor{0%{opacity:1;transform:none}100%{opacity:0;transform:translatex(70px)}}@keyframes bpturn{0%{transform:none}100%{transform:rotate(360deg)}}@media (max-width:600px){.bp-lr{font-size:15vw}}";
223 document.head[appendEl](style);
224
225 // create container element
226 container = document[createEl]('DIV');
227 container.id = 'bp_container';
228 container.onclick = close;
229 closeButton = createCloseButton('bp-x');
230 container[appendEl](closeButton);
231 // gallery touch listeners
232 if ('ontouchend' in window && window.visualViewport) {
233 supportsTouch = true;
234 container.ontouchstart = function (ref) {
235 var touches = ref.touches;
236 var changedTouches = ref.changedTouches;
237
238 isPinch = touches.length > 1;
239 startX = changedTouches[0].pageX;
240 };
241 container.ontouchend = function (ref) {
242 var changedTouches = ref.changedTouches;
243
244 if (galleryOpen && !isPinch && window.visualViewport.scale <= 1) {
245 var distX = changedTouches[0].pageX - startX;
246 // swipe right
247 distX < -30 && updateGallery(1);
248 // swipe left
249 distX > 30 && updateGallery(-1);
250 }
251 };
252 }
253
254 // create display image element
255 displayImage = document[createEl]('IMG');
256
257 // create display video element
258 displayVideo = document[createEl]('VIDEO');
259 displayVideo.id = 'bp_vid';
260 displayVideo.setAttribute('playsinline', true);
261 displayVideo.controls = true;
262 displayVideo.loop = true;
263
264 // create audio element
265 displayAudio = document[createEl]('audio');
266 displayAudio.id = 'bp_aud';
267 displayAudio.controls = true;
268 displayAudio.loop = true;
269
270 // create gallery counter
271 galleryCounter = document[createEl]('span');
272 galleryCounter.id = 'bp_count';
273
274 // create caption elements
275 caption = document[createEl]('DIV');
276 caption.id = 'bp_caption';
277 captionHideButton = createCloseButton('bp-xc');
278 captionHideButton.onclick = toggleCaption.bind(null, false);
279 caption[appendEl](captionHideButton);
280 captionText = document[createEl]('SPAN');
281 caption[appendEl](captionText);
282 container[appendEl](caption);
283
284 // left / right arrow icons
285 rightArrowBtn = createArrowSymbol(1, 'transform:scalex(-1)');
286 leftArrowBtn = createArrowSymbol(-1, 'left:0;right:auto');
287
288 // create loading icon element
289 loadingIcon = document[createEl]('DIV');
290 loadingIcon.id = 'bp_loader';
291 loadingIcon.innerHTML =
292 '<svg viewbox="0 0 32 32" fill="#fff" opacity=".8"><path d="M16 0a16 16 0 0 0 0 32 16 16 0 0 0 0-32m0 4a12 12 0 0 1 0 24 12 12 0 0 1 0-24" fill="#000" opacity=".5"/><path d="M16 0a16 16 0 0 1 16 16h-4A12 12 0 0 0 16 4z"/></svg>';
293 // create youtube / vimeo container
294 iframeContainer = document[createEl]('DIV');
295 iframeContainer.id = 'bp_sv';
296
297 // create iframe to hold youtube / vimeo player
298 iframeSiteVid = document[createEl]('IFRAME');
299 iframeSiteVid.setAttribute('allowfullscreen', true);
300 iframeSiteVid.allow = 'autoplay; fullscreen';
301 iframeSiteVid.onload = function () { return iframeContainer[removeEl](loadingIcon); };
302 changeCSS(
303 iframeSiteVid,
304 'border:0;position:absolute;height:100%;width:100%;left:0;top:0'
305 );
306 iframeContainer[appendEl](iframeSiteVid);
307
308 // display image bindings for image load and error
309 displayImage.onload = open;
310 displayImage.onerror = open.bind(null, 'image');
311
312 window.addEventListener('resize', function () {
313 // adjust loader position on window resize
314 galleryOpen || (isLoading && toggleLoadingIcon(true));
315 // adjust iframe dimensions
316 displayElement === iframeContainer && updateDimensions();
317 });
318
319 // close container on escape key press and arrow buttons for gallery
320 document.addEventListener('keyup', function (ref) {
321 var keyCode = ref.keyCode;
322
323 keyCode === 27 && isOpen && close();
324 if (galleryOpen) {
325 keyCode === 39 && updateGallery(1);
326 keyCode === 37 && updateGallery(-1);
327 keyCode === 38 && updateGallery(10);
328 keyCode === 40 && updateGallery(-10);
329 }
330 });
331 // prevent scrolling with arrow keys if gallery open
332 document.addEventListener('keydown', function (e) {
333 var usedKeys = [37, 38, 39, 40];
334 if (galleryOpen && ~usedKeys.indexOf(e.keyCode)) {
335 e.preventDefault();
336 }
337 });
338
339 // trap focus within conainer while open
340 document.addEventListener(
341 'focus',
342 function (e) {
343 if (isOpen && !container.contains(e.target)) {
344 e.stopPropagation();
345 closeButton.focus();
346 }
347 },
348 true
349 );
350
351 // all done
352 initialized = true;
353 }
354
355 // return transform style to make full size display el match trigger el size
356 function getRect() {
357 var ref = el.getBoundingClientRect();
358 var top = ref.top;
359 var left = ref.left;
360 var width = ref.width;
361 var height = ref.height;
362 var leftOffset = left - (container.clientWidth - width) / 2;
363 var centerTop = top - (container.clientHeight - height) / 2;
364 var scaleWidth = el.clientWidth / displayElement.clientWidth;
365 var scaleHeight = el.clientHeight / displayElement.clientHeight;
366 return ("transform:translate3D(" + leftOffset + "px, " + centerTop + "px, 0) scale3D(" + scaleWidth + ", " + scaleHeight + ", 0)")
367 }
368
369 function makeVidSrc(source) {
370 if (Array.isArray(source)) {
371 displayElement = displayVideo.cloneNode();
372 source.forEach(function (src) {
373 var source = document[createEl]('SOURCE');
374 source.src = src;
375 source.type = "video/" + (src.match(/.(\w+)$/)[1]);
376 displayElement[appendEl](source);
377 });
378 } else {
379 displayElement = displayVideo;
380 displayElement.src = source;
381 }
382 }
383
384 function makeGallery(gallery, position) {
385 var galleryAttribute = opts.galleryAttribute || 'data-bp';
386 if (Array.isArray(gallery)) {
387 // is array of images
388 galleryPosition = position || 0;
389 galleryEls = gallery;
390 captionContent = gallery[galleryPosition].caption;
391 } else {
392 // is element selector or nodelist
393 galleryEls = [].slice.call(
394 typeof gallery === 'string'
395 ? document.querySelectorAll((gallery + " [" + galleryAttribute + "]"))
396 : gallery
397 );
398 // find initial gallery position
399 var elIndex = galleryEls.indexOf(el);
400 galleryPosition =
401 position === 0 || position ? position : elIndex !== -1 ? elIndex : 0;
402 // make gallery object w/ els / src / caption
403 galleryEls = galleryEls.map(function (el) { return ({
404 el: el,
405 src: el.getAttribute(galleryAttribute),
406 caption: el.getAttribute('data-caption'),
407 }); });
408 }
409 // show loading icon if needed
410 remoteImage = true;
411 // set initial src to imgSrc so it will be cached in open func
412 imgSrc = galleryEls[galleryPosition].src;
413 !~imgCache.indexOf(imgSrc) && toggleLoadingIcon(true);
414 if (galleryEls.length > 1) {
415 // if length is greater than one, add gallery stuff
416 container[appendEl](galleryCounter);
417 galleryCounter.innerHTML = (galleryPosition + 1) + "/" + (galleryEls.length);
418 if (!supportsTouch) {
419 // add arrows if device doesn't support touch
420 container[appendEl](rightArrowBtn);
421 container[appendEl](leftArrowBtn);
422 }
423 } else {
424 // gallery is one, just show without clutter
425 galleryEls = false;
426 }
427 displayElement = displayImage;
428 // set initial image src
429 displayElement.src = imgSrc;
430 }
431
432 function updateGallery(movement) {
433 var galleryLength = galleryEls.length - 1;
434
435 // only allow one change at a time
436 if (isLoading) {
437 return
438 }
439
440 // return if requesting out of range image
441 var isEnd =
442 (movement > 0 && galleryPosition === galleryLength) ||
443 (movement < 0 && !galleryPosition);
444 if (isEnd) {
445 // if beginning or end of gallery, run end animation
446 if (!opts.loop) {
447 changeCSS(displayImage, '');
448 setTimeout(
449 changeCSS,
450 9,
451 displayImage,
452 ("animation:" + (movement > 0 ? 'bpl' : 'bpf') + " .3s;transition:transform .35s")
453 );
454 return
455 }
456 // if gallery is looped, adjust position to beginning / end
457 galleryPosition = movement > 0 ? -1 : galleryLength + 1;
458 }
459
460 // normalize position
461 galleryPosition = Math.max(
462 0,
463 Math.min(galleryPosition + movement, galleryLength)
464 )
465
466 // load images before and after for quicker scrolling through pictures
467 ;[galleryPosition - 1, galleryPosition, galleryPosition + 1].forEach(
468 function (position) {
469 // normalize position
470 position = Math.max(0, Math.min(position, galleryLength));
471 // cancel if image has already been preloaded
472 if (preloadedImages[position]) { return }
473 var src = galleryEls[position].src;
474 // create image for preloadedImages
475 var img = document[createEl]('IMG');
476 img.addEventListener('load', addToImgCache.bind(null, src));
477 img.src = src;
478 preloadedImages[position] = img;
479 }
480 );
481 // if image is loaded, show it
482 if (preloadedImages[galleryPosition].complete) {
483 return changeGalleryImage(movement)
484 }
485 // if not, show loading icon and change when loaded
486 isLoading = true;
487 changeCSS(loadingIcon, 'opacity:.4;');
488 container[appendEl](loadingIcon);
489 preloadedImages[galleryPosition].onload = function () {
490 galleryOpen && changeGalleryImage(movement);
491 };
492 // if error, store error object in el array
493 preloadedImages[galleryPosition].onerror = function () {
494 galleryEls[galleryPosition] = {
495 error: 'Error loading image',
496 };
497 galleryOpen && changeGalleryImage(movement);
498 };
499 }
500
501 function changeGalleryImage(movement) {
502 if (isLoading) {
503 container[removeEl](loadingIcon);
504 isLoading = false;
505 }
506 var activeEl = galleryEls[galleryPosition];
507 if (activeEl.error) {
508 // show alert if error
509 alert(activeEl.error);
510 } else {
511 // add new image, animate images in and out w/ css animation
512 var oldimg = container.querySelector('img:last-of-type');
513 displayImage = displayElement = preloadedImages[galleryPosition];
514 changeCSS(
515 displayImage,
516 ("animation:" + (movement > 0 ? 'bpfl' : 'bpfr') + " .35s;transition:transform .35s")
517 );
518 changeCSS(oldimg, ("animation:" + (movement > 0 ? 'bpfol' : 'bpfor') + " .35s both"));
519 container[appendEl](displayImage);
520 // update el for closing animation
521 if (activeEl.el) {
522 el = activeEl.el;
523 }
524 }
525 // update counter
526 galleryCounter.innerHTML = (galleryPosition + 1) + "/" + (galleryEls.length);
527 // show / hide caption
528 toggleCaption(galleryEls[galleryPosition].caption);
529 // execute onChangeImage callback
530 onChangeImage && onChangeImage([displayImage, galleryEls[galleryPosition]]);
531 }
532
533 // create video iframe
534 function createIframe() {
535 var url;
536 var prefix = 'https://';
537 var suffix = 'autoplay=1';
538
539 // create appropriate url
540 if (opts.ytSrc) {
541 url = prefix + "www.youtube" + (opts.ytNoCookie ? '-nocookie' : '') + ".com/embed/" + siteVidID + "?html5=1&rel=0&playsinline=1&" + suffix;
542 } else if (opts.vimeoSrc) {
543 url = prefix + "player.vimeo.com/video/" + siteVidID + "?" + suffix;
544 } else if (opts.iframeSrc) {
545 url = opts.iframeSrc;
546 }
547
548 // add loading spinner to iframe container
549 changeCSS(loadingIcon, '');
550 iframeContainer[appendEl](loadingIcon);
551
552 // set iframe src to url
553 iframeSiteVid.src = url;
554
555 updateDimensions();
556
557 setTimeout(open, 9);
558 }
559
560 function updateDimensions() {
561 var height;
562 var width;
563
564 // handle height / width / aspect / max width for iframe
565 var windowHeight = window.innerHeight * 0.95;
566 var windowWidth = window.innerWidth * 0.95;
567 var windowAspect = windowHeight / windowWidth;
568
569 var ref = opts.dimensions || [1920, 1080];
570 var dimensionWidth = ref[0];
571 var dimensionHeight = ref[1];
572
573 var iframeAspect = dimensionHeight / dimensionWidth;
574
575 if (iframeAspect > windowAspect) {
576 height = Math.min(dimensionHeight, windowHeight);
577 width = height / iframeAspect;
578 } else {
579 width = Math.min(dimensionWidth, windowWidth);
580 height = width * iframeAspect;
581 }
582
583 iframeContainer.style.cssText += "width:" + width + "px;height:" + height + "px;";
584 }
585
586 // timeout to check video status while loading
587 function checkMedia(errMsg) {
588 if (~[1, 4].indexOf(displayElement.readyState)) {
589 open();
590 // short timeout to to make sure controls show in safari 11
591 setTimeout(function () {
592 displayElement.play();
593 }, 99);
594 } else if (displayElement.error) {
595 open(errMsg);
596 } else {
597 checkMediaTimeout = setTimeout(checkMedia, 35, errMsg);
598 }
599 }
600
601 // hide / show loading icon
602 function toggleLoadingIcon(bool) {
603 // don't show loading icon if noLoader is specified
604 if (opts.noLoader) {
605 return
606 }
607 // bool is true if we want to show icon, false if we want to remove
608 // change style to match trigger element dimensions if we want to show
609 bool &&
610 changeCSS(
611 loadingIcon,
612 ("top:" + (el.offsetTop) + "px;left:" + (el.offsetLeft) + "px;height:" + (el.clientHeight) + "px;width:" + (el.clientWidth) + "px")
613 );
614 // add or remove loader from DOM
615 el.parentElement[bool ? appendEl : removeEl](loadingIcon);
616 isLoading = bool;
617 }
618
619 // hide & show caption
620 function toggleCaption(captionContent) {
621 if (captionContent) {
622 captionText.innerHTML = captionContent;
623 }
624 changeCSS(
625 caption,
626 ("opacity:" + (captionContent ? "1;pointer-events:auto" : '0'))
627 );
628 }
629
630 function addToImgCache(url) {
631 !~imgCache.indexOf(url) && imgCache.push(url);
632 }
633
634 // animate open of image / video; display caption if needed
635 function open(err) {
636 // hide loading spinner
637 isLoading && toggleLoadingIcon();
638
639 // execute animationStart callback
640 animationStart && animationStart();
641
642 // check if we have an error string instead of normal event
643 if (typeof err === 'string') {
644 removeContainer();
645 return opts.onError
646 ? opts.onError()
647 : alert(("Error: The requested " + err + " could not be loaded."))
648 }
649
650 // if remote image is loaded, add url to imgCache array
651 remoteImage && addToImgCache(imgSrc);
652
653 // transform displayEl to match trigger el
654 displayElement.style.cssText += getRect();
655
656 // fade in container
657 changeCSS(container, "opacity:1;pointer-events:auto");
658
659 // set animationEnd callback to run after animation ends (cleared if container closed)
660 if (animationEnd) {
661 animationEnd = setTimeout(animationEnd, 410);
662 }
663
664 isOpen = true;
665
666 galleryOpen = !!galleryEls;
667
668 // enlarge displayEl, fade in caption if hasCaption
669 setTimeout(function () {
670 displayElement.style.cssText += 'transition:transform .35s;transform:none';
671 captionContent && setTimeout(toggleCaption, 250, captionContent);
672 }, 60);
673 }
674
675 // close active display element
676 function close(e) {
677 var target = e ? e.target : container;
678 var clickEls = [
679 caption,
680 captionHideButton,
681 displayVideo,
682 displayAudio,
683 captionText,
684 leftArrowBtn,
685 rightArrowBtn,
686 loadingIcon ];
687
688 // blur to hide close button focus style
689 target.blur();
690
691 // don't close if one of the clickEls was clicked or container is already closing
692 if (isClosing || ~clickEls.indexOf(target)) {
693 return
694 }
695
696 // animate closing
697 displayElement.style.cssText += getRect();
698 changeCSS(container, 'pointer-events:auto');
699
700 // timeout to remove els from dom; use variable to avoid calling more than once
701 setTimeout(removeContainer, 350);
702
703 // clear animationEnd timeout
704 clearTimeout(animationEnd);
705
706 isOpen = false;
707 isClosing = true;
708 }
709
710 // remove container / display element from the DOM
711 function removeContainer() {
712 // clear src of displayElement (or iframe if display el is iframe container)
713 // needs to be done before removing container in IE
714 var srcEl =
715 displayElement === iframeContainer ? iframeSiteVid : displayElement;
716 srcEl.removeAttribute('src');
717
718 // remove container from DOM & clear inline style
719 document.body[removeEl](container);
720 container[removeEl](displayElement);
721 changeCSS(container, '');
722 changeCSS(displayElement, '');
723
724 // remove caption
725 toggleCaption(false);
726
727 if (galleryOpen) {
728 // remove all gallery stuff
729 var images = container.querySelectorAll('img');
730 for (var i = 0; i < images.length; i++) {
731 container[removeEl](images[i]);
732 }
733 isLoading && container[removeEl](loadingIcon);
734 container[removeEl](galleryCounter);
735 galleryOpen = galleryEls = false;
736 preloadedImages = {};
737 supportsTouch || container[removeEl](rightArrowBtn);
738 supportsTouch || container[removeEl](leftArrowBtn);
739 // in case displayimage changed, we need to update event listeners
740 displayImage.onload = open;
741 displayImage.onerror = open.bind(null, 'image');
742 }
743
744 // run close callback
745 opts.onClose && opts.onClose();
746
747 isClosing = isLoading = false;
748 }
749
750 // style helper functions
751 function changeCSS(ref, newStyle) {
752 var style = ref.style;
753
754 style.cssText = newStyle;
755 }
756
757 return BigPicture;
758
759 }());
760