blueimp / Gallery

blueimp Gallery is a touch-enabled, responsive and customizable image & video gallery, carousel and lightbox, optimized for both mobile and desktop web browsers. It features swipe, mouse and keyboard navigation, transition effects, slideshow functionality, fullscreen support and on-demand content loading.
https://blueimp.github.io/Gallery/
Other
3.76k stars 984 forks source link

Is it possible to generate gallery with picture tag to use WebP images? #243

Closed pintergreg closed 4 years ago

pintergreg commented 4 years ago

I'm modifying my website to provide the images in WebP format as well, that requires a HTML code like this:

<picture>
  <source srcset="foo.webp">
  <img src="foo.jpg">
</picture>

It would be nice if the gallery could also show WebP images in browsers that support it. Can I somehow modify the generated HTML?

blueimp commented 4 years ago

Hi @pintergreg,

At the moment, only img srcset is supported (using the urlset or data-urlset property on your link objects), while providing multiple image media formats like WebP requires the picture tag as in your example.

You might be able to add support for this by implementing your own Gallery content factory.

Or better yet, you could determine browser support for WebP and then open the Gallery with image objects containing links to the images the browser supports: https://github.com/blueimp/Gallery#api

pintergreg commented 4 years ago

Hey @blueimp,

I've checked the textFactory, but to be honest I don't really understand yet how can I tell the Gallery to use a new factory and how should I modify it for my use case.

I use the HTML version of the API with the data-gallery attributes, so my use-case is very basic. I think I need to understand much more your code for this task, but when I'm done I plan to post my factory here for others who seek a similar solution.

blueimp commented 4 years ago

As another alternative, you could also use server-side content negotiation to serve WebP images.

However I think the best option is what I outlined above as second alternative, using WebP detection in JavaScript and then provide WebP image links for your Gallery.

Here's some example code to help you get started:

var img = document.createElement('img')
img.onload = function () {
  // Check if the browser can render the image:
  if (img.width > 0) {
    // Select all Gallery image links:
    document.querySelectorAll('[data-gallery]').forEach(function (a) {
      // Replace JPEG links with WebP links:
      a.href = a.href.replace(/\.jpe?g$/, '.webp')
    })
  }
}
// Lossless encoded WebP image as data URL:
img.src = 'data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA=='
pintergreg commented 4 years ago

I only want to modify the generated HTML to use a picture tag. I do want to let the browser decide which source will it use. Partly because I think this is the clearest, partly because I don't want any mod_rewrite and stuff.

For now, I've overwritten the imageFacory:

                blueimp.Gallery.prototype.picturePrototype = document.createElement('picture');
                blueimp.Gallery.prototype.webpPrototype = document.createElement('source');
                blueimp.Gallery.prototype.imageFactory = function (obj, callback) {
                  var that = this
                  var picture = this.picturePrototype.cloneNode(false)
                  var webp = this.webpPrototype.cloneNode(false)
                  var img = this.imagePrototype.cloneNode(false)
                  var url = obj
                  var backgroundSize = this.options.stretchImages
                  var called
                  var element
                  var title
                  var altText
                  /**
                   * Wraps the callback function for the load/error event
                   *
                   * @param {event} event load/error event
                   * @returns {number} timeout ID
                   */
                  function callbackWrapper(event) {
                    if (!called) {
                      event = {
                        type: event.type,
                        target: element
                      }
                      if (!element.parentNode) {
                        // Fix for IE7 firing the load event for
                        // cached images before the element could
                        // be added to the DOM:
                        return that.setTimeout(callbackWrapper, [event])
                      }
                      called = true
                      $(img).off('load error', callbackWrapper)
                      if (backgroundSize) {
                        if (event.type === 'load') {
                          element.style.background = 'url("' + url + '") center no-repeat'
                          element.style.backgroundSize = backgroundSize
                        }
                      }
                      callback(event)
                    }
                  }
                  if (typeof url !== 'string') {
                    url = this.getItemProperty(obj, this.options.urlProperty)
                    title = this.getItemProperty(obj, this.options.titleProperty)
                    //altText =
                      //this.getItemProperty(obj, this.options.altTextProperty) || title
                  }
                  if (backgroundSize === true) {
                    backgroundSize = 'contain'
                  }
                  backgroundSize =
                    this.support.backgroundSize &&
                    this.support.backgroundSize[backgroundSize] &&
                    backgroundSize
                  if (backgroundSize) {
                    element = this.elementPrototype.cloneNode(false)
                  } else {
                    element = picture
                    //element = img
                    picture.append(webp)
                    picture.append(img)
                    element.draggable = false
                  }
                  if (title) {
                    element.title = title
                  }
                  if (altText) {
                    element.alt = altText
                  }
                  $(img).on('load error', callbackWrapper)
                  img.src = url
                  webp.srcset = url.replace(/\.jpe?g$/, '.webp')
                  return element
                }

But I had to comment out the altText = this.getItemProperty(obj, this.options.altTextProperty) || title line because it caused some error that I don't understand. Currently I use an old version of your Gallery, I will update and test it with my modified imageFactory, but it looks pretty good so far.

The generated HTML is like this:

<div class="slide " data-index="2" style="width: 904px; left: -1808px; transition-duration: 400ms; transform: translate(0px) translateZ(0px);">
  <picture draggable="false" class="slide-content">
    <source srcset="http://localhost:1313/magazine/55/gallery/004.webp">
    <img src="http://localhost:1313/magazine/55/gallery/004.jpg">
  </picture>
</div>

The general solution would be something like introducing a data-webp-url tag that contains the URL for the webp image, which is good if someone want to store the images in a folder structure like this:

And append the source tag only if the data-webp-url is present. The picture is valid with a single img tag, so that should be okay is webp is not used. A more general solution would be using a data-picture-source1, data-picture-source2 etc. attributes to provide more sources, but I'm not sure that the data-picture-source attributes could be iterated as an array, so that might not even work...

blueimp commented 4 years ago

Support for images with multiple media type resources has been added with the latest commit. ☺️ It's gonna be part of the next major release.

The documentation has already been updated: https://github.com/blueimp/Gallery#responsive-images

s-gbz commented 4 years ago

I was facing the same issue. This is the solution I decided on (for now)

document.getElementById('masonryGrid').onclick = function (event) {
        event = event || window.event;
        const target = event.target || event.srcElement;
        const options = {
            index: currentIndex, event: event, preloadRange: 0
        };
        const links = useWebpImagesInGalleryIfSupported(target);

        blueimp.Gallery(links, options);
    }

    function useWebpImagesInGalleryIfSupported(target) {
        let links = [];

        // Safari doesn't support WebP - use jpg instead
        if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) {
            for (let i = 0; i < numberOfPhotos; i++) {
                links.push({ href: basePathFulls + i + ".jpg", thumbnail: basePathTinyThumbnails + i + ".gif" });
            }
        } 
       // Use webp on all other browsers
        else {
            for (let i = 0; i < numberOfPhotos; i++) {
                links.push({ href: basePathFulls + i + ".webp", thumbnail: basePathTinyThumbnails + i + ".gif" });
            }
        }

        return links;
    }

This is how I loop the picture tag with Vue

            <picture @click="setCurrentIndex(index)">
                <source loading="lazy" :srcset="basePathThumbnails + index + '.webp'" type="image/webp">
                <img loading="lazy" alt="Model Portrait Photography" :src="basePathThumbnails + index + '.jpg'"
                    type="image/jpg">
            </picture>

Maybe it helps someone 🤷 😌 Thanks for the awesome Gallery ❤️