| 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 |
|