From 468ea24152ddab2adda5e1803a144e1af2a5d2e0 Mon Sep 17 00:00:00 2001 From: hank Date: Sun, 10 Nov 2019 19:02:24 -0500 Subject: [PATCH] 2.3.0 release - embed dimensions --- .npmignore | 1 - README.md | 4 +- dist/BigPicture.js | 512 +++++++------ dist/BigPicture.min.js | 2 +- example_page/index.html | 4 +- example_page/js/BigPicture.js | 2 +- index.js | 1297 +++++++++++++++++---------------- package.json | 2 +- src/BigPicture.js | 44 +- 9 files changed, 980 insertions(+), 888 deletions(-) diff --git a/.npmignore b/.npmignore index 4d443be..f37af83 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,4 @@ example_page -build.sh yarn.lock shrinkwrap.yaml pnpm-lock.yaml \ No newline at end of file diff --git a/README.md b/README.md index 9d8d294..1097f9d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Example page screenshot](https://i.imgur.com/7T6dnN3.gif) -Lightweight (3.75 KB gzip) and framework independent JavaScript image / video viewer. +3.7 kB gzip vanilla JavaScript image / video viewer. Supports Youtube, Vimeo, and direct video links. @@ -220,7 +220,7 @@ BigPicture({ ## Change dimensions of embed / youtube / vimeo -By default, embeds are displayed in 16:9 aspect at a maximum of 1600px by 900px. To change this, supply an array with width and height in pixels. Default is `[1600, 900]`. +By default, embeds are displayed in 16:9 aspect at a maximum of 1920px by 1080px. To change this, supply an array with width and height in pixels. Default is `[1920, 1080]`. ```javascript BigPicture({ diff --git a/dist/BigPicture.js b/dist/BigPicture.js index 63a4fab..b5c5204 100644 --- a/dist/BigPicture.js +++ b/dist/BigPicture.js @@ -1,89 +1,114 @@ -// BigPicture.js | license MIT | henrygd.me/bigpicture -(function() { - var // assign window object to variable - global = window, - // trigger element used to open popup - el, - // set to true after first interaction - initialized, - // container element holding html needed for script - container, - // currently active display element (image, video, youtube / vimeo iframe container) - displayElement, - // popup image element - displayImage, - // popup video element - displayVideo, - // popup audio element - displayAudio, - // container element to hold youtube / vimeo iframe - iframeContainer, - // iframe to hold youtube / vimeo player - iframeSiteVid, - // store requested image source - imgSrc, - // button that closes the container - closeButton, - // youtube / vimeo video id - siteVidID, - // keeps track of loading icon display state - isLoading, - // timeout to check video status while loading - checkMediaTimeout, - // loading icon element - loadingIcon, - // caption element - caption, - // caption content element - captionText, - // store caption content - captionContent, - // hide caption button element - captionHideButton, - // open state for container element - isOpen, - // gallery open state - galleryOpen, - // used during close animation to avoid triggering timeout twice - isClosing, - // array of prev viewed image urls to check if cached before showing loading icon - imgCache = [], - // store whether image requested is remote or local - remoteImage, - // store animation opening callbacks - animationStart, - animationEnd, - // store changeGalleryImage callback - onChangeImage, - // gallery left / right icons - rightArrowBtn, - leftArrowBtn, - // position of gallery - galleryPosition, - // hold active gallery els / image src - galleryEls, - // counter element - galleryCounter, - // store images in gallery that are being loaded - preloadedImages = {}, - // whether device supports touch events - supportsTouch, - // options object - opts, - // Save bytes in the minified version - doc = document, - appendEl = 'appendChild', - createEl = 'createElement', - removeEl = 'removeChild', - htmlInner = 'innerHTML', - pointerEventsAuto = 'pointer-events:auto', - cHeight = 'clientHeight', - cWidth = 'clientWidth', - listenFor = 'addEventListener', - timeout = global.setTimeout, - clearTimeout = global.clearTimeout; - - global.BigPicture = function(options) { +var BigPicture = (function () { + // BigPicture.js | license MIT | henrygd.me/bigpicture + + // trigger element used to open popup + var el; + + // set to true after first interaction + var initialized; + + // container element holding html needed for script + var container; + + // currently active display element (image, video, youtube / vimeo iframe container) + var displayElement; + + // popup image element + var displayImage; + + // popup video element + var displayVideo; + + // popup audio element + var displayAudio; + + // container element to hold youtube / vimeo iframe + var iframeContainer; + + // iframe to hold youtube / vimeo player + var iframeSiteVid; + + // store requested image source + var imgSrc; + + // button that closes the container + var closeButton; + + // youtube / vimeo video id + var siteVidID; + + // keeps track of loading icon display state + var isLoading; + + // timeout to check video status while loading + var checkMediaTimeout; + + // loading icon element + var loadingIcon; + + // caption element + var caption; + + // caption content element + var captionText; + + // store caption content + var captionContent; + + // hide caption button element + var captionHideButton; + + // open state for container element + var isOpen; + + // gallery open state + var galleryOpen; + + // used during close animation to avoid triggering timeout twice + var isClosing; + + // array of prev viewed image urls to check if cached before showing loading icon + var imgCache = []; + + // store whether image requested is remote or local + var remoteImage; + + // store animation opening callbacks + var animationStart; + var animationEnd; + + // store changeGalleryImage callback + var onChangeImage; + + // gallery left / right icons + var rightArrowBtn; + + var leftArrowBtn; + + // position of gallery + var galleryPosition; + + // hold active gallery els / image src + var galleryEls; + + // counter element + var galleryCounter; + + // store images in gallery that are being loaded + var preloadedImages = {}; + + // whether device supports touch events + var supportsTouch; + + // options object + var opts; + + // Save bytes in the minified version + var appendEl = 'appendChild'; + var createEl = 'createElement'; + var removeEl = 'removeChild'; + + function BigPicture (options) { // initialize called on initial open to create elements / style / event handlers initialized || initialize(); @@ -116,7 +141,7 @@ makeGallery(options.gallery, options.position); } else if (siteVidID || options.iframeSrc) { // if vimeo, youtube, or iframe video - toggleLoadingIcon(true); + // toggleLoadingIcon(true) displayElement = iframeContainer; createIframe(); } else if (options.imgSrc) { @@ -144,68 +169,71 @@ displayElement.src = el.tagName === 'IMG' ? el.src - : global + : window .getComputedStyle(el) .backgroundImage.replace(/^url|[(|)|'|"]/g, ''); } // add container to page container[appendEl](displayElement); - doc.body[appendEl](container); - }; + document.body[appendEl](container); + } // create all needed methods / store dom elements on first use function initialize() { var startX; // return close button elements function createCloseButton(className) { - var el = doc[createEl]('button'); + var el = document[createEl]('button'); el.className = className; - el[htmlInner] = - ''; - return el; + el.innerHTML = + ''; + return el } function createArrowSymbol(direction, style) { - var el = doc[createEl]('button'); + var el = document[createEl]('button'); el.className = 'bp-lr'; - el[htmlInner] = - ''; + el.innerHTML = + ''; changeCSS(el, style); - el.onclick = function(e) { + el.onclick = function (e) { e.stopPropagation(); updateGallery(direction); }; - return el; + return el } // add style - if you want to tweak, run through beautifier - var style = doc[createEl]('STYLE'); - style[htmlInner] = - '#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:rgba(0,0,0,.7);opacity:0;transition:opacity .35s}#bp_loader{top:0;left:0;bottom:0;display:flex;margin:0;cursor:wait;z-index:9;background:0 0}#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{height:0;padding-bottom:54%;background-color:#000;width:96%}#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 0;cursor:pointer;transition:all .1s}#bp_container>.bp-x{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 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}}@media (min-aspect-ratio:9/5){#bp_sv{height:98%;width:170.6vh;padding:0}}'; - doc.head[appendEl](style); + var style = document[createEl]('STYLE'); + style.innerHTML = + '#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:rgba(0,0,0,.7);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{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}}'; + document.head[appendEl](style); // create container element - container = doc[createEl]('DIV'); + container = document[createEl]('DIV'); container.id = 'bp_container'; container.onclick = close; closeButton = createCloseButton('bp-x'); container[appendEl](closeButton); // gallery swipe listeners - if ('ontouchstart' in global) { + if ('ontouchstart' in window) { supportsTouch = true; - container.ontouchstart = function(e) { - startX = e.changedTouches[0].pageX; + container.ontouchstart = function (ref) { + var changedTouches = ref.changedTouches; + + startX = changedTouches[0].pageX; }; - container.ontouchmove = function(e) { + container.ontouchmove = function (e) { e.preventDefault(); }; - container.ontouchend = function(e) { + container.ontouchend = function (ref) { + var changedTouches = ref.changedTouches; + if (!galleryOpen) { - return; + return } - var touchobj = e.changedTouches[0]; - var distX = touchobj.pageX - startX; + var distX = changedTouches[0].pageX - startX; // swipe right distX < -30 && updateGallery(1); // swipe left @@ -214,32 +242,32 @@ } // create display image element - displayImage = doc[createEl]('IMG'); + displayImage = document[createEl]('IMG'); // create display video element - displayVideo = doc[createEl]('VIDEO'); + displayVideo = document[createEl]('VIDEO'); displayVideo.id = 'bp_vid'; displayVideo.setAttribute('playsinline', true); displayVideo.controls = true; displayVideo.loop = true; // create audio element - displayAudio = doc[createEl]('audio'); + displayAudio = document[createEl]('audio'); displayAudio.id = 'bp_aud'; displayAudio.controls = true; displayAudio.loop = true; // create gallery counter - galleryCounter = doc[createEl]('span'); + galleryCounter = document[createEl]('span'); galleryCounter.id = 'bp_count'; // create caption elements - caption = doc[createEl]('DIV'); + caption = document[createEl]('DIV'); caption.id = 'bp_caption'; captionHideButton = createCloseButton('bp-xc'); captionHideButton.onclick = toggleCaption.bind(null, false); caption[appendEl](captionHideButton); - captionText = doc[createEl]('SPAN'); + captionText = document[createEl]('SPAN'); caption[appendEl](captionText); container[appendEl](caption); @@ -248,19 +276,19 @@ leftArrowBtn = createArrowSymbol(-1, 'left:0;right:auto'); // create loading icon element - loadingIcon = doc[createEl]('DIV'); + loadingIcon = document[createEl]('DIV'); loadingIcon.id = 'bp_loader'; - loadingIcon[htmlInner] = - ''; + loadingIcon.innerHTML = + ''; // create youtube / vimeo container - iframeContainer = doc[createEl]('DIV'); + iframeContainer = document[createEl]('DIV'); iframeContainer.id = 'bp_sv'; // create iframe to hold youtube / vimeo player - iframeSiteVid = doc[createEl]('IFRAME'); + iframeSiteVid = document[createEl]('IFRAME'); iframeSiteVid.setAttribute('allowfullscreen', true); iframeSiteVid.allow = 'autoplay; fullscreen'; - iframeSiteVid.onload = open; + iframeSiteVid.onload = function () { return iframeContainer[removeEl](loadingIcon); }; changeCSS( iframeSiteVid, 'border:0;position:absolute;height:100%;width:100%;left:0;top:0' @@ -271,24 +299,27 @@ displayImage.onload = open; displayImage.onerror = open.bind(null, 'image'); - // adjust loader position on window resize - global[listenFor]('resize', function() { + window.addEventListener('resize', function () { + // adjust loader position on window resize galleryOpen || (isLoading && toggleLoadingIcon(true)); + // adjust iframe dimensions + displayElement === iframeContainer && updateIframeDimensions(); }); // close container on escape key press and arrow buttons for gallery - doc[listenFor]('keyup', function(e) { - var key = e.keyCode; - key === 27 && isOpen && close(container); + document.addEventListener('keyup', function (ref) { + var keyCode = ref.keyCode; + + keyCode === 27 && isOpen && close(container); if (galleryOpen) { - key === 39 && updateGallery(1); - key === 37 && updateGallery(-1); - key === 38 && updateGallery(10); - key === 40 && updateGallery(-10); + keyCode === 39 && updateGallery(1); + keyCode === 37 && updateGallery(-1); + keyCode === 38 && updateGallery(10); + keyCode === 40 && updateGallery(-10); } }); // prevent scrolling with arrow keys if gallery open - doc[listenFor]('keydown', function(e) { + document.addEventListener('keydown', function (e) { var usedKeys = [37, 38, 39, 40]; if (galleryOpen && ~usedKeys.indexOf(e.keyCode)) { e.preventDefault(); @@ -296,9 +327,9 @@ }); // trap focus within conainer while open - doc[listenFor]( + document.addEventListener( 'focus', - function(e) { + function (e) { if (isOpen && !container.contains(e.target)) { e.stopPropagation(); closeButton.focus(); @@ -313,31 +344,25 @@ // return transform style to make full size display el match trigger el size function getRect() { - var rect = el.getBoundingClientRect(); - var leftOffset = rect.left - (container[cWidth] - rect.width) / 2; - var centerTop = rect.top - (container[cHeight] - rect.height) / 2; - var scaleWidth = el[cWidth] / displayElement[cWidth]; - var scaleHeight = el[cHeight] / displayElement[cHeight]; - return ( - 'transform:translate3D(' + - leftOffset + - 'px, ' + - centerTop + - 'px, 0) scale3D(' + - scaleWidth + - ', ' + - scaleHeight + - ', 0)' - ); + var ref = el.getBoundingClientRect(); + var top = ref.top; + var left = ref.left; + var width = ref.width; + var height = ref.height; + var leftOffset = left - (container.clientWidth - width) / 2; + var centerTop = top - (container.clientHeight - height) / 2; + var scaleWidth = el.clientWidth / displayElement.clientWidth; + var scaleHeight = el.clientHeight / displayElement.clientHeight; + return ("transform:translate3D(" + leftOffset + "px, " + centerTop + "px, 0) scale3D(" + scaleWidth + ", " + scaleHeight + ", 0)") } function makeVidSrc(source) { if (Array.isArray(source)) { displayElement = displayVideo.cloneNode(); - source.forEach(function(src) { - var source = doc[createEl]('SOURCE'); + source.forEach(function (src) { + var source = document[createEl]('SOURCE'); source.src = src; - source.type = 'video/' + src.match(/.(\w+)$/)[1]; + source.type = "video/" + (src.match(/.(\w+)$/)[1]); displayElement[appendEl](source); }); } else { @@ -356,20 +381,19 @@ // is element selector or nodelist galleryEls = [].slice.call( typeof gallery === 'string' - ? doc.querySelectorAll(gallery + ' [data-bp]') + ? document.querySelectorAll((gallery + " [data-bp]")) : gallery ); // find initial gallery position var elIndex = galleryEls.indexOf(el); - galleryPosition = (position === 0 || position) ? position : (elIndex !== -1 ? elIndex : 0); + galleryPosition = + position === 0 || position ? position : elIndex !== -1 ? elIndex : 0; // make gallery object w/ els / src / caption - galleryEls = galleryEls.map(function(el) { - return { - el: el, - src: el.getAttribute('data-bp'), - caption: el.getAttribute('data-caption'), - }; - }); + galleryEls = galleryEls.map(function (el) { return ({ + el: el, + src: el.getAttribute('data-bp'), + caption: el.getAttribute('data-caption'), + }); }); } // show loading icon if needed remoteImage = true; @@ -379,7 +403,7 @@ if (galleryEls.length > 1) { // if length is greater than one, add gallery stuff container[appendEl](galleryCounter); - galleryCounter[htmlInner] = galleryPosition + 1 + '/' + galleryEls.length; + galleryCounter.innerHTML = (galleryPosition + 1) + "/" + (galleryEls.length); if (!supportsTouch) { // add arrows if device doesn't support touch container[appendEl](rightArrowBtn); @@ -399,7 +423,7 @@ // only allow one change at a time if (isLoading) { - return; + return } // return if requesting out of range image @@ -410,15 +434,13 @@ // if beginning or end of gallery, run end animation if (!opts.loop) { changeCSS(displayImage, ''); - timeout( + setTimeout( changeCSS, 9, displayImage, - 'animation:' + - (movement > 0 ? 'bpl' : 'bpf') + - ' .3s;transition:transform .35s' + ("animation:" + (movement > 0 ? 'bpl' : 'bpf') + " .3s;transition:transform .35s") ); - return; + return } // if gallery is looped, adjust position to beginning / end galleryPosition = movement > 0 ? -1 : galleryLength + 1; @@ -428,36 +450,36 @@ galleryPosition = Math.max( 0, Math.min(galleryPosition + movement, galleryLength) - ); + ) // load images before and after for quicker scrolling through pictures - [galleryPosition - 1, galleryPosition, galleryPosition + 1].forEach( - function(position) { + ;[galleryPosition - 1, galleryPosition, galleryPosition + 1].forEach( + function (position) { // normalize position position = Math.max(0, Math.min(position, galleryLength)); // cancel if image has already been preloaded - if (preloadedImages[position]) return; + if (preloadedImages[position]) { return } var src = galleryEls[position].src; // create image for preloadedImages - var img = doc[createEl]('IMG'); - img[listenFor]('load', addToImgCache.bind(null, src)); + var img = document[createEl]('IMG'); + img.addEventListener('load', addToImgCache.bind(null, src)); img.src = src; preloadedImages[position] = img; } ); // if image is loaded, show it if (preloadedImages[galleryPosition].complete) { - return changeGalleryImage(movement); + return changeGalleryImage(movement) } // if not, show loading icon and change when loaded isLoading = true; changeCSS(loadingIcon, 'opacity:.4;'); container[appendEl](loadingIcon); - preloadedImages[galleryPosition].onload = function() { + preloadedImages[galleryPosition].onload = function () { galleryOpen && changeGalleryImage(movement); }; // if error, store error object in el array - preloadedImages[galleryPosition].onerror = function() { + preloadedImages[galleryPosition].onerror = function () { galleryEls[galleryPosition] = { error: 'Error loading image', }; @@ -480,14 +502,9 @@ displayImage = displayElement = preloadedImages[galleryPosition]; changeCSS( displayImage, - 'animation:' + - (movement > 0 ? 'bpfl' : 'bpfr') + - ' .35s;transition:transform .35s' - ); - changeCSS( - oldimg, - 'animation:' + (movement > 0 ? 'bpfol' : 'bpfor') + ' .35s both' + ("animation:" + (movement > 0 ? 'bpfl' : 'bpfr') + " .35s;transition:transform .35s") ); + changeCSS(oldimg, ("animation:" + (movement > 0 ? 'bpfol' : 'bpfor') + " .35s both")); container[appendEl](displayImage); // update el for closing animation if (activeEl.el) { @@ -495,7 +512,7 @@ } } // update counter - galleryCounter[htmlInner] = galleryPosition + 1 + '/' + galleryEls.length; + galleryCounter.innerHTML = (galleryPosition + 1) + "/" + (galleryEls.length); // show / hide caption toggleCaption(galleryEls[galleryPosition].caption); // execute onChangeImage callback @@ -510,20 +527,49 @@ // create appropriate url if (opts.ytSrc) { - url = - prefix + - 'www.youtube.com/embed/' + - siteVidID + - '?html5=1&rel=0&playsinline=1&' + - suffix; + url = prefix + "www.youtube.com/embed/" + siteVidID + "?html5=1&rel=0&playsinline=1&" + suffix; } else if (opts.vimeoSrc) { - url = prefix + 'player.vimeo.com/video/' + siteVidID + '?' + suffix; + url = prefix + "player.vimeo.com/video/" + siteVidID + "?" + suffix; } else if (opts.iframeSrc) { url = opts.iframeSrc; } + // add loading spinner to iframe container + changeCSS(loadingIcon, ''); + iframeContainer[appendEl](loadingIcon); + // set iframe src to url iframeSiteVid.src = url; + + updateIframeDimensions(); + + setTimeout(open, 9); + } + + function updateIframeDimensions() { + var height; + var width; + + // handle height / width / aspect / max width for iframe + var windowHeight = window.innerHeight * 0.95; + var windowWidth = window.innerWidth * 0.95; + var windowAspect = windowHeight / windowWidth; + + var ref = opts.dimensions || [1920, 1080]; + var dimensionWidth = ref[0]; + var dimensionHeight = ref[1]; + + var iframeAspect = dimensionHeight / dimensionWidth; + + if (iframeAspect > windowAspect) { + height = Math.min(dimensionHeight, windowHeight); + width = height / iframeAspect; + } else { + width = Math.min(dimensionWidth, windowWidth); + height = width * iframeAspect; + } + + iframeContainer.style.cssText += "width:" + width + "px;height:" + height + "px;"; } // timeout to check video status while loading @@ -531,31 +577,28 @@ if (~[1, 4].indexOf(displayElement.readyState)) { open(); // short timeout to to make sure controls show in safari 11 - timeout(function() { + setTimeout(function () { displayElement.play(); }, 99); - } else if (displayElement.error) open(errMsg); - else checkMediaTimeout = timeout(checkMedia, 35, errMsg); + } else if (displayElement.error) { + open(errMsg); + } else { + checkMediaTimeout = setTimeout(checkMedia, 35, errMsg); + } } // hide / show loading icon function toggleLoadingIcon(bool) { // don't show loading icon if noLoader is specified - if (opts.noLoader) return; + if (opts.noLoader) { + return + } // bool is true if we want to show icon, false if we want to remove // change style to match trigger element dimensions if we want to show bool && changeCSS( loadingIcon, - 'top:' + - el.offsetTop + - 'px;left:' + - el.offsetLeft + - 'px;height:' + - el[cHeight] + - 'px;width:' + - el[cWidth] + - 'px' + ("top:" + (el.offsetTop) + "px;left:" + (el.offsetLeft) + "px;height:" + (el.clientHeight) + "px;width:" + (el.clientWidth) + "px") ); // add or remove loader from DOM el.parentElement[bool ? appendEl : removeEl](loadingIcon); @@ -565,11 +608,11 @@ // hide & show caption function toggleCaption(captionContent) { if (captionContent) { - captionText[htmlInner] = captionContent; + captionText.innerHTML = captionContent; } changeCSS( caption, - 'opacity:' + (captionContent ? '1;' + pointerEventsAuto : '0') + ("opacity:" + (captionContent ? "1;pointer-events:auto" : '0')) ); } @@ -590,29 +633,29 @@ removeContainer(); return opts.onError ? opts.onError() - : alert('Error: The requested ' + err + ' could not be loaded.'); + : alert(("Error: The requested " + err + " could not be loaded.")) } // if remote image is loaded, add url to imgCache array remoteImage && addToImgCache(imgSrc); // transform displayEl to match trigger el - changeCSS(displayElement, getRect()); + displayElement.style.cssText += getRect(); // fade in container - changeCSS(container, 'opacity:1;' + pointerEventsAuto); + changeCSS(container, "opacity:1;pointer-events:auto"); // set animationEnd callback to run after animation ends (cleared if container closed) - animationEnd = timeout(animationEnd, 410); + animationEnd = setTimeout(animationEnd, 410); isOpen = true; galleryOpen = !!galleryEls; // enlarge displayEl, fade in caption if hasCaption - timeout(function() { - changeCSS(displayElement, 'transition:transform .35s;transform:none'); - captionContent && timeout(toggleCaption, 250, captionContent); + setTimeout(function () { + displayElement.style.cssText += 'transition:transform .35s;transform:none'; + captionContent && setTimeout(toggleCaption, 250, captionContent); }, 60); } @@ -627,23 +670,22 @@ captionText, leftArrowBtn, rightArrowBtn, - loadingIcon, - ]; + loadingIcon ]; // blur to hide close button focus style target && target.blur(); // don't close if one of the clickEls was clicked or container is already closing if (isClosing || ~clickEls.indexOf(target)) { - return; + return } // animate closing displayElement.style.cssText += getRect(); - changeCSS(container, pointerEventsAuto); + changeCSS(container, 'pointer-events:auto'); // timeout to remove els from dom; use variable to avoid calling more than once - timeout(removeContainer, 350); + setTimeout(removeContainer, 350); // clear animationEnd timeout clearTimeout(animationEnd); @@ -654,16 +696,17 @@ // remove container / display element from the DOM function removeContainer() { + // clear src of displayElement (or iframe if display el is iframe container) + // needs to be done before removing container in IE + var srcEl = + displayElement === iframeContainer ? iframeSiteVid : displayElement; + srcEl.removeAttribute('src'); + // remove container from DOM & clear inline style - doc.body[removeEl](container); + document.body[removeEl](container); container[removeEl](displayElement); changeCSS(container, ''); - - // clear src of displayElement (or iframe if display el is iframe container) - (displayElement === iframeContainer - ? iframeSiteVid - : displayElement - ).removeAttribute('src'); + changeCSS(displayElement, ''); // remove caption toggleCaption(false); @@ -692,7 +735,12 @@ } // style helper functions - function changeCSS(element, newStyle) { - element.style.cssText = newStyle; + function changeCSS(ref, newStyle) { + var style = ref.style; + + style.cssText = newStyle; } -})(); + + return BigPicture; + +}()); diff --git a/dist/BigPicture.min.js b/dist/BigPicture.min.js index 8a836e6..6a3951b 100644 --- a/dist/BigPicture.min.js +++ b/dist/BigPicture.min.js @@ -1 +1 @@ -!function(){var r,e,a,i,p,c,l,s,f,d,u,b,g,o,m,h,x,v,y,w,_,k,S,n,A,E,M,z,I,C,T,D,L,O=window,N=[],V={},q=document,B="appendChild",P="createElement",G="removeChild",R="innerHTML",H="pointer-events:auto",X="clientHeight",F="clientWidth",U="addEventListener",W=O.setTimeout,Y=O.clearTimeout;function $(){var t=r.getBoundingClientRect();return"transform:translate3D("+(t.left-(a[F]-t.width)/2)+"px, "+(t.top-(a[X]-t.height)/2)+"px, 0) scale3D("+r[F]/i[F]+", "+r[X]/i[X]+", 0)"}function j(t){var r=C.length-1;if(!g){if(0',o}function o(o,t){var n=q[P]("button");return n.className="bp-lr",n[R]='',et(n,t),n.onclick=function(t){t.stopPropagation(),j(o)},n}var r=q[P]("STYLE");r[R]="#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:rgba(0,0,0,.7);opacity:0;transition:opacity .35s}#bp_loader{top:0;left:0;bottom:0;display:flex;margin:0;cursor:wait;z-index:9;background:0 0}#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{height:0;padding-bottom:54%;background-color:#000;width:96%}#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 0;cursor:pointer;transition:all .1s}#bp_container>.bp-x{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 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}}@media (min-aspect-ratio:9/5){#bp_sv{height:98%;width:170.6vh;padding:0}}",q.head[B](r),(a=q[P]("DIV")).id="bp_container",a.onclick=nt,u=t("bp-x"),a[B](u),"ontouchstart"in O&&(D=!0,a.ontouchstart=function(t){n=t.changedTouches[0].pageX},a.ontouchmove=function(t){t.preventDefault()},a.ontouchend=function(t){if(_){var o=t.changedTouches[0].pageX-n;o<-30&&j(1),30',(s=q[P]("DIV")).id="bp_sv",(f=q[P]("IFRAME")).setAttribute("allowfullscreen",!0),f.allow="autoplay; fullscreen",f.onload=ot,et(f,"border:0;position:absolute;height:100%;width:100%;left:0;top:0"),s[B](f),p.onload=ot,p.onerror=ot.bind(null,"image"),O[U]("resize",function(){_||g&&Q(!0)}),q[U]("keyup",function(t){var o=t.keyCode;27===o&&w&&nt(a),_&&(39===o&&j(1),37===o&&j(-1),38===o&&j(10),40===o&&j(-10))}),q[U]("keydown",function(t){_&&~[37,38,39,40].indexOf(t.keyCode)&&t.preventDefault()}),q[U]("focus",function(t){w&&!a.contains(t.target)&&(t.stopPropagation(),u.focus())},!0),e=!0}(),g&&(Y(o),rt()),b=(L=t).ytSrc||t.vimeoSrc,n=t.animationStart,A=t.animationEnd,E=t.onChangeImage,r=t.el,S=!1,v=r.getAttribute("data-caption"),t.gallery?function(t,o){if(Array.isArray(t))v=(C=t)[I=o||0].caption;else{var n=(C=[].slice.call("string"==typeof t?q.querySelectorAll(t+" [data-bp]"):t)).indexOf(r);I=0===o||o?o:-1!==n?n:0,C=C.map(function(t){return{el:t,src:t.getAttribute("data-bp"),caption:t.getAttribute("data-caption")}})}S=!0,d=C[I].src,~N.indexOf(d)||Q(!0),10&&E===n||t<0&&!E){if(!I.loop)return j(i,""),void setTimeout(j,9,i,"animation:"+(t>0?"bpl":"bpf")+" .3s;transition:transform .35s");E=t>0?-1:n+1}if([(E=Math.max(0,Math.min(E+t,n)))-1,E,E+1].forEach(function(t){if(t=Math.max(0,Math.min(t,n)),!D[t]){var e=A[t].src,o=document[N]("IMG");o.addEventListener("load",F.bind(null,e)),o.src=e,D[t]=o}}),D[E].complete)return B(t);u=1,j(m,"opacity:.4;"),e[O](m),D[E].onload=function(){y&&B(t)},D[E].onerror=function(){A[E]={error:"Error loading image"},y&&B(t)}}}function B(n){u&&(e[V](m),u=0);var r=A[E];if(r.error)alert(r.error);else{var a=e.querySelector("img:last-of-type");j(i=o=D[E],"animation:"+(n>0?"bpfl":"bpfr")+" .35s;transition:transform .35s"),j(a,"animation:"+(n>0?"bpfol":"bpfor")+" .35s both"),e[O](i),r.el&&(t=r.el)}H.innerHTML=E+1+"/"+A.length,X(A[E].caption),M&&M([i,A[E]])}function P(){var t,n,e=.95*window.innerHeight,o=.95*window.innerWidth,i=I.dimensions||[1920,1080],r=i[0],a=i[1],p=a/r;p>e/o?n=(t=Math.min(a,e))/p:t=(n=Math.min(r,o))*p,c.style.cssText+="width:"+n+"px;height:"+t+"px;"}function G(t){~[1,4].indexOf(o.readyState)?(U(),setTimeout(function(){o.play()},99)):o.error?U(t):f=setTimeout(G,35,t)}function R(n){I.noLoader||(n&&j(m,"top:"+t.offsetTop+"px;left:"+t.offsetLeft+"px;height:"+t.clientHeight+"px;width:"+t.clientWidth+"px"),t.parentElement[n?O:V](m),u=n)}function X(t){t&&(g.innerHTML=t),j(b,"opacity:"+(t?"1;pointer-events:auto":"0"))}function F(t){!~C.indexOf(t)&&C.push(t)}function U(t){if(u&&R(),T&&T(),"string"==typeof t)return $(),I.onError?I.onError():alert("Error: The requested "+t+" could not be loaded.");_&&F(s),o.style.cssText+=W(),j(e,"opacity:1;pointer-events:auto"),k=setTimeout(k,410),v=1,y=!!A,setTimeout(function(){o.style.cssText+="transition:transform .35s;transform:none",h&&setTimeout(X,250,h)},60)}function Y(t){var n=t.target,i=[b,x,r,a,g,L,S,m];n&&n.blur(),w||~i.indexOf(n)||(o.style.cssText+=W(),j(e,"pointer-events:auto"),setTimeout($,350),clearTimeout(k),v=0,w=1)}function $(){if((o===c?p:o).removeAttribute("src"),document.body[V](e),e[V](o),j(e,""),j(o,""),X(0),y){for(var t=e.querySelectorAll("img"),n=0;n',n}function d(t,n){var e=document[N]("button");return e.className="bp-lr",e.innerHTML='',j(e,n),e.onclick=function(n){n.stopPropagation(),q(t)},e}var f=document[N]("STYLE");f.innerHTML="#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:rgba(0,0,0,.7);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{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}}",document.head[O](f),(e=document[N]("DIV")).id="bp_container",e.onclick=Y,l=s("bp-x"),e[O](l),"ontouchstart"in window&&(z=1,e.ontouchstart=function(n){var e=n.changedTouches;t=e[0].pageX},e.ontouchmove=function(t){t.preventDefault()},e.ontouchend=function(n){var e=n.changedTouches;if(y){var o=e[0].pageX-t;o<-30&&q(1),o>30&&q(-1)}}),i=document[N]("IMG"),(r=document[N]("VIDEO")).id="bp_vid",r.setAttribute("playsinline",1),r.controls=1,r.loop=1,(a=document[N]("audio")).id="bp_aud",a.controls=1,a.loop=1,(H=document[N]("span")).id="bp_count",(b=document[N]("DIV")).id="bp_caption",(x=s("bp-xc")).onclick=X.bind(null,0),b[O](x),g=document[N]("SPAN"),b[O](g),e[O](b),S=d(1,"transform:scalex(-1)"),L=d(-1,"left:0;right:auto"),(m=document[N]("DIV")).id="bp_loader",m.innerHTML='',(c=document[N]("DIV")).id="bp_sv",(p=document[N]("IFRAME")).setAttribute("allowfullscreen",1),p.allow="autoplay; fullscreen",p.onload=function(){return c[V](m)},j(p,"border:0;position:absolute;height:100%;width:100%;left:0;top:0"),c[O](p),i.onload=U,i.onerror=U.bind(null,"image"),window.addEventListener("resize",function(){y||u&&R(1),o===c&&P()}),document.addEventListener("keyup",function(t){var n=t.keyCode;27===n&&v&&Y(e),y&&(39===n&&q(1),37===n&&q(-1),38===n&&q(10),40===n&&q(-10))}),document.addEventListener("keydown",function(t){y&&~[37,38,39,40].indexOf(t.keyCode)&&t.preventDefault()}),document.addEventListener("focus",function(t){v&&!e.contains(t.target)&&(t.stopPropagation(),l.focus())},1),n=1}(),u&&(clearTimeout(f),$()),I=w,d=w.ytSrc||w.vimeoSrc,T=w.animationStart,k=w.animationEnd,M=w.onChangeImage,_=0,h=(t=w.el).getAttribute("data-caption"),w.gallery?function(n,r){if(Array.isArray(n))A=n,h=n[E=r||0].caption;else{var a=(A=[].slice.call("string"==typeof n?document.querySelectorAll(n+" [data-bp]"):n)).indexOf(t);E=0===r||r?r:-1!==a?a:0,A=A.map(function(t){return{el:t,src:t.getAttribute("data-bp"),caption:t.getAttribute("data-caption")}})}_=1,!~C.indexOf(s=A[E].src)&&R(1),A.length>1?(e[O](H),H.innerHTML=E+1+"/"+A.length,z||(e[O](S),e[O](L))):A=0,(o=i).src=s}(w.gallery,w.position):d||w.iframeSrc?(o=c,I.ytSrc?W="https://www.youtube.com/embed/"+d+"?html5=1&rel=0&playsinline=1&autoplay=1":I.vimeoSrc?W="https://player.vimeo.com/video/"+d+"?autoplay=1":I.iframeSrc&&(W=I.iframeSrc),j(m,""),c[O](m),p.src=W,P(),setTimeout(U,9)):w.imgSrc?(_=1,!~C.indexOf(s=w.imgSrc)&&R(1),(o=i).src=s):w.audio?(R(1),(o=a).src=w.audio,G("audio file")):w.vidSrc?(R(1),D=w.vidSrc,Array.isArray(D)?(o=r.cloneNode(),D.forEach(function(t){var n=document[N]("SOURCE");n.src=t,n.type="video/"+t.match(/.(\w+)$/)[1],o[O](n)})):(o=r).src=D,G("video")):(o=i).src="IMG"===t.tagName?t.src:window.getComputedStyle(t).backgroundImage.replace(/^url|[(|)|'|"]/g,""),e[O](o),document.body[O](e);var W}}(); diff --git a/example_page/index.html b/example_page/index.html index ff31a55..f01fed3 100644 --- a/example_page/index.html +++ b/example_page/index.html @@ -80,7 +80,7 @@

Videos / Embeds

>any iframe, as well as Error Handling e.preventDefault() BigPicture({ el: this, - audio: '/audio/happy-step.mp3', + audio: 'audio/happy-step.mp3', }) }) })() diff --git a/example_page/js/BigPicture.js b/example_page/js/BigPicture.js index ee5517f..3b993d8 100644 --- a/example_page/js/BigPicture.js +++ b/example_page/js/BigPicture.js @@ -1,2 +1,2 @@ -var BigPicture=function(){var t,n,e,o,i,r,a,c,p,s,l,d,u,f,m,b,g,h,x,v,y,w,_,T,k,M,S,L,E,A,H,z,I,C=[],D={},O="appendChild",N="createElement",V="removeChild";function W(){var n=t.getBoundingClientRect();return"transform:translate3D("+(n.left-(e.clientWidth-n.width)/2)+"px, "+(n.top-(e.clientHeight-n.height)/2)+"px, 0) scale3D("+t.clientWidth/o.clientWidth+", "+t.clientHeight/o.clientHeight+", 0)"}function q(t){var n=A.length-1;if(!u){if(t>0&&E===n||t<0&&!E){if(!I.loop)return j(i,""),void setTimeout(j,9,i,"animation:"+(t>0?"bpl":"bpf")+" .3s;transition:transform .35s");E=t>0?-1:n+1}if([(E=Math.max(0,Math.min(E+t,n)))-1,E,E+1].forEach(function(t){if(t=Math.max(0,Math.min(t,n)),!D[t]){var e=A[t].src,o=document[N]("IMG");o.addEventListener("load",F.bind(null,e)),o.src=e,D[t]=o}}),D[E].complete)return B(t);u=1,j(m,"opacity:.4;"),e[O](m),D[E].onload=function(){y&&B(t)},D[E].onerror=function(){A[E]={error:"Error loading image"},y&&B(t)}}}function B(n){u&&(e[V](m),u=0);var r=A[E];if(r.error)alert(r.error);else{var a=e.querySelector("img:last-of-type");j(i=o=D[E],"animation:"+(n>0?"bpfl":"bpfr")+" .35s;transition:transform .35s"),j(a,"animation:"+(n>0?"bpfol":"bpfor")+" .35s both"),e[O](i),r.el&&(t=r.el)}H.innerHTML=E+1+"/"+A.length,X(A[E].caption),M&&M([i,A[E]])}function P(){var t,n,e=.95*window.innerHeight,o=.95*window.innerWidth,i=I.dimensions||[1600,900];i[1]/i[0]>e/o?(t=Math.min(i[1],e),n=i[0]/i[1]*t):(n=Math.min(i[0],o),t=i[1]/i[0]*n),c.style.cssText+="width:"+n+"px;height:"+t+"px;"}function G(t){~[1,4].indexOf(o.readyState)?(U(),setTimeout(function(){o.play()},99)):o.error?U(t):f=setTimeout(G,35,t)}function R(n){I.noLoader||(n&&j(m,"top:"+t.offsetTop+"px;left:"+t.offsetLeft+"px;height:"+t.clientHeight+"px;width:"+t.clientWidth+"px"),t.parentElement[n?O:V](m),u=n)}function X(t){t&&(g.innerHTML=t),j(b,"opacity:"+(t?"1;pointer-events:auto":"0"))}function F(t){!~C.indexOf(t)&&C.push(t)}function U(t){if(u&&R(),T&&T(),"string"==typeof t)return $(),I.onError?I.onError():alert("Error: The requested "+t+" could not be loaded.");_&&F(s),o.style.cssText+=W(),j(e,"opacity:1;pointer-events:auto"),k=setTimeout(k,410),v=1,y=!!A,setTimeout(function(){o.style.cssText+="transition:transform .35s;transform:none",h&&setTimeout(X,250,h)},60)}function Y(t){var n=t.target,i=[b,x,r,a,g,L,S,m];n&&n.blur(),w||~i.indexOf(n)||(o.style.cssText+=W(),j(e,"pointer-events:auto"),setTimeout($,350),clearTimeout(k),v=0,w=1)}function $(){if(document.body[V](e),e[V](o),j(e,""),j(o,""),(o===c?p:o).removeAttribute("src"),X(0),y){for(var t=e.querySelectorAll("img"),n=0;n',n}function d(t,n){var e=document[N]("button");return e.className="bp-lr",e.innerHTML='',j(e,n),e.onclick=function(n){n.stopPropagation(),q(t)},e}var f=document[N]("STYLE");f.innerHTML="#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:rgba(0,0,0,.7);opacity:0;transition:opacity .35s}#bp_loader{top:0;left:0;bottom:0;display:flex;align-items:center;cursor:wait;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:80px}#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 0;cursor:pointer;transition:all .1s}#bp_container>.bp-x{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 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}}",document.head[O](f),(e=document[N]("DIV")).id="bp_container",e.onclick=Y,l=s("bp-x"),e[O](l),"ontouchstart"in window&&(z=1,e.ontouchstart=function(n){var e=n.changedTouches;t=e[0].pageX},e.ontouchmove=function(t){t.preventDefault()},e.ontouchend=function(n){var e=n.changedTouches;if(y){var o=e[0].pageX-t;o<-30&&q(1),o>30&&q(-1)}}),i=document[N]("IMG"),(r=document[N]("VIDEO")).id="bp_vid",r.setAttribute("playsinline",1),r.controls=1,r.loop=1,(a=document[N]("audio")).id="bp_aud",a.controls=1,a.loop=1,(H=document[N]("span")).id="bp_count",(b=document[N]("DIV")).id="bp_caption",(x=s("bp-xc")).onclick=X.bind(null,0),b[O](x),g=document[N]("SPAN"),b[O](g),e[O](b),S=d(1,"transform:scalex(-1)"),L=d(-1,"left:0;right:auto"),(m=document[N]("DIV")).id="bp_loader",m.innerHTML='',(c=document[N]("DIV")).id="bp_sv",(p=document[N]("IFRAME")).setAttribute("allowfullscreen",1),p.allow="autoplay; fullscreen",p.onload=function(){return c[V](m)},j(p,"border:0;position:absolute;height:100%;width:100%;left:0;top:0"),c[O](p),i.onload=U,i.onerror=U.bind(null,"image"),window.addEventListener("resize",function(){y||u&&R(1),o===c&&P()}),document.addEventListener("keyup",function(t){var n=t.keyCode;27===n&&v&&Y(e),y&&(39===n&&q(1),37===n&&q(-1),38===n&&q(10),40===n&&q(-10))}),document.addEventListener("keydown",function(t){y&&~[37,38,39,40].indexOf(t.keyCode)&&t.preventDefault()}),document.addEventListener("focus",function(t){v&&!e.contains(t.target)&&(t.stopPropagation(),l.focus())},1),n=1}(),u&&(clearTimeout(f),$()),I=w,d=w.ytSrc||w.vimeoSrc,T=w.animationStart,k=w.animationEnd,M=w.onChangeImage,_=0,h=(t=w.el).getAttribute("data-caption"),w.gallery?function(n,r){if(Array.isArray(n))A=n,h=n[E=r||0].caption;else{var a=(A=[].slice.call("string"==typeof n?document.querySelectorAll(n+" [data-bp]"):n)).indexOf(t);E=0===r||r?r:-1!==a?a:0,A=A.map(function(t){return{el:t,src:t.getAttribute("data-bp"),caption:t.getAttribute("data-caption")}})}_=1,!~C.indexOf(s=A[E].src)&&R(1),A.length>1?(e[O](H),H.innerHTML=E+1+"/"+A.length,z||(e[O](S),e[O](L))):A=0,(o=i).src=s}(w.gallery,w.position):d||w.iframeSrc?(o=c,I.ytSrc?W="https://www.youtube.com/embed/"+d+"?html5=1&rel=0&playsinline=1&autoplay=1":I.vimeoSrc?W="https://player.vimeo.com/video/"+d+"?autoplay=1":I.iframeSrc&&(W=I.iframeSrc),p.src=W,P(),setTimeout(function(){j(m,""),c[O](m),U()},9)):w.imgSrc?(_=1,!~C.indexOf(s=w.imgSrc)&&R(1),(o=i).src=s):w.audio?(R(1),(o=a).src=w.audio,G("audio file")):w.vidSrc?(R(1),D=w.vidSrc,Array.isArray(D)?(o=r.cloneNode(),D.forEach(function(t){var n=document[N]("SOURCE");n.src=t,n.type="video/"+t.match(/.(\w+)$/)[1],o[O](n)})):(o=r).src=D,G("video")):(o=i).src="IMG"===t.tagName?t.src:window.getComputedStyle(t).backgroundImage.replace(/^url|[(|)|'|"]/g,""),e[O](o),document.body[O](e);var W}}(); +var BigPicture=function(){var t,n,e,o,i,r,a,c,p,s,l,d,u,f,m,b,g,h,x,v,y,w,_,T,k,M,S,L,E,A,H,z,I,C=[],D={},O="appendChild",N="createElement",V="removeChild";function W(){var n=t.getBoundingClientRect();return"transform:translate3D("+(n.left-(e.clientWidth-n.width)/2)+"px, "+(n.top-(e.clientHeight-n.height)/2)+"px, 0) scale3D("+t.clientWidth/o.clientWidth+", "+t.clientHeight/o.clientHeight+", 0)"}function q(t){var n=A.length-1;if(!u){if(t>0&&E===n||t<0&&!E){if(!I.loop)return j(i,""),void setTimeout(j,9,i,"animation:"+(t>0?"bpl":"bpf")+" .3s;transition:transform .35s");E=t>0?-1:n+1}if([(E=Math.max(0,Math.min(E+t,n)))-1,E,E+1].forEach(function(t){if(t=Math.max(0,Math.min(t,n)),!D[t]){var e=A[t].src,o=document[N]("IMG");o.addEventListener("load",F.bind(null,e)),o.src=e,D[t]=o}}),D[E].complete)return B(t);u=1,j(m,"opacity:.4;"),e[O](m),D[E].onload=function(){y&&B(t)},D[E].onerror=function(){A[E]={error:"Error loading image"},y&&B(t)}}}function B(n){u&&(e[V](m),u=0);var r=A[E];if(r.error)alert(r.error);else{var a=e.querySelector("img:last-of-type");j(i=o=D[E],"animation:"+(n>0?"bpfl":"bpfr")+" .35s;transition:transform .35s"),j(a,"animation:"+(n>0?"bpfol":"bpfor")+" .35s both"),e[O](i),r.el&&(t=r.el)}H.innerHTML=E+1+"/"+A.length,X(A[E].caption),M&&M([i,A[E]])}function P(){var t,n,e=.95*window.innerHeight,o=.95*window.innerWidth,i=I.dimensions||[1920,1080],r=i[0],a=i[1],p=a/r;p>e/o?n=(t=Math.min(a,e))/p:t=(n=Math.min(r,o))*p,c.style.cssText+="width:"+n+"px;height:"+t+"px;"}function G(t){~[1,4].indexOf(o.readyState)?(U(),setTimeout(function(){o.play()},99)):o.error?U(t):f=setTimeout(G,35,t)}function R(n){I.noLoader||(n&&j(m,"top:"+t.offsetTop+"px;left:"+t.offsetLeft+"px;height:"+t.clientHeight+"px;width:"+t.clientWidth+"px"),t.parentElement[n?O:V](m),u=n)}function X(t){t&&(g.innerHTML=t),j(b,"opacity:"+(t?"1;pointer-events:auto":"0"))}function F(t){!~C.indexOf(t)&&C.push(t)}function U(t){if(u&&R(),T&&T(),"string"==typeof t)return $(),I.onError?I.onError():alert("Error: The requested "+t+" could not be loaded.");_&&F(s),o.style.cssText+=W(),j(e,"opacity:1;pointer-events:auto"),k=setTimeout(k,410),v=1,y=!!A,setTimeout(function(){o.style.cssText+="transition:transform .35s;transform:none",h&&setTimeout(X,250,h)},60)}function Y(t){var n=t.target,i=[b,x,r,a,g,L,S,m];n&&n.blur(),w||~i.indexOf(n)||(o.style.cssText+=W(),j(e,"pointer-events:auto"),setTimeout($,350),clearTimeout(k),v=0,w=1)}function $(){if((o===c?p:o).removeAttribute("src"),document.body[V](e),e[V](o),j(e,""),j(o,""),X(0),y){for(var t=e.querySelectorAll("img"),n=0;n',n}function d(t,n){var e=document[N]("button");return e.className="bp-lr",e.innerHTML='',j(e,n),e.onclick=function(n){n.stopPropagation(),q(t)},e}var f=document[N]("STYLE");f.innerHTML="#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:rgba(0,0,0,.7);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{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}}",document.head[O](f),(e=document[N]("DIV")).id="bp_container",e.onclick=Y,l=s("bp-x"),e[O](l),"ontouchstart"in window&&(z=1,e.ontouchstart=function(n){var e=n.changedTouches;t=e[0].pageX},e.ontouchmove=function(t){t.preventDefault()},e.ontouchend=function(n){var e=n.changedTouches;if(y){var o=e[0].pageX-t;o<-30&&q(1),o>30&&q(-1)}}),i=document[N]("IMG"),(r=document[N]("VIDEO")).id="bp_vid",r.setAttribute("playsinline",1),r.controls=1,r.loop=1,(a=document[N]("audio")).id="bp_aud",a.controls=1,a.loop=1,(H=document[N]("span")).id="bp_count",(b=document[N]("DIV")).id="bp_caption",(x=s("bp-xc")).onclick=X.bind(null,0),b[O](x),g=document[N]("SPAN"),b[O](g),e[O](b),S=d(1,"transform:scalex(-1)"),L=d(-1,"left:0;right:auto"),(m=document[N]("DIV")).id="bp_loader",m.innerHTML='',(c=document[N]("DIV")).id="bp_sv",(p=document[N]("IFRAME")).setAttribute("allowfullscreen",1),p.allow="autoplay; fullscreen",p.onload=function(){return c[V](m)},j(p,"border:0;position:absolute;height:100%;width:100%;left:0;top:0"),c[O](p),i.onload=U,i.onerror=U.bind(null,"image"),window.addEventListener("resize",function(){y||u&&R(1),o===c&&P()}),document.addEventListener("keyup",function(t){var n=t.keyCode;27===n&&v&&Y(e),y&&(39===n&&q(1),37===n&&q(-1),38===n&&q(10),40===n&&q(-10))}),document.addEventListener("keydown",function(t){y&&~[37,38,39,40].indexOf(t.keyCode)&&t.preventDefault()}),document.addEventListener("focus",function(t){v&&!e.contains(t.target)&&(t.stopPropagation(),l.focus())},1),n=1}(),u&&(clearTimeout(f),$()),I=w,d=w.ytSrc||w.vimeoSrc,T=w.animationStart,k=w.animationEnd,M=w.onChangeImage,_=0,h=(t=w.el).getAttribute("data-caption"),w.gallery?function(n,r){if(Array.isArray(n))A=n,h=n[E=r||0].caption;else{var a=(A=[].slice.call("string"==typeof n?document.querySelectorAll(n+" [data-bp]"):n)).indexOf(t);E=0===r||r?r:-1!==a?a:0,A=A.map(function(t){return{el:t,src:t.getAttribute("data-bp"),caption:t.getAttribute("data-caption")}})}_=1,!~C.indexOf(s=A[E].src)&&R(1),A.length>1?(e[O](H),H.innerHTML=E+1+"/"+A.length,z||(e[O](S),e[O](L))):A=0,(o=i).src=s}(w.gallery,w.position):d||w.iframeSrc?(o=c,I.ytSrc?W="https://www.youtube.com/embed/"+d+"?html5=1&rel=0&playsinline=1&autoplay=1":I.vimeoSrc?W="https://player.vimeo.com/video/"+d+"?autoplay=1":I.iframeSrc&&(W=I.iframeSrc),j(m,""),c[O](m),p.src=W,P(),setTimeout(U,9)):w.imgSrc?(_=1,!~C.indexOf(s=w.imgSrc)&&R(1),(o=i).src=s):w.audio?(R(1),(o=a).src=w.audio,G("audio file")):w.vidSrc?(R(1),D=w.vidSrc,Array.isArray(D)?(o=r.cloneNode(),D.forEach(function(t){var n=document[N]("SOURCE");n.src=t,n.type="video/"+t.match(/.(\w+)$/)[1],o[O](n)})):(o=r).src=D,G("video")):(o=i).src="IMG"===t.tagName?t.src:window.getComputedStyle(t).backgroundImage.replace(/^url|[(|)|'|"]/g,""),e[O](o),document.body[O](e);var W}}(); //# sourceMappingURL=BigPicture.js.map diff --git a/index.js b/index.js index 20ec37f..cfdb2cc 100644 --- a/index.js +++ b/index.js @@ -1,698 +1,743 @@ // BigPicture.js | license MIT | henrygd.me/bigpicture -(function() { - var // assign window object to variable - global = window, - // trigger element used to open popup - el, - // set to true after first interaction - initialized, - // container element holding html needed for script - container, - // currently active display element (image, video, youtube / vimeo iframe container) - displayElement, - // popup image element - displayImage, - // popup video element - displayVideo, - // popup audio element - displayAudio, - // container element to hold youtube / vimeo iframe - iframeContainer, - // iframe to hold youtube / vimeo player - iframeSiteVid, - // store requested image source - imgSrc, - // button that closes the container - closeButton, - // youtube / vimeo video id - siteVidID, - // keeps track of loading icon display state - isLoading, - // timeout to check video status while loading - checkMediaTimeout, - // loading icon element - loadingIcon, - // caption element - caption, - // caption content element - captionText, - // store caption content - captionContent, - // hide caption button element - captionHideButton, - // open state for container element - isOpen, - // gallery open state - galleryOpen, - // used during close animation to avoid triggering timeout twice - isClosing, - // array of prev viewed image urls to check if cached before showing loading icon - imgCache = [], - // store whether image requested is remote or local - remoteImage, - // store animation opening callbacks - animationStart, - animationEnd, - // store changeGalleryImage callback - onChangeImage, - // gallery left / right icons - rightArrowBtn, - leftArrowBtn, - // position of gallery - galleryPosition, - // hold active gallery els / image src - galleryEls, - // counter element - galleryCounter, - // store images in gallery that are being loaded - preloadedImages = {}, - // whether device supports touch events - supportsTouch, - // options object - opts, - // Save bytes in the minified version - doc = document, - appendEl = 'appendChild', - createEl = 'createElement', - removeEl = 'removeChild', - htmlInner = 'innerHTML', - pointerEventsAuto = 'pointer-events:auto', - cHeight = 'clientHeight', - cWidth = 'clientWidth', - listenFor = 'addEventListener', - timeout = global.setTimeout, - clearTimeout = global.clearTimeout; - - module.exports = function(options) { - // initialize called on initial open to create elements / style / event handlers - initialized || initialize(); - - // clear currently loading stuff - if (isLoading) { - clearTimeout(checkMediaTimeout); - removeContainer(); - } - opts = options; - - // store video id if youtube / vimeo video is requested - siteVidID = options.ytSrc || options.vimeoSrc; - - // store optional callbacks - animationStart = options.animationStart; - animationEnd = options.animationEnd; - onChangeImage = options.onChangeImage; - - // set trigger element - el = options.el; - - // wipe existing remoteImage state - remoteImage = false; - - // set caption if provided - captionContent = el.getAttribute('data-caption'); - - if (options.gallery) { - makeGallery(options.gallery, options.position); - } else if (siteVidID || options.iframeSrc) { - // if vimeo, youtube, or iframe video - toggleLoadingIcon(true); - displayElement = iframeContainer; - createIframe(); - } else if (options.imgSrc) { - // if remote image - remoteImage = true; - imgSrc = options.imgSrc; - !~imgCache.indexOf(imgSrc) && toggleLoadingIcon(true); - displayElement = displayImage; - displayElement.src = imgSrc; - } else if (options.audio) { - // if direct video link - toggleLoadingIcon(true); - displayElement = displayAudio; - displayElement.src = options.audio; - checkMedia('audio file'); - } else if (options.vidSrc) { - // if direct video link - toggleLoadingIcon(true); - makeVidSrc(options.vidSrc); - checkMedia('video'); - } else { - // local image / background image already loaded on page - displayElement = displayImage; - // get img source or element background image - displayElement.src = - el.tagName === 'IMG' - ? el.src - : global - .getComputedStyle(el) - .backgroundImage.replace(/^url|[(|)|'|"]/g, ''); - } +// trigger element used to open popup +var el; - // add container to page - container[appendEl](displayElement); - doc.body[appendEl](container); - }; +// set to true after first interaction +var initialized; - // create all needed methods / store dom elements on first use - function initialize() { - var startX; - // return close button elements - function createCloseButton(className) { - var el = doc[createEl]('button'); - el.className = className; - el[htmlInner] = - ''; - return el; - } +// container element holding html needed for script +var container; - function createArrowSymbol(direction, style) { - var el = doc[createEl]('button'); - el.className = 'bp-lr'; - el[htmlInner] = - ''; - changeCSS(el, style); - el.onclick = function(e) { - e.stopPropagation(); - updateGallery(direction); - }; - return el; - } +// currently active display element (image, video, youtube / vimeo iframe container) +var displayElement; - // add style - if you want to tweak, run through beautifier - var style = doc[createEl]('STYLE'); - style[htmlInner] = - '#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:rgba(0,0,0,.7);opacity:0;transition:opacity .35s}#bp_loader{top:0;left:0;bottom:0;display:flex;margin:0;cursor:wait;z-index:9;background:0 0}#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{height:0;padding-bottom:54%;background-color:#000;width:96%}#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 0;cursor:pointer;transition:all .1s}#bp_container>.bp-x{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 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}}@media (min-aspect-ratio:9/5){#bp_sv{height:98%;width:170.6vh;padding:0}}'; - doc.head[appendEl](style); - - // create container element - container = doc[createEl]('DIV'); - container.id = 'bp_container'; - container.onclick = close; - closeButton = createCloseButton('bp-x'); - container[appendEl](closeButton); - // gallery swipe listeners - if ('ontouchstart' in global) { - supportsTouch = true; - container.ontouchstart = function(e) { - startX = e.changedTouches[0].pageX; - }; - container.ontouchmove = function(e) { - e.preventDefault(); - }; - container.ontouchend = function(e) { - if (!galleryOpen) { - return; - } - var touchobj = e.changedTouches[0]; - var distX = touchobj.pageX - startX; - // swipe right - distX < -30 && updateGallery(1); - // swipe left - distX > 30 && updateGallery(-1); - }; - } +// popup image element +var displayImage; - // create display image element - displayImage = doc[createEl]('IMG'); - - // create display video element - displayVideo = doc[createEl]('VIDEO'); - displayVideo.id = 'bp_vid'; - displayVideo.setAttribute('playsinline', true); - displayVideo.controls = true; - displayVideo.loop = true; - - // create audio element - displayAudio = doc[createEl]('audio'); - displayAudio.id = 'bp_aud'; - displayAudio.controls = true; - displayAudio.loop = true; - - // create gallery counter - galleryCounter = doc[createEl]('span'); - galleryCounter.id = 'bp_count'; - - // create caption elements - caption = doc[createEl]('DIV'); - caption.id = 'bp_caption'; - captionHideButton = createCloseButton('bp-xc'); - captionHideButton.onclick = toggleCaption.bind(null, false); - caption[appendEl](captionHideButton); - captionText = doc[createEl]('SPAN'); - caption[appendEl](captionText); - container[appendEl](caption); - - // left / right arrow icons - rightArrowBtn = createArrowSymbol(1, 'transform:scalex(-1)'); - leftArrowBtn = createArrowSymbol(-1, 'left:0;right:auto'); - - // create loading icon element - loadingIcon = doc[createEl]('DIV'); - loadingIcon.id = 'bp_loader'; - loadingIcon[htmlInner] = - ''; - // create youtube / vimeo container - iframeContainer = doc[createEl]('DIV'); - iframeContainer.id = 'bp_sv'; - - // create iframe to hold youtube / vimeo player - iframeSiteVid = doc[createEl]('IFRAME'); - iframeSiteVid.setAttribute('allowfullscreen', true); - iframeSiteVid.allow = 'autoplay; fullscreen'; - iframeSiteVid.onload = open; - changeCSS( - iframeSiteVid, - 'border:0;position:absolute;height:100%;width:100%;left:0;top:0' - ); - iframeContainer[appendEl](iframeSiteVid); +// popup video element +var displayVideo; - // display image bindings for image load and error - displayImage.onload = open; - displayImage.onerror = open.bind(null, 'image'); +// popup audio element +var displayAudio; - // adjust loader position on window resize - global[listenFor]('resize', function() { - galleryOpen || (isLoading && toggleLoadingIcon(true)); - }); +// container element to hold youtube / vimeo iframe +var iframeContainer; - // close container on escape key press and arrow buttons for gallery - doc[listenFor]('keyup', function(e) { - var key = e.keyCode; - key === 27 && isOpen && close(container); - if (galleryOpen) { - key === 39 && updateGallery(1); - key === 37 && updateGallery(-1); - key === 38 && updateGallery(10); - key === 40 && updateGallery(-10); - } - }); - // prevent scrolling with arrow keys if gallery open - doc[listenFor]('keydown', function(e) { - var usedKeys = [37, 38, 39, 40]; - if (galleryOpen && ~usedKeys.indexOf(e.keyCode)) { - e.preventDefault(); - } - }); +// iframe to hold youtube / vimeo player +var iframeSiteVid; - // trap focus within conainer while open - doc[listenFor]( - 'focus', - function(e) { - if (isOpen && !container.contains(e.target)) { - e.stopPropagation(); - closeButton.focus(); - } - }, - true - ); +// store requested image source +var imgSrc; - // all done - initialized = true; - } +// button that closes the container +var closeButton; - // return transform style to make full size display el match trigger el size - function getRect() { - var rect = el.getBoundingClientRect(); - var leftOffset = rect.left - (container[cWidth] - rect.width) / 2; - var centerTop = rect.top - (container[cHeight] - rect.height) / 2; - var scaleWidth = el[cWidth] / displayElement[cWidth]; - var scaleHeight = el[cHeight] / displayElement[cHeight]; - return ( - 'transform:translate3D(' + - leftOffset + - 'px, ' + - centerTop + - 'px, 0) scale3D(' + - scaleWidth + - ', ' + - scaleHeight + - ', 0)' - ); - } +// youtube / vimeo video id +var siteVidID; - function makeVidSrc(source) { - if (Array.isArray(source)) { - displayElement = displayVideo.cloneNode(); - source.forEach(function(src) { - var source = doc[createEl]('SOURCE'); - source.src = src; - source.type = 'video/' + src.match(/.(\w+)$/)[1]; - displayElement[appendEl](source); - }); - } else { - displayElement = displayVideo; - displayElement.src = source; - } +// keeps track of loading icon display state +var isLoading; + +// timeout to check video status while loading +var checkMediaTimeout; + +// loading icon element +var loadingIcon; + +// caption element +var caption; + +// caption content element +var captionText; + +// store caption content +var captionContent; + +// hide caption button element +var captionHideButton; + +// open state for container element +var isOpen; + +// gallery open state +var galleryOpen; + +// used during close animation to avoid triggering timeout twice +var isClosing; + +// array of prev viewed image urls to check if cached before showing loading icon +var imgCache = []; + +// store whether image requested is remote or local +var remoteImage; + +// store animation opening callbacks +var animationStart; +var animationEnd; + +// store changeGalleryImage callback +var onChangeImage; + +// gallery left / right icons +var rightArrowBtn; + +var leftArrowBtn; + +// position of gallery +var galleryPosition; + +// hold active gallery els / image src +var galleryEls; + +// counter element +var galleryCounter; + +// store images in gallery that are being loaded +var preloadedImages = {}; + +// whether device supports touch events +var supportsTouch; + +// options object +var opts; + +// Save bytes in the minified version +var appendEl = 'appendChild'; +var createEl = 'createElement'; +var removeEl = 'removeChild'; + +function BigPicture (options) { + // initialize called on initial open to create elements / style / event handlers + initialized || initialize(); + + // clear currently loading stuff + if (isLoading) { + clearTimeout(checkMediaTimeout); + removeContainer(); } - function makeGallery(gallery, position) { - if (Array.isArray(gallery)) { - // is array of images - galleryPosition = position || 0; - galleryEls = gallery; - captionContent = gallery[galleryPosition].caption; - } else { - // is element selector or nodelist - galleryEls = [].slice.call( - typeof gallery === 'string' - ? doc.querySelectorAll(gallery + ' [data-bp]') - : gallery - ); - // find initial gallery position - var elIndex = galleryEls.indexOf(el); - galleryPosition = (position === 0 || position) ? position : (elIndex !== -1 ? elIndex : 0); - // make gallery object w/ els / src / caption - galleryEls = galleryEls.map(function(el) { - return { - el: el, - src: el.getAttribute('data-bp'), - caption: el.getAttribute('data-caption'), - }; - }); - } - // show loading icon if needed + opts = options; + + // store video id if youtube / vimeo video is requested + siteVidID = options.ytSrc || options.vimeoSrc; + + // store optional callbacks + animationStart = options.animationStart; + animationEnd = options.animationEnd; + onChangeImage = options.onChangeImage; + + // set trigger element + el = options.el; + + // wipe existing remoteImage state + remoteImage = false; + + // set caption if provided + captionContent = el.getAttribute('data-caption'); + + if (options.gallery) { + makeGallery(options.gallery, options.position); + } else if (siteVidID || options.iframeSrc) { + // if vimeo, youtube, or iframe video + // toggleLoadingIcon(true) + displayElement = iframeContainer; + createIframe(); + } else if (options.imgSrc) { + // if remote image remoteImage = true; - // set initial src to imgSrc so it will be cached in open func - imgSrc = galleryEls[galleryPosition].src; + imgSrc = options.imgSrc; !~imgCache.indexOf(imgSrc) && toggleLoadingIcon(true); - if (galleryEls.length > 1) { - // if length is greater than one, add gallery stuff - container[appendEl](galleryCounter); - galleryCounter[htmlInner] = galleryPosition + 1 + '/' + galleryEls.length; - if (!supportsTouch) { - // add arrows if device doesn't support touch - container[appendEl](rightArrowBtn); - container[appendEl](leftArrowBtn); - } - } else { - // gallery is one, just show without clutter - galleryEls = false; - } displayElement = displayImage; - // set initial image src displayElement.src = imgSrc; + } else if (options.audio) { + // if direct video link + toggleLoadingIcon(true); + displayElement = displayAudio; + displayElement.src = options.audio; + checkMedia('audio file'); + } else if (options.vidSrc) { + // if direct video link + toggleLoadingIcon(true); + makeVidSrc(options.vidSrc); + checkMedia('video'); + } else { + // local image / background image already loaded on page + displayElement = displayImage; + // get img source or element background image + displayElement.src = + el.tagName === 'IMG' + ? el.src + : window + .getComputedStyle(el) + .backgroundImage.replace(/^url|[(|)|'|"]/g, ''); } - function updateGallery(movement) { - var galleryLength = galleryEls.length - 1; - - // only allow one change at a time - if (isLoading) { - return; - } + // add container to page + container[appendEl](displayElement); + document.body[appendEl](container); +} + +// create all needed methods / store dom elements on first use +function initialize() { + var startX; + // return close button elements + function createCloseButton(className) { + var el = document[createEl]('button'); + el.className = className; + el.innerHTML = + ''; + return el + } - // return if requesting out of range image - var isEnd = - (movement > 0 && galleryPosition === galleryLength) || - (movement < 0 && !galleryPosition); - if (isEnd) { - // if beginning or end of gallery, run end animation - if (!opts.loop) { - changeCSS(displayImage, ''); - timeout( - changeCSS, - 9, - displayImage, - 'animation:' + - (movement > 0 ? 'bpl' : 'bpf') + - ' .3s;transition:transform .35s' - ); - return; - } - // if gallery is looped, adjust position to beginning / end - galleryPosition = movement > 0 ? -1 : galleryLength + 1; - } + function createArrowSymbol(direction, style) { + var el = document[createEl]('button'); + el.className = 'bp-lr'; + el.innerHTML = + ''; + changeCSS(el, style); + el.onclick = function (e) { + e.stopPropagation(); + updateGallery(direction); + }; + return el + } - // normalize position - galleryPosition = Math.max( - 0, - Math.min(galleryPosition + movement, galleryLength) - ); + // add style - if you want to tweak, run through beautifier + var style = document[createEl]('STYLE'); + style.innerHTML = + '#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:rgba(0,0,0,.7);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{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}}'; + document.head[appendEl](style); + + // create container element + container = document[createEl]('DIV'); + container.id = 'bp_container'; + container.onclick = close; + closeButton = createCloseButton('bp-x'); + container[appendEl](closeButton); + // gallery swipe listeners + if ('ontouchstart' in window) { + supportsTouch = true; + container.ontouchstart = function (ref) { + var changedTouches = ref.changedTouches; + + startX = changedTouches[0].pageX; + }; + container.ontouchmove = function (e) { + e.preventDefault(); + }; + container.ontouchend = function (ref) { + var changedTouches = ref.changedTouches; - // load images before and after for quicker scrolling through pictures - [galleryPosition - 1, galleryPosition, galleryPosition + 1].forEach( - function(position) { - // normalize position - position = Math.max(0, Math.min(position, galleryLength)); - // cancel if image has already been preloaded - if (preloadedImages[position]) return; - var src = galleryEls[position].src; - // create image for preloadedImages - var img = doc[createEl]('IMG'); - img[listenFor]('load', addToImgCache.bind(null, src)); - img.src = src; - preloadedImages[position] = img; + if (!galleryOpen) { + return } - ); - // if image is loaded, show it - if (preloadedImages[galleryPosition].complete) { - return changeGalleryImage(movement); - } - // if not, show loading icon and change when loaded - isLoading = true; - changeCSS(loadingIcon, 'opacity:.4;'); - container[appendEl](loadingIcon); - preloadedImages[galleryPosition].onload = function() { - galleryOpen && changeGalleryImage(movement); - }; - // if error, store error object in el array - preloadedImages[galleryPosition].onerror = function() { - galleryEls[galleryPosition] = { - error: 'Error loading image', - }; - galleryOpen && changeGalleryImage(movement); + var distX = changedTouches[0].pageX - startX; + // swipe right + distX < -30 && updateGallery(1); + // swipe left + distX > 30 && updateGallery(-1); }; } - function changeGalleryImage(movement) { - if (isLoading) { - container[removeEl](loadingIcon); - isLoading = false; + // create display image element + displayImage = document[createEl]('IMG'); + + // create display video element + displayVideo = document[createEl]('VIDEO'); + displayVideo.id = 'bp_vid'; + displayVideo.setAttribute('playsinline', true); + displayVideo.controls = true; + displayVideo.loop = true; + + // create audio element + displayAudio = document[createEl]('audio'); + displayAudio.id = 'bp_aud'; + displayAudio.controls = true; + displayAudio.loop = true; + + // create gallery counter + galleryCounter = document[createEl]('span'); + galleryCounter.id = 'bp_count'; + + // create caption elements + caption = document[createEl]('DIV'); + caption.id = 'bp_caption'; + captionHideButton = createCloseButton('bp-xc'); + captionHideButton.onclick = toggleCaption.bind(null, false); + caption[appendEl](captionHideButton); + captionText = document[createEl]('SPAN'); + caption[appendEl](captionText); + container[appendEl](caption); + + // left / right arrow icons + rightArrowBtn = createArrowSymbol(1, 'transform:scalex(-1)'); + leftArrowBtn = createArrowSymbol(-1, 'left:0;right:auto'); + + // create loading icon element + loadingIcon = document[createEl]('DIV'); + loadingIcon.id = 'bp_loader'; + loadingIcon.innerHTML = + ''; + // create youtube / vimeo container + iframeContainer = document[createEl]('DIV'); + iframeContainer.id = 'bp_sv'; + + // create iframe to hold youtube / vimeo player + iframeSiteVid = document[createEl]('IFRAME'); + iframeSiteVid.setAttribute('allowfullscreen', true); + iframeSiteVid.allow = 'autoplay; fullscreen'; + iframeSiteVid.onload = function () { return iframeContainer[removeEl](loadingIcon); }; + changeCSS( + iframeSiteVid, + 'border:0;position:absolute;height:100%;width:100%;left:0;top:0' + ); + iframeContainer[appendEl](iframeSiteVid); + + // display image bindings for image load and error + displayImage.onload = open; + displayImage.onerror = open.bind(null, 'image'); + + window.addEventListener('resize', function () { + // adjust loader position on window resize + galleryOpen || (isLoading && toggleLoadingIcon(true)); + // adjust iframe dimensions + displayElement === iframeContainer && updateIframeDimensions(); + }); + + // close container on escape key press and arrow buttons for gallery + document.addEventListener('keyup', function (ref) { + var keyCode = ref.keyCode; + + keyCode === 27 && isOpen && close(container); + if (galleryOpen) { + keyCode === 39 && updateGallery(1); + keyCode === 37 && updateGallery(-1); + keyCode === 38 && updateGallery(10); + keyCode === 40 && updateGallery(-10); } - var activeEl = galleryEls[galleryPosition]; - if (activeEl.error) { - // show alert if error - alert(activeEl.error); - } else { - // add new image, animate images in and out w/ css animation - var oldimg = container.querySelector('img:last-of-type'); - displayImage = displayElement = preloadedImages[galleryPosition]; - changeCSS( - displayImage, - 'animation:' + - (movement > 0 ? 'bpfl' : 'bpfr') + - ' .35s;transition:transform .35s' - ); - changeCSS( - oldimg, - 'animation:' + (movement > 0 ? 'bpfol' : 'bpfor') + ' .35s both' - ); - container[appendEl](displayImage); - // update el for closing animation - if (activeEl.el) { - el = activeEl.el; - } + }); + // prevent scrolling with arrow keys if gallery open + document.addEventListener('keydown', function (e) { + var usedKeys = [37, 38, 39, 40]; + if (galleryOpen && ~usedKeys.indexOf(e.keyCode)) { + e.preventDefault(); } - // update counter - galleryCounter[htmlInner] = galleryPosition + 1 + '/' + galleryEls.length; - // show / hide caption - toggleCaption(galleryEls[galleryPosition].caption); - // execute onChangeImage callback - onChangeImage && onChangeImage([displayImage, galleryEls[galleryPosition]]); - } + }); - // create video iframe - function createIframe() { - var url; - var prefix = 'https://'; - var suffix = 'autoplay=1'; - - // create appropriate url - if (opts.ytSrc) { - url = - prefix + - 'www.youtube.com/embed/' + - siteVidID + - '?html5=1&rel=0&playsinline=1&' + - suffix; - } else if (opts.vimeoSrc) { - url = prefix + 'player.vimeo.com/video/' + siteVidID + '?' + suffix; - } else if (opts.iframeSrc) { - url = opts.iframeSrc; + // trap focus within conainer while open + document.addEventListener( + 'focus', + function (e) { + if (isOpen && !container.contains(e.target)) { + e.stopPropagation(); + closeButton.focus(); + } + }, + true + ); + + // all done + initialized = true; +} + +// return transform style to make full size display el match trigger el size +function getRect() { + var ref = el.getBoundingClientRect(); + var top = ref.top; + var left = ref.left; + var width = ref.width; + var height = ref.height; + var leftOffset = left - (container.clientWidth - width) / 2; + var centerTop = top - (container.clientHeight - height) / 2; + var scaleWidth = el.clientWidth / displayElement.clientWidth; + var scaleHeight = el.clientHeight / displayElement.clientHeight; + return ("transform:translate3D(" + leftOffset + "px, " + centerTop + "px, 0) scale3D(" + scaleWidth + ", " + scaleHeight + ", 0)") +} + +function makeVidSrc(source) { + if (Array.isArray(source)) { + displayElement = displayVideo.cloneNode(); + source.forEach(function (src) { + var source = document[createEl]('SOURCE'); + source.src = src; + source.type = "video/" + (src.match(/.(\w+)$/)[1]); + displayElement[appendEl](source); + }); + } else { + displayElement = displayVideo; + displayElement.src = source; + } +} + +function makeGallery(gallery, position) { + if (Array.isArray(gallery)) { + // is array of images + galleryPosition = position || 0; + galleryEls = gallery; + captionContent = gallery[galleryPosition].caption; + } else { + // is element selector or nodelist + galleryEls = [].slice.call( + typeof gallery === 'string' + ? document.querySelectorAll((gallery + " [data-bp]")) + : gallery + ); + // find initial gallery position + var elIndex = galleryEls.indexOf(el); + galleryPosition = + position === 0 || position ? position : elIndex !== -1 ? elIndex : 0; + // make gallery object w/ els / src / caption + galleryEls = galleryEls.map(function (el) { return ({ + el: el, + src: el.getAttribute('data-bp'), + caption: el.getAttribute('data-caption'), + }); }); + } + // show loading icon if needed + remoteImage = true; + // set initial src to imgSrc so it will be cached in open func + imgSrc = galleryEls[galleryPosition].src; + !~imgCache.indexOf(imgSrc) && toggleLoadingIcon(true); + if (galleryEls.length > 1) { + // if length is greater than one, add gallery stuff + container[appendEl](galleryCounter); + galleryCounter.innerHTML = (galleryPosition + 1) + "/" + (galleryEls.length); + if (!supportsTouch) { + // add arrows if device doesn't support touch + container[appendEl](rightArrowBtn); + container[appendEl](leftArrowBtn); } - - // set iframe src to url - iframeSiteVid.src = url; + } else { + // gallery is one, just show without clutter + galleryEls = false; } + displayElement = displayImage; + // set initial image src + displayElement.src = imgSrc; +} + +function updateGallery(movement) { + var galleryLength = galleryEls.length - 1; - // timeout to check video status while loading - function checkMedia(errMsg) { - if (~[1, 4].indexOf(displayElement.readyState)) { - open(); - // short timeout to to make sure controls show in safari 11 - timeout(function() { - displayElement.play(); - }, 99); - } else if (displayElement.error) open(errMsg); - else checkMediaTimeout = timeout(checkMedia, 35, errMsg); + // only allow one change at a time + if (isLoading) { + return } - // hide / show loading icon - function toggleLoadingIcon(bool) { - // don't show loading icon if noLoader is specified - if (opts.noLoader) return; - // bool is true if we want to show icon, false if we want to remove - // change style to match trigger element dimensions if we want to show - bool && - changeCSS( - loadingIcon, - 'top:' + - el.offsetTop + - 'px;left:' + - el.offsetLeft + - 'px;height:' + - el[cHeight] + - 'px;width:' + - el[cWidth] + - 'px' + // return if requesting out of range image + var isEnd = + (movement > 0 && galleryPosition === galleryLength) || + (movement < 0 && !galleryPosition); + if (isEnd) { + // if beginning or end of gallery, run end animation + if (!opts.loop) { + changeCSS(displayImage, ''); + setTimeout( + changeCSS, + 9, + displayImage, + ("animation:" + (movement > 0 ? 'bpl' : 'bpf') + " .3s;transition:transform .35s") ); - // add or remove loader from DOM - el.parentElement[bool ? appendEl : removeEl](loadingIcon); - isLoading = bool; + return + } + // if gallery is looped, adjust position to beginning / end + galleryPosition = movement > 0 ? -1 : galleryLength + 1; } - // hide & show caption - function toggleCaption(captionContent) { - if (captionContent) { - captionText[htmlInner] = captionContent; + // normalize position + galleryPosition = Math.max( + 0, + Math.min(galleryPosition + movement, galleryLength) + ) + + // load images before and after for quicker scrolling through pictures + ;[galleryPosition - 1, galleryPosition, galleryPosition + 1].forEach( + function (position) { + // normalize position + position = Math.max(0, Math.min(position, galleryLength)); + // cancel if image has already been preloaded + if (preloadedImages[position]) { return } + var src = galleryEls[position].src; + // create image for preloadedImages + var img = document[createEl]('IMG'); + img.addEventListener('load', addToImgCache.bind(null, src)); + img.src = src; + preloadedImages[position] = img; } + ); + // if image is loaded, show it + if (preloadedImages[galleryPosition].complete) { + return changeGalleryImage(movement) + } + // if not, show loading icon and change when loaded + isLoading = true; + changeCSS(loadingIcon, 'opacity:.4;'); + container[appendEl](loadingIcon); + preloadedImages[galleryPosition].onload = function () { + galleryOpen && changeGalleryImage(movement); + }; + // if error, store error object in el array + preloadedImages[galleryPosition].onerror = function () { + galleryEls[galleryPosition] = { + error: 'Error loading image', + }; + galleryOpen && changeGalleryImage(movement); + }; +} + +function changeGalleryImage(movement) { + if (isLoading) { + container[removeEl](loadingIcon); + isLoading = false; + } + var activeEl = galleryEls[galleryPosition]; + if (activeEl.error) { + // show alert if error + alert(activeEl.error); + } else { + // add new image, animate images in and out w/ css animation + var oldimg = container.querySelector('img:last-of-type'); + displayImage = displayElement = preloadedImages[galleryPosition]; changeCSS( - caption, - 'opacity:' + (captionContent ? '1;' + pointerEventsAuto : '0') + displayImage, + ("animation:" + (movement > 0 ? 'bpfl' : 'bpfr') + " .35s;transition:transform .35s") ); + changeCSS(oldimg, ("animation:" + (movement > 0 ? 'bpfol' : 'bpfor') + " .35s both")); + container[appendEl](displayImage); + // update el for closing animation + if (activeEl.el) { + el = activeEl.el; + } } - - function addToImgCache(url) { - !~imgCache.indexOf(url) && imgCache.push(url); + // update counter + galleryCounter.innerHTML = (galleryPosition + 1) + "/" + (galleryEls.length); + // show / hide caption + toggleCaption(galleryEls[galleryPosition].caption); + // execute onChangeImage callback + onChangeImage && onChangeImage([displayImage, galleryEls[galleryPosition]]); +} + +// create video iframe +function createIframe() { + var url; + var prefix = 'https://'; + var suffix = 'autoplay=1'; + + // create appropriate url + if (opts.ytSrc) { + url = prefix + "www.youtube.com/embed/" + siteVidID + "?html5=1&rel=0&playsinline=1&" + suffix; + } else if (opts.vimeoSrc) { + url = prefix + "player.vimeo.com/video/" + siteVidID + "?" + suffix; + } else if (opts.iframeSrc) { + url = opts.iframeSrc; } - // animate open of image / video; display caption if needed - function open(err) { - // hide loading spinner - isLoading && toggleLoadingIcon(); + // add loading spinner to iframe container + changeCSS(loadingIcon, ''); + iframeContainer[appendEl](loadingIcon); - // execute animationStart callback - animationStart && animationStart(); + // set iframe src to url + iframeSiteVid.src = url; - // check if we have an error string instead of normal event - if (typeof err === 'string') { - removeContainer(); - return opts.onError - ? opts.onError() - : alert('Error: The requested ' + err + ' could not be loaded.'); - } + updateIframeDimensions(); - // if remote image is loaded, add url to imgCache array - remoteImage && addToImgCache(imgSrc); + setTimeout(open, 9); +} - // transform displayEl to match trigger el - changeCSS(displayElement, getRect()); +function updateIframeDimensions() { + var height; + var width; - // fade in container - changeCSS(container, 'opacity:1;' + pointerEventsAuto); + // handle height / width / aspect / max width for iframe + var windowHeight = window.innerHeight * 0.95; + var windowWidth = window.innerWidth * 0.95; + var windowAspect = windowHeight / windowWidth; - // set animationEnd callback to run after animation ends (cleared if container closed) - animationEnd = timeout(animationEnd, 410); + var ref = opts.dimensions || [1920, 1080]; + var dimensionWidth = ref[0]; + var dimensionHeight = ref[1]; - isOpen = true; + var iframeAspect = dimensionHeight / dimensionWidth; - galleryOpen = !!galleryEls; + if (iframeAspect > windowAspect) { + height = Math.min(dimensionHeight, windowHeight); + width = height / iframeAspect; + } else { + width = Math.min(dimensionWidth, windowWidth); + height = width * iframeAspect; + } - // enlarge displayEl, fade in caption if hasCaption - timeout(function() { - changeCSS(displayElement, 'transition:transform .35s;transform:none'); - captionContent && timeout(toggleCaption, 250, captionContent); - }, 60); + iframeContainer.style.cssText += "width:" + width + "px;height:" + height + "px;"; +} + +// timeout to check video status while loading +function checkMedia(errMsg) { + if (~[1, 4].indexOf(displayElement.readyState)) { + open(); + // short timeout to to make sure controls show in safari 11 + setTimeout(function () { + displayElement.play(); + }, 99); + } else if (displayElement.error) { + open(errMsg); + } else { + checkMediaTimeout = setTimeout(checkMedia, 35, errMsg); } +} - // close active display element - function close(e) { - var target = e.target; - var clickEls = [ - caption, - captionHideButton, - displayVideo, - displayAudio, - captionText, - leftArrowBtn, - rightArrowBtn, +// hide / show loading icon +function toggleLoadingIcon(bool) { + // don't show loading icon if noLoader is specified + if (opts.noLoader) { + return + } + // bool is true if we want to show icon, false if we want to remove + // change style to match trigger element dimensions if we want to show + bool && + changeCSS( loadingIcon, - ]; + ("top:" + (el.offsetTop) + "px;left:" + (el.offsetLeft) + "px;height:" + (el.clientHeight) + "px;width:" + (el.clientWidth) + "px") + ); + // add or remove loader from DOM + el.parentElement[bool ? appendEl : removeEl](loadingIcon); + isLoading = bool; +} + +// hide & show caption +function toggleCaption(captionContent) { + if (captionContent) { + captionText.innerHTML = captionContent; + } + changeCSS( + caption, + ("opacity:" + (captionContent ? "1;pointer-events:auto" : '0')) + ); +} + +function addToImgCache(url) { + !~imgCache.indexOf(url) && imgCache.push(url); +} + +// animate open of image / video; display caption if needed +function open(err) { + // hide loading spinner + isLoading && toggleLoadingIcon(); + + // execute animationStart callback + animationStart && animationStart(); + + // check if we have an error string instead of normal event + if (typeof err === 'string') { + removeContainer(); + return opts.onError + ? opts.onError() + : alert(("Error: The requested " + err + " could not be loaded.")) + } - // blur to hide close button focus style - target && target.blur(); + // if remote image is loaded, add url to imgCache array + remoteImage && addToImgCache(imgSrc); - // don't close if one of the clickEls was clicked or container is already closing - if (isClosing || ~clickEls.indexOf(target)) { - return; - } + // transform displayEl to match trigger el + displayElement.style.cssText += getRect(); - // animate closing - displayElement.style.cssText += getRect(); - changeCSS(container, pointerEventsAuto); + // fade in container + changeCSS(container, "opacity:1;pointer-events:auto"); - // timeout to remove els from dom; use variable to avoid calling more than once - timeout(removeContainer, 350); + // set animationEnd callback to run after animation ends (cleared if container closed) + animationEnd = setTimeout(animationEnd, 410); - // clear animationEnd timeout - clearTimeout(animationEnd); + isOpen = true; - isOpen = false; - isClosing = true; - } + galleryOpen = !!galleryEls; + + // enlarge displayEl, fade in caption if hasCaption + setTimeout(function () { + displayElement.style.cssText += 'transition:transform .35s;transform:none'; + captionContent && setTimeout(toggleCaption, 250, captionContent); + }, 60); +} - // remove container / display element from the DOM - function removeContainer() { - // remove container from DOM & clear inline style - doc.body[removeEl](container); - container[removeEl](displayElement); - changeCSS(container, ''); +// close active display element +function close(e) { + var target = e.target; + var clickEls = [ + caption, + captionHideButton, + displayVideo, + displayAudio, + captionText, + leftArrowBtn, + rightArrowBtn, + loadingIcon ]; - // clear src of displayElement (or iframe if display el is iframe container) - (displayElement === iframeContainer - ? iframeSiteVid - : displayElement - ).removeAttribute('src'); + // blur to hide close button focus style + target && target.blur(); - // remove caption - toggleCaption(false); + // don't close if one of the clickEls was clicked or container is already closing + if (isClosing || ~clickEls.indexOf(target)) { + return + } - if (galleryOpen) { - // remove all gallery stuff - var images = container.querySelectorAll('img'); - for (var i = 0; i < images.length; i++) { - container[removeEl](images[i]); - } - isLoading && container[removeEl](loadingIcon); - container[removeEl](galleryCounter); - galleryOpen = galleryEls = false; - preloadedImages = {}; - supportsTouch || container[removeEl](rightArrowBtn); - supportsTouch || container[removeEl](leftArrowBtn); - // in case displayimage changed, we need to update event listeners - displayImage.onload = open; - displayImage.onerror = open.bind(null, 'image'); + // animate closing + displayElement.style.cssText += getRect(); + changeCSS(container, 'pointer-events:auto'); + + // timeout to remove els from dom; use variable to avoid calling more than once + setTimeout(removeContainer, 350); + + // clear animationEnd timeout + clearTimeout(animationEnd); + + isOpen = false; + isClosing = true; +} + +// remove container / display element from the DOM +function removeContainer() { + // clear src of displayElement (or iframe if display el is iframe container) + // needs to be done before removing container in IE + var srcEl = + displayElement === iframeContainer ? iframeSiteVid : displayElement; + srcEl.removeAttribute('src'); + + // remove container from DOM & clear inline style + document.body[removeEl](container); + container[removeEl](displayElement); + changeCSS(container, ''); + changeCSS(displayElement, ''); + + // remove caption + toggleCaption(false); + + if (galleryOpen) { + // remove all gallery stuff + var images = container.querySelectorAll('img'); + for (var i = 0; i < images.length; i++) { + container[removeEl](images[i]); } + isLoading && container[removeEl](loadingIcon); + container[removeEl](galleryCounter); + galleryOpen = galleryEls = false; + preloadedImages = {}; + supportsTouch || container[removeEl](rightArrowBtn); + supportsTouch || container[removeEl](leftArrowBtn); + // in case displayimage changed, we need to update event listeners + displayImage.onload = open; + displayImage.onerror = open.bind(null, 'image'); + } - // run close callback - opts.onClose && opts.onClose(); + // run close callback + opts.onClose && opts.onClose(); - isClosing = isLoading = false; - } + isClosing = isLoading = false; +} - // style helper functions - function changeCSS(element, newStyle) { - element.style.cssText = newStyle; - } -})(); +// style helper functions +function changeCSS(ref, newStyle) { + var style = ref.style; + + style.cssText = newStyle; +} + +module.exports = BigPicture; diff --git a/package.json b/package.json index 137ca55..ea1b953 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigpicture", - "version": "2.2.1", + "version": "2.3.0", "description": "Lightweight image and video viewer, supports youtube / vimeo", "main": "index.js", "scripts": { diff --git a/src/BigPicture.js b/src/BigPicture.js index e6ebd18..c593950 100644 --- a/src/BigPicture.js +++ b/src/BigPicture.js @@ -186,7 +186,7 @@ function initialize() { const el = document[createEl]('button') el.className = className el.innerHTML = - '' + '' return el } @@ -194,7 +194,7 @@ function initialize() { const el = document[createEl]('button') el.className = 'bp-lr' el.innerHTML = - '' + '' changeCSS(el, style) el.onclick = e => { e.stopPropagation() @@ -206,7 +206,7 @@ function initialize() { // add style - if you want to tweak, run through beautifier const style = document[createEl]('STYLE') style.innerHTML = - '#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:rgba(0,0,0,.7);opacity:0;transition:opacity .35s}#bp_loader{top:0;left:0;bottom:0;display:flex;align-items:center;cursor:wait;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:80px}#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 0;cursor:pointer;transition:all .1s}#bp_container>.bp-x{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 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}}' + '#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:rgba(0,0,0,.7);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{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}}' document.head[appendEl](style) // create container element @@ -274,7 +274,7 @@ function initialize() { loadingIcon = document[createEl]('DIV') loadingIcon.id = 'bp_loader' loadingIcon.innerHTML = - '' + '' // create youtube / vimeo container iframeContainer = document[createEl]('DIV') iframeContainer.id = 'bp_sv' @@ -527,39 +527,39 @@ function createIframe() { url = opts.iframeSrc } + // add loading spinner to iframe container + changeCSS(loadingIcon, '') + iframeContainer[appendEl](loadingIcon) + // set iframe src to url iframeSiteVid.src = url updateIframeDimensions() - setTimeout(() => { - changeCSS(loadingIcon, '') - iframeContainer[appendEl](loadingIcon) - open() - }, 9) + setTimeout(open, 9) } function updateIframeDimensions() { let height let width + // handle height / width / aspect / max width for iframe const windowHeight = window.innerHeight * 0.95 const windowWidth = window.innerWidth * 0.95 const windowAspect = windowHeight / windowWidth - const dimensions = opts.dimensions || [1600, 900] - let dimensionHeight = dimensions[1] - let dimensionWidth = dimensions[0] + const [dimensionWidth, dimensionHeight] = opts.dimensions || [1920, 1080] const iframeAspect = dimensionHeight / dimensionWidth if (iframeAspect > windowAspect) { - height = Math.min(dimensions[1], windowHeight) - width = (dimensions[0] / dimensions[1]) * height + height = Math.min(dimensionHeight, windowHeight) + width = height / iframeAspect } else { - width = Math.min(dimensions[0], windowWidth) - height = (dimensions[1] / dimensions[0]) * width + width = Math.min(dimensionWidth, windowWidth) + height = width * iframeAspect } + iframeContainer.style.cssText += `width:${width}px;height:${height}px;` } @@ -688,18 +688,18 @@ function close(e) { // remove container / display element from the DOM function removeContainer() { + // clear src of displayElement (or iframe if display el is iframe container) + // needs to be done before removing container in IE + let srcEl = + displayElement === iframeContainer ? iframeSiteVid : displayElement + srcEl.removeAttribute('src') + // remove container from DOM & clear inline style document.body[removeEl](container) container[removeEl](displayElement) changeCSS(container, '') changeCSS(displayElement, '') - // clear src of displayElement (or iframe if display el is iframe container) - ;(displayElement === iframeContainer - ? iframeSiteVid - : displayElement - ).removeAttribute('src') - // remove caption toggleCaption(false)