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!