//- PREVIOUS VERSION: https://codepen.io/mican/pen/awxmpY

- var placeholder = function(width, height) { return "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http://www.w3.org/2000/svg'%20viewBox%3D'0%200%20" + width + "%20" + height + "'%20%2F%3E" }

- var images = [{ id: '-gS54SWrHMg', width: 1000, height: 500 },{id: 'AU1rKyKPJco', width: 500, height: 1000, caption: 'Hello from Poland' },{ id: 'AXfDvKOawZQ' },{ id: 'gKlkZrsG_Pw', width: 1000, height: 500 },{ id: 'DVONaLgCRxo', width: 500, height: 1000 },{ id: 'o7txpYpxNLs', caption: 'Have a nice day' },{ id: 'ZsgUsl8GATg', width: 500, height: 1000, caption: 'This is a very very long description to show you it\'s possible to add something like this' },{ id: 'CkagyZJ88kE' },{ id: 'PpQ4-HOZ_8U', width: 1000, height: 500 },{ id: 'si7gjqJQj_8' },{ id: 'u0M0gyuexfE', width: 500, height: 1000 },{ id: 'aQcE3gDSSTY' },{ id: 'GkCafprWKRo', width: 500, height: 1000 },{ id: 'OFlzoTfpRdw' },{ id: 'YlFM0-LdHu8' },{ id: 'c_Tc9ZELeYw' }]


mixin gallery-item(id, width=500, height=500, caption)
  if height > width
    - var klass = 'vertical' 
  else if width > height
    - var klass = 'horizontal'

  figure.gallery-item(itemprop='associatedMedia', itemscope='', itemtype='http://schema.org/ImageObject', class=klass)
    a(href=`https://source.unsplash.com/${id}/${width*2}x${height*2}`, itemprop='contentUrl', data-size=`${width*2}x${height*2}`)
      img.lazyload.lazypreload.fadein(src=placeholder(width,height) data-src=`https://source.unsplash.com/${id}/${width}x${height}`, itemprop='thumbnail', alt='Image description')
    figcaption.gallery-caption(itemprop='caption description')= caption || 'Caption'


.gallery(itemscope='', itemtype='http://schema.org/ImageGallery')
  each image in images
    +gallery-item(image.id,image.width,image.height, image.caption)
    
    
// Root element of PhotoSwipe. Must have class pswp.
.pswp(tabindex='-1', role='dialog', aria-hidden='true')
  //
    Background of PhotoSwipe.
    It's a separate element as animating opacity is faster than rgba().
  .pswp__bg
  // Slides wrapper with overflow:hidden.
  .pswp__scroll-wrap
    //
      Container that holds slides.
      PhotoSwipe keeps only 3 of them in the DOM to save memory.
      Don't modify these 3 pswp__item elements, data is added later on.
    .pswp__container
      .pswp__item
      .pswp__item
      .pswp__item
    // Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed.
    .pswp__ui.pswp__ui--hidden
      .pswp__top-bar
        // Controls are self-explanatory. Order can be changed.
        .pswp__counter
        button.pswp__button.pswp__button--close(title='Close (Esc)')
        button.pswp__button.pswp__button--share(title='Share')
        button.pswp__button.pswp__button--fs(title='Toggle fullscreen')
        button.pswp__button.pswp__button--zoom(title='Zoom in/out')
        // Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR
        // element will get class pswp__preloader--active when preloader is running
        .pswp__preloader
          .pswp__preloader__icn
            .pswp__preloader__cut
              .pswp__preloader__donut
      .pswp__share-modal.pswp__share-modal--hidden.pswp__single-tap
        .pswp__share-tooltip
      button.pswp__button.pswp__button--arrow--left(title='Previous (arrow left)')
      button.pswp__button.pswp__button--arrow--right(title='Next (arrow right)')
      .pswp__caption
        .pswp__caption__center
View Compiled
%gallery-caption
  +absolute(bottom 4rem left 50%)
  transform: translate(-50%,0%)
  font-size: 12px
  +breakpoint($mobile)
    font-size: 14px
  color: rgba(white,0)
  padding: 1.25em 1.5em
  transition: all .2s ease
  font-weight: 600
  // min-width: 10rem
  // width: 50%
  // max-width: calc(100% - 6rem)

  line-height: 1.25
  text-align: center
  box-sizing: border-box
  pointer-events: none

  &:before, &:after
    content: ''
    +absolute(top right left bottom)
    background: rgba(black,1)
    width: 100%
    height: 100%
    transition: all .3s ease 0s
    z-index: -1

  &:before
    top: auto
    height: 3px
    transform: scale(0,1)
    transform-origin: bottom left
    transition-delay: .6s

  &:after
    transform: scale(1,0)
    transform-origin: bottom
    transition-delay: .3s


  &.visible
    color: rgba(white,1)
    text-shadow: 0 0 1px rgba(black,.2)
    transition: all .3s ease .3s
    &:before
      transform: scale(1,1)
      transition-delay: 0s
    &:after
      transform: scale(1,1)

  &:empty
    display: none

$corner: 1rem

%image-border
  +relative
  &:before, &:after
    content: ''
    +absolute(top right left bottom)
    border: 0 solid rgba(black,.1)
    transition: all .2s
    will-change: border
    z-index: 10

  &.active
    &:before
      border-width: .5rem
    &:after
      border-width: 2px
  
  &:after
    margin: $corner
    border: 2px solid rgba(white,.5)
    clip-path: polygon(0 calc(100% - #{$corner}), 0 100%, $corner 100%, $corner 0, 0 0, 0 $corner, 100% $corner, 100% 0, calc(100% - #{$corner}) 0, calc(100% - #{$corner}) 100%, 100% 100%, 100% calc(100% - #{$corner}))

  &:hover
    &:after
      transform: scale(.9)
      border-color: rgba(white,1)

%caption-outside
  background-color: black
  color: white
  padding: .75em 1em
  display: inline-block
  text-align: left


%gallery-grid
  +sans-serif-font
  width: 100%
  display: grid
  grid-template-rows: flow
  grid-auto-flow: dense

  +breakpoint(max-width $mobile - 1px)
    grid-template-columns: repeat(2, 1fr)
  +breakpoint($mobile $desktop - 1px)
    grid-template-columns: repeat(3, 1fr)
  +breakpoint($desktop $laptop - px)
    grid-template-columns: repeat(4, 1fr)
  +breakpoint($laptop $screen - 1px)
    grid-template-columns: repeat(5, 1fr)  
  +breakpoint($screen)
    grid-template-columns: repeat(6, 1fr)  
%gallery-item
  +relative
  background-color: rgba(black,.5)
  overflow: hidden
  
  img, a
    display: block

  &.vertical
    grid-row: span 2

  &.horizontal
    grid-column: span 2
    
.gallery
  @extend %gallery-grid


.gallery-item
  @extend %gallery-item
  
  a
    display: block
    @extend %image-border

    .lazy-images &.image-lazyloaded, html:not(.lazy-images) &
      @extend %image-border.active


.gallery-caption

  html:not(.touchevents) &
    @extend %gallery-caption

  
  .gallery-item:hover &
    @extend %gallery-caption.visible

  [class*=list] &, .gallery-size-thumbnail &
    display: none
View Compiled
initPhotoSwipeFromDOM = (gallerySelector) ->
  # parse slide data (url, title, size ...) from DOM elements 
  # (children of gallerySelector)

  parseThumbnailElements = (el) ->
    thumbElements = el.childNodes
    numNodes = thumbElements.length
    items = []
    figureEl = undefined
    linkEl = undefined
    size = undefined
    item = undefined
    i = 0
    while i < numNodes
      figureEl = thumbElements[i]
      # <figure> element
      # include only element nodes 
      if figureEl.nodeType != 1
        i++
        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
      i++
    items

  # find nearest parent element

  closest = (el, fn) ->
    el and (if fn(el) then el else closest(el.parentNode, fn))

  # triggers when user clicks on thumbnail

  onThumbnailsClick = (e) ->
    e = e or window.event
    if e.preventDefault then e.preventDefault() else (e.returnValue = false)
    eTarget = e.target or e.srcElement
    # find root element of slide
    clickedListItem = closest(eTarget, (el) ->
      el.tagName and el.tagName.toUpperCase() == 'FIGURE'
    )
    if !clickedListItem
      return
    # find index of clicked item by looping through all child nodes
    # alternatively, you may define index via data- attribute
    clickedGallery = clickedListItem.parentNode
    childNodes = clickedListItem.parentNode.childNodes
    numChildNodes = childNodes.length
    nodeIndex = 0
    index = undefined
    i = 0
    while i < numChildNodes
      if childNodes[i].nodeType != 1
        i++
        continue
      if childNodes[i] == clickedListItem
        index = nodeIndex
        break
      nodeIndex++
      i++
    if index >= 0
      # open PhotoSwipe if valid index found
      openPhotoSwipe index, clickedGallery
    false

  # parse picture index and gallery index from URL (#&pid=1&gid=2)

  photoswipeParseHash = ->
    hash = window.location.hash.substring(1)
    params = {}
    if hash.length < 5
      return params
    vars = hash.split('&')
    i = 0
    while i < vars.length
      if !vars[i]
        i++
        continue
      pair = vars[i].split('=')
      if pair.length < 2
        i++
        continue
      params[pair[0]] = pair[1]
      i++
    if params.gid
      params.gid = parseInt(params.gid, 10)
    params

  openPhotoSwipe = (index, galleryElement, disableAnimation, fromURL) ->
    pswpElement = document.querySelectorAll('.pswp')[0]
    gallery = undefined
    options = undefined
    items = undefined
    items = parseThumbnailElements(galleryElement)
    # define options (if needed)
    options =
      galleryUID: galleryElement.getAttribute('data-pswp-uid')
      getThumbBoundsFn: (index) ->
        # See Options -> getThumbBoundsFn section of documentation for more info
        thumbnail = items[index].el.getElementsByTagName('img')[0]
        pageYScroll = window.pageYOffset or document.documentElement.scrollTop
        rect = thumbnail.getBoundingClientRect()
        {
          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
        j = 0
        while j < items.length
          if items[j].pid == index
            options.index = j
            break
          j++
      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()
    return

  # loop through all gallery elements and bind events
  galleryElements = document.querySelectorAll(gallerySelector)
  i = 0
  l = galleryElements.length
  while i < l
    galleryElements[i].setAttribute 'data-pswp-uid', i + 1
    galleryElements[i].onclick = onThumbnailsClick
    i++
  # Parse URL and open gallery if it contains #&pid=3&gid=1
  hashData = photoswipeParseHash()
  if hashData.pid and hashData.gid
    openPhotoSwipe hashData.pid, galleryElements[hashData.gid - 1], true, true
  return

# execute above function
initPhotoSwipeFromDOM '.gallery'

# ---
# generated by js2coffee 2.2.0
View Compiled

External CSS

  1. https://codepen.io/mican/pen/xYpoWX.sass
  2. https://codepen.io/mican/pen/yoOYLZ.sass
  3. https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.2/photoswipe.css
  4. https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.2/default-skin/default-skin.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/lazysizes/4.0.2/lazysizes.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.2/photoswipe.js
  3. https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.2/photoswipe-ui-default.js