100% mobile optimized - Touch-friendly Combination of swiper.js slider/carousel and photoswipe image gallery lightbox - for the best native look on mobile devices.
A Pen by Ezra Siton Web & Graphic Designer on CodePen.
| <h1> | |
| <a title="swiper.js" href="http://idangero.us/swiper/" target="_blank">Swiper.js (4.3.5)</a> | |
| & | |
| <a title="photoswipe" href="http://photoswipe.com/" target="_blank">Photoswipe.js (4.1.1)</a> | |
| - Mobile Native feel slider gallery | |
| </h1> | |
| <p>Combine two of the most powerfull JS plugins (Endless options / Great docs / Fast / Modern / Mobile freindly) - <a title="swiper.js" href="http://idangero.us/swiper/" target="_blank">SWIPER</a> IS PERFECT FOR THIS IDEA BEACUSE OF ITS unique <code>preventClicks</code> Parameter (Prevent accidental unwanted clicks on links during swiping) - <strong>Works like magic</strong>. Also its really <b>hard</b> to find - Code example of working photoswipe combination with any slider out there(slick, flickity, owl etc.) and in general slider & lightbox - so i hope this example be usefull for you.</p> | |
| <!-- Slider main container --> | |
| <div class="swiper-container"> | |
| <!-- Additional required wrapper --> | |
| <ul class="swiper-wrapper my-gallery" itemscope itemtype="http://schema.org/ImageGallery"> | |
| <!-- Slides --> | |
| <li class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject"> | |
| <a title="click to zoom-in" href="http://placehold.it/1200x600/EC407A/ffffff?text=Zoom-image-1" itemprop="contentUrl" data-size="1200x600"> | |
| <img src="http://placehold.it/600x300/EC407A/ffffff?text=Thumbnail-image-1" itemprop="thumbnail" alt="Image description" /> | |
| </a> </li> | |
| <li class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject"> | |
| <a title="click to zoom-in" href="http://placehold.it/1200x600/AB47BC/ffffff?text=Zoom-image-2" itemprop="contentUrl" data-size="1200x600"> | |
| <img src="http://placehold.it/600x300/AB47BC/ffffff?text=Thumbnail-image-2" itemprop="thumbnail" alt="Image description" /> | |
| </a> | |
| </li> | |
| <li class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject"> | |
| <a title="click to zoom-in" href="http://placehold.it/1200x600/EF5350/ffffff?text=Zoom-image-3" itemprop="contentUrl" data-size="1200x600"> | |
| <img src="http://placehold.it/600x300/EF5350/ffffff?text=Thumbnail-image-3" itemprop="thumbnail" alt="Image description" /> | |
| </a> | |
| </li> | |
| <li class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject"> | |
| <a title="click to zoom-in" href="http://placehold.it/1200x600/1976D2/ffffff?text=Zoom-image-4" itemprop="contentUrl" data-size="1200x600"> | |
| <img src="http://placehold.it/600x300/1976D2/ffffff?text=Thumbnail-image-4" itemprop="thumbnail" alt="Image description" /> | |
| </a> | |
| </li> | |
| <li class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject"> | |
| <a title="click to zoom-in" href="https://picsum.photos/1200/603" itemprop="contentUrl" data-size="1200x600"> | |
| <img src="https://picsum.photos/1200/603" itemprop="thumbnail" alt="Image description" /> | |
| </a> | |
| </li> | |
| </ul> | |
| <!-- Add Pagination --> | |
| <div class="swiper-pagination"></div> | |
| <!-- If we need navigation buttons --> | |
| <div class="swiper-button-prev"></div> | |
| <div class="swiper-button-next"></div> | |
| </div> | |
| <!-- Root element of PhotoSwipe. Must have class pswp. --> | |
| <div class="pswp" tabindex="-1" role="dialog" aria-hidden="true"> | |
| <!-- Background of PhotoSwipe. | |
| It's a separate element, as animating opacity is faster than rgba(). --> | |
| <div class="pswp__bg"></div> | |
| <!-- Slides wrapper with overflow:hidden. --> | |
| <div class="pswp__scroll-wrap"> | |
| <!-- Container that holds slides. PhotoSwipe keeps only 3 slides in DOM to save memory. --> | |
| <!-- don't modify these 3 pswp__item elements, data is added later on. --> | |
| <div class="pswp__container"> | |
| <div class="pswp__item"></div> | |
| <div class="pswp__item"></div> | |
| <div class="pswp__item"></div> | |
| </div> | |
| <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. --> | |
| <div class="pswp__ui pswp__ui--hidden"> | |
| <div class="pswp__top-bar"> | |
| <!-- Controls are self-explanatory. Order can be changed. --> | |
| <div class="pswp__counter"></div> | |
| <button class="pswp__button pswp__button--close" title="Close (Esc)"></button> | |
| <button class="pswp__button pswp__button--share" title="Share"></button> | |
| <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button> | |
| <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button> | |
| <!-- Preloader demo https://codepen.io/dimsemenov/pen/yyBWoR --> | |
| <!-- element will get class pswp__preloader--active when preloader is running --> | |
| <div class="pswp__preloader"> | |
| <div class="pswp__preloader__icn"> | |
| <div class="pswp__preloader__cut"> | |
| <div class="pswp__preloader__donut"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap"> | |
| <div class="pswp__share-tooltip"></div> | |
| </div> | |
| <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"> | |
| </button> | |
| <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"> | |
| </button> | |
| <div class="pswp__caption"> | |
| <div class="pswp__caption__center"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- //////////////////////// | |
| DOCS | |
| //////////////////////////// | |
| --> | |
| <section id="docs"> | |
| <br> | |
| <br> | |
| <br> | |
| <hr> | |
| <h2>Noted / Important</h2> | |
| <ol> | |
| <li> | |
| <h3>Loop & Counter</h3> | |
| <p> | |
| <strong> Wont work well </strong> with swiper: <code>loop = true;</code> & photoswipe: <code>counterEl: true,</code>(What is counter? example: 1/5...2/5) - "loop" duplicate images - the photoswipe counter will be wrong. *** If you dont want a | |
| loop - you can set photoswipe counter <code>counterEl: true,</code> | |
| </p> | |
| </li> | |
| <li> | |
| <h3>Markup</h3> | |
| <p> | |
| <a href="">Schema.org</a> markup + semantic HTML: use unordered (bulleted) list (If you want a div change JS - <strong>"(find) control+f-->"</strong> tagname value) . Copy-paste - this code to check: <a target="_blank" href="https://search.google.com/structured-data/testing-tool">Structured Data Testing Tool - Google</a> | |
| </p> | |
| </li> | |
| <li> | |
| <h3>Match index</h3> | |
| <p> | |
| <strong> | |
| Extra CODE "match index" | |
| </strong> - EXAMPLE: When you click(zoom) image1 -- goes to image 2 - close image2 (X) - also the swiper update is position (BETTER User Experience) (find(ctr +f)--> <code>mySwiper.slideTo(getCurrentIndex, false);</code>) - This idea miss | |
| in most slider & lightbox examples/plugins mixed. | |
| </p> | |
| </li> | |
| <li> | |
| <h3>Photoswipe options</h3> | |
| <p> | |
| JS - line (find) -ctr +f --> the term:<code>// define options (if needed)</code>. You find endless options for <strong>photoswipe</strong> - This is the place to add/modify options. Full Options list her | |
| <a href="http://photoswipe.com/documentation/options.html" target="_blank">PhotoSwipe | |
| Options</a> | |
| </p> | |
| </li> | |
| <li> | |
| <h3>SWIPER options</h3> | |
| <h4>slideperview</h4> | |
| <p> | |
| <code>slideperview</code> - option1: Set number (1,2,3 and so on) <a href="http://idangero.us/swiper/demos/110-slides-per-view.html" target="_blank"> - example </a> ||||| option2(<b>"Carousel Mode"</b> this example): Set to "<code>auto</code>" | |
| than add CSS <a href="https://www.w3schools.com/cssref/pr_dim_width.asp" target="_blank">width Property</a></code> <code>.swiper-slide</code> (in thie case eash slide is 88% width) - <a href="http://idangero.us/swiper/demos/120-slides-per-view-auto.html" | |
| target="_blank">example</a>. | |
| </p> | |
| <h4>spaceBetween & centeredSlides</h4> | |
| <p> | |
| Space Between slide by js option <code>spaceBetween</code> - and also usefull to change <code>centeredSlides</code>(true/flase). <br> | |
| <a href="http://idangero.us/swiper/api/" target="_blank">Swiper API</a> | |
| </p> | |
| </li> | |
| <li> | |
| <h3> | |
| A non-jQuery dependent | |
| </h3> | |
| </li> | |
| </ol> | |
| <hr> | |
| <h3>Related Example</h3> | |
| <p> | |
| <a title="FancyBox3 & Flickity" href="https://codepen.io/ezra_siton/pen/OQmjoq" target="_blank">#FancyBox3 - lightbox & Flickity Slider</a> | |
| </p> | |
| </section> | |
| /* 1 of 2 : SWIPER */ | |
| var mySwiper = new Swiper(".swiper-container", { | |
| // If loop true set photoswipe - counterEl: false | |
| loop: true, | |
| /* slidesPerView || auto - if you want to set width by css like flickity.js layout - in this case width:80% by CSS */ | |
| slidesPerView: "auto", | |
| spaceBetween: 7, | |
| centeredSlides: true, | |
| // If we need pagination | |
| pagination: { | |
| el: ".swiper-pagination", | |
| clickable: true, | |
| renderBullet: function(index, className) { | |
| return '<span class="' + className + '">' + (index + 1) + "</span>"; | |
| } | |
| }, | |
| // Navigation arrows | |
| navigation: { | |
| nextEl: '.swiper-button-next', | |
| prevEl: '.swiper-button-prev', | |
| } | |
| }); | |
| // 2 of 2 : PHOTOSWIPE | |
| var initPhotoSwipeFromDOM = function(gallerySelector) { | |
| // parse slide data (url, title, size ...) from DOM elements | |
| // (children of gallerySelector) | |
| var parseThumbnailElements = function(el) { | |
| var thumbElements = el.childNodes, | |
| numNodes = thumbElements.length, | |
| items = [], | |
| figureEl, | |
| linkEl, | |
| size, | |
| item; | |
| for (var i = 0; i < numNodes; i++) { | |
| figureEl = thumbElements[i]; // <figure> element | |
| // include only element nodes | |
| if (figureEl.nodeType !== 1) { | |
| continue; | |
| } | |
| linkEl = figureEl.children[0]; // <a> element | |
| size = linkEl.getAttribute("data-size").split("x"); | |
| // create slide object | |
| item = { | |
| src: linkEl.getAttribute("href"), | |
| w: parseInt(size[0], 10), | |
| h: parseInt(size[1], 10) | |
| }; | |
| if (figureEl.children.length > 1) { | |
| // <figcaption> content | |
| item.title = figureEl.children[1].innerHTML; | |
| } | |
| if (linkEl.children.length > 0) { | |
| // <img> thumbnail element, retrieving thumbnail url | |
| item.msrc = linkEl.children[0].getAttribute("src"); | |
| } | |
| item.el = figureEl; // save link to element for getThumbBoundsFn | |
| items.push(item); | |
| } | |
| return items; | |
| }; | |
| // find nearest parent element | |
| var closest = function closest(el, fn) { | |
| return el && (fn(el) ? el : closest(el.parentNode, fn)); | |
| }; | |
| // triggers when user clicks on thumbnail | |
| var onThumbnailsClick = function(e) { | |
| e = e || window.event; | |
| e.preventDefault ? e.preventDefault() : (e.returnValue = false); | |
| var eTarget = e.target || e.srcElement; | |
| // find root element of slide | |
| var clickedListItem = closest(eTarget, function(el) { | |
| return el.tagName && el.tagName.toUpperCase() === "LI"; | |
| }); | |
| if (!clickedListItem) { | |
| return; | |
| } | |
| // find index of clicked item by looping through all child nodes | |
| // alternatively, you may define index via data- attribute | |
| var clickedGallery = clickedListItem.parentNode, | |
| childNodes = clickedListItem.parentNode.childNodes, | |
| numChildNodes = childNodes.length, | |
| nodeIndex = 0, | |
| index; | |
| for (var i = 0; i < numChildNodes; i++) { | |
| if (childNodes[i].nodeType !== 1) { | |
| continue; | |
| } | |
| if (childNodes[i] === clickedListItem) { | |
| index = nodeIndex; | |
| break; | |
| } | |
| nodeIndex++; | |
| } | |
| if (index >= 0) { | |
| // open PhotoSwipe if valid index found | |
| openPhotoSwipe(index, clickedGallery); | |
| } | |
| return false; | |
| }; | |
| // parse picture index and gallery index from URL (#&pid=1&gid=2) | |
| var photoswipeParseHash = function() { | |
| var hash = window.location.hash.substring(1), | |
| params = {}; | |
| if (hash.length < 5) { | |
| return params; | |
| } | |
| var vars = hash.split("&"); | |
| for (var i = 0; i < vars.length; i++) { | |
| if (!vars[i]) { | |
| continue; | |
| } | |
| var pair = vars[i].split("="); | |
| if (pair.length < 2) { | |
| continue; | |
| } | |
| params[pair[0]] = pair[1]; | |
| } | |
| if (params.gid) { | |
| params.gid = parseInt(params.gid, 10); | |
| } | |
| return params; | |
| }; | |
| var openPhotoSwipe = function( | |
| index, | |
| galleryElement, | |
| disableAnimation, | |
| fromURL | |
| ) { | |
| var pswpElement = document.querySelectorAll(".pswp")[0], | |
| gallery, | |
| options, | |
| items; | |
| items = parseThumbnailElements(galleryElement); | |
| // define options (if needed) | |
| options = { | |
| /* "showHideOpacity" uncomment this If dimensions of your small thumbnail don't match dimensions of large image */ | |
| //showHideOpacity:true, | |
| // Buttons/elements | |
| closeEl: true, | |
| captionEl: true, | |
| fullscreenEl: true, | |
| zoomEl: true, | |
| shareEl: true, | |
| counterEl: false, | |
| arrowEl: true, | |
| preloaderEl: true, | |
| // define gallery index (for URL) | |
| galleryUID: galleryElement.getAttribute("data-pswp-uid"), | |
| getThumbBoundsFn: function(index) { | |
| // See Options -> getThumbBoundsFn section of documentation for more info | |
| var thumbnail = items[index].el.getElementsByTagName("img")[0], // find thumbnail | |
| pageYScroll = | |
| window.pageYOffset || document.documentElement.scrollTop, | |
| rect = thumbnail.getBoundingClientRect(); | |
| return { x: rect.left, y: rect.top + pageYScroll, w: rect.width }; | |
| } | |
| }; | |
| // PhotoSwipe opened from URL | |
| if (fromURL) { | |
| if (options.galleryPIDs) { | |
| // parse real index when custom PIDs are used | |
| // http://photoswipe.com/documentation/faq.html#custom-pid-in-url | |
| for (var j = 0; j < items.length; j++) { | |
| if (items[j].pid == index) { | |
| options.index = j; | |
| break; | |
| } | |
| } | |
| } else { | |
| // in URL indexes start from 1 | |
| options.index = parseInt(index, 10) - 1; | |
| } | |
| } else { | |
| options.index = parseInt(index, 10); | |
| } | |
| // exit if index not found | |
| if (isNaN(options.index)) { | |
| return; | |
| } | |
| if (disableAnimation) { | |
| options.showAnimationDuration = 0; | |
| } | |
| // Pass data to PhotoSwipe and initialize it | |
| gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options); | |
| gallery.init(); | |
| /* EXTRA CODE (NOT FROM THE CORE) - UPDATE SWIPER POSITION TO THE CURRENT ZOOM_IN IMAGE (BETTER UI) */ | |
| // photoswipe event: Gallery unbinds events | |
| // (triggers before closing animation) | |
| gallery.listen("unbindEvents", function() { | |
| // This is index of current photoswipe slide | |
| var getCurrentIndex = gallery.getCurrentIndex(); | |
| // Update position of the slider | |
| mySwiper.slideTo(getCurrentIndex, false); | |
| }); | |
| }; | |
| // loop through all gallery elements and bind events | |
| var galleryElements = document.querySelectorAll(gallerySelector); | |
| for (var i = 0, l = galleryElements.length; i < l; i++) { | |
| galleryElements[i].setAttribute("data-pswp-uid", i + 1); | |
| galleryElements[i].onclick = onThumbnailsClick; | |
| } | |
| // Parse URL and open gallery if it contains #&pid=3&gid=1 | |
| var hashData = photoswipeParseHash(); | |
| if (hashData.pid && hashData.gid) { | |
| openPhotoSwipe(hashData.pid, galleryElements[hashData.gid - 1], true, true); | |
| } | |
| }; | |
| // execute above function | |
| initPhotoSwipeFromDOM(".my-gallery"); |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.1/photoswipe.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.1/photoswipe-ui-default.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.3.5/js/swiper.min.js"></script> |
100% mobile optimized - Touch-friendly Combination of swiper.js slider/carousel and photoswipe image gallery lightbox - for the best native look on mobile devices.
A Pen by Ezra Siton Web & Graphic Designer on CodePen.
| /*================================== | |
| SWIPER | |
| ===================================*/ | |
| /* remove bullet and space from the list */ | |
| ul.swiper-wrapper { | |
| list-style-type: none; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| /* Swiper styles */ | |
| .swiper-container { | |
| max-width: 100%; | |
| } | |
| /* responive image */ | |
| .swiper-container img { | |
| width: 100%; | |
| height: auto; | |
| } | |
| .swiper-slide { | |
| text-align: center; | |
| /* Remove this if you want 1 slide perview - than change slidesPerView js-option to 1 -or- 2+ instead of 'auto' */ | |
| width: 80%; | |
| } | |
| /* Swiper custom pagination */ | |
| .swiper-pagination-bullet { | |
| width: 34px; | |
| height: 34px; | |
| text-align: center; | |
| line-height: 34px; | |
| font-size: 14px; | |
| color: #000; | |
| opacity: 1; | |
| background: rgba(0, 0, 0, 0.3); | |
| } | |
| .swiper-pagination-bullet-active { | |
| color: #fff; | |
| background: black; | |
| } | |
| /*====================================================== | |
| CODEPEN STYLES - Remove this from your code | |
| =======================================================*/ | |
| .swiper-docs { | |
| font-family: "Roboto", sans-serif; | |
| } | |
| .swiper-docs #docs h3 { | |
| margin-bottom: 0px; | |
| webkit-margin-after: 0em; | |
| } | |
| .swiper-docs #docs ol li:not(:first-of-type) { | |
| border-top: thin solid rgba(203, 202, 204, 1); | |
| } | |
| .swiper-docs #docs p { | |
| -webkit-margin-before: 1em; | |
| line-height: 22px; | |
| font-size: 0.9em; | |
| } | |
| .swiper-docs section #docs code { | |
| padding: 0; | |
| padding: 3px 5px; | |
| margin: 0; | |
| background: #f2f2f2; | |
| border-radius: 2px; | |
| } | |
| .swiper-docs #docs ol li { | |
| margin-bottom: 12px; | |
| } | |
| .swiper-docs { | |
| max-width: 960px; | |
| margin: 0px auto; | |
| padding: 15px; | |
| } | |
| .swiper-docs a { | |
| color: #4285f4; | |
| } | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.1/photoswipe.min.css" rel="stylesheet" /> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.1/default-skin/default-skin.min.css" rel="stylesheet" /> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.3.5/css/swiper.min.css" rel="stylesheet" /> |
I moved these examples into Codepen to better explore this Gist https://codepen.io/bambii7/pen/mdOzame
Great work combining Swiper & Lightbox effect!