Jekyll Lightbox

:camera: Photos on this page by Earvin Huang on Unsplash.

Single image lightbox

Jekyll makes it easy to embed single inline or block level images into a blog post using the standard Markdown image syntax.

However, what if we want to highlight an image or a gallery in a modal? Since Jekyll is a static site generator, we obviously need to extend our page with some scripting for this to work. The Lightbox2 library is a good choice since it has minimal dependencies and is very mature.

Installing Lightbox2 on a Jekyll website

In order to include Lightbox2, we need to

  1. download scripts and style sources,
  2. add them to page heads, and
  3. properly tag images which are meant to be shown in a modal.

The sources are available directly from Lightbox2 project’s GitHub page:

To download and add them to Jekyll’s scripts, respectively stylesheet assets, run the following commands from the root of your Jekyll project.

curl https://raw.githubusercontent.com/lokesh/lightbox2/dev/dist/js/lightbox-plus-jquery.min.js > assets/scripts/lightbox-plus-jquery.js
curl https://raw.githubusercontent.com/lokesh/lightbox2/dev/dist/css/lightbox.min.css > assets/stylesheets/lightbox.css

Note that Lightbox2 requires jQuery. Conveniently, the project authors provide a bundled script including both their library code and a compatible jQuery version.

To use the library on a Jekyll page, we have to include the script and style sources in the page head template. I.e. add the following to _includes/page.html:

  <!-- Add scripts/style for Lightbox2 -->
  <script src="/assets/scripts/lightbox-plus-jquery.js"></script>
  <link rel="stylesheet" href="/assets/stylesheets/lightbox.css">

Any single anchor element with a data-lightbox attribute will now have a click handler and show the image in the Lightbox2 modal. For example

<p>
  <a href="/assets/images/2021-30-12_jekyll-lightbox_sample-image.jpeg" data-lightbox="sample single image">
    <img src="/assets/images/2021-30-12_jekyll-lightbox_sample-image.jpeg" alt="lightbox-sample-image" title="Lightbox sample image">
  </a> 
</p>

will render as follows (click to open in lightbox modal).

lightbox-sample-image

If the data-lightbox attribute is not present or empty, the link will not be disabled and simply redirect to the image file.

Adding a Liquid tag for automation

Now this already is pretty nice but it may get tedious always having to write the correct HTML to create the Lightbox2 image link. Luckily, AppFoundry provide a lightweight Ruby plugin called lightbox.rb to automate the generation of image links for Lightbox2.

To use the plugin, we simply download the Ruby file and copy it to the _plugins/ directory of our Jekyll project.

curl https://raw.githubusercontent.com/appfoundry/jekyll-lightbox/master/lightbox.rb > _plugins/lightbox.rb

Now we can write

{% lightbox /assets/images/2021-30-12_jekyll-lightbox_sample-image.png --alt="lightbox-sample-image" %}

to embed the image into our page.

However, if you do this with the original lightbox plugin, you will notice that the Lightbox2 modal doesn’t open and you are redirected to the image file instead. This is because the plugin doesn’t add a default—non-empty—value for the the data-lightbox attribute. Consequently, Lightbox2 doesn’t pick up on the image link and the Lightbox2 modal handler will not be registered.

There are two additional problems with the plugin that we can fix while we’re at it: the plugin will not

  1. allow external URLs, nor
  2. provide an option to wrap the image link in a wrapper element and style it.

To fix all of these problems, we change a few lines of code. First, we add parsing for parameters wrapper and wrapper-class

# Add --wrapper and --wrapper-class parameters to the lightbox liquid tag
if text =~ /--wrapper/i
  @wrapper = true
end
if text =~ /--wrapper-class="([^"]*)"/i 
  @wrapper = true 
  @wrapper_class = text.match(/--wrapper-class="([^"]*)"/i)[1]
end

which we will use to wrap the image link in a wrapper DIV element with the (optional) class. Note that if wrapper-class is given, wrapper is implied.

Moreover, we check for external URLs (simplified to checking for a http or https prefix on the path and thumb parameters)

# Keep external src and thumbSrc URLs as they are
src = is_external_url?(@path) ? @path : File.join(relative, @path == nil ? '' : @path);
thumbSrc = is_external_url?(@thumb) ? @thumb : File.join(relative, @thumb == nil ? '' : @thumb);

Lastly, if no data parameter is given, we make it default to the value of the path parameter.

# Check if data is given and otherwise set to default
data = @data.to_s.empty? ? @path : @data

The full code is shown below

# Jekyll Lightbox Plugin
#
# Bart Vandeweerdt | www.appfoundry.be
#
# Example usage: {% lightbox images/appfoundry.png --thumb="images/thumbs/appfoundry.png" --data="some data" --title="some title" --alt="some alt" --img-style="css styling" --class="yourclass"%}
module Jekyll
  class LightboxTag < Liquid::Tag

    def initialize(tag_name, text, token)
      super

      # The path to our image
      @path = Liquid::Template.parse(
        # Regex: split on first whitespace character while allowing double quoting for surrounding spaces in a file path
        text.split(/\s(?=(?:[^"]|"[^"]*")*$)/)[0].strip
      ).render(@context)

      # Defaults
      @title = ''
      @alt = ''
      @img_style = ''
      @class = ''
      @data = ''
      @thumb = @path
      @wrapper = false
      @wrapper_class = ''

      # Parse Options
      if text =~ /--title="([^"]*)"/i
        @title = text.match(/--title="([^"]*)"/i)[1]
      end
      if text =~ /--alt="([^"]*)"/i
        @alt = text.match(/--alt="([^"]*)"/i)[1]
      end
      if text =~ /--img-style="([^"]*)"/i
        @img_style = text.match(/--img-style="([^"]*)"/i)[1]
      end
      if text =~ /--class="([^"]*)"/i
        @class = text.match(/--class="([^"]*)"/i)[1]
      end
      if text =~ /--data="([^"]*)"/i
        @data = text.match(/--data="([^"]*)"/i)[1]
      end
      if text =~ /--thumb="([^"]*)"/i
        @thumb = text.match(/--thumb="([^"]*)"/i)[1]
      end
      # Add --wrapper and --wrapper-class parameters to the lightbox liquid tag
      if text =~ /--wrapper/i
        @wrapper = true
      end
      if text =~ /--wrapper-class="([^"]*)"/i 
        @wrapper = true 
        @wrapper_class = text.match(/--wrapper-class="([^"]*)"/i)[1]
      end
    end

    # Check if the given URL is external (simplified here to checking for http or https URL)
    def is_external_url?(url)
      url.match(/^https?:\/\/.*/)
    end

    def render(context)
      url = context.registers[:page]["url"]
      relative = "../" * (url.split("/").length-1)
      # Keep external src and thumbSrc URLs; check if data is given and otherwise set to default
      src = is_external_url?(@path) ? @path : File.join(relative, @path == nil ? '' : @path);
      thumbSrc = is_external_url?(@thumb) ? @thumb : File.join(relative, @thumb == nil ? '' : @thumb);
      data = @data.to_s.empty? ? @path : @data

      # Conditionally wrap in a div and pass wrapper class
      if @wrapper
        %{<div class="#{@wrapper_class}"><a href="#{src}" data-lightbox="#{data}" data-title="#{@title}"><img src="#{thumbSrc}" alt="#{@alt || @title}" class="#{@class}" style="#{@img_style}"/></a></div>}
      else
        %{<a href="#{src}" data-lightbox="#{data}" data-title="#{@title}"><img src="#{thumbSrc}" alt="#{@alt || @title}" class="#{@class}" style="#{@img_style}"/></a>}
      end
    end
  end
end

Liquid::Template.register_tag('lightbox', Jekyll::LightboxTag)

With these changes, we can now directly link to an external URL and wrap the image link in a wrapper with suitable class as follows. (Note the fancy box shadow. ;))

{% lightbox https://images.unsplash.com/photo-1553671972-15e7bef005d7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwcm9maWxlLXBhZ2V8MXx8fGVufDB8fHx8&auto=format&fit=crop&w=900&q=60 --thumb="https://images.unsplash.com/photo-1553671972-15e7bef005d7?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwcm9maWxlLXBhZ2V8MXx8fGVufDB8fHx8&auto=format&fit=crop&w=900&q=60" --wrapper-class="lightbox-thumbnail-container" --alt="lightbox-sample-image" %}

Galleries

Another nice feature of Lightbox2 is that image links with the same data-lightbox attribute value will automatically be grouped together in a gallery. In the LightboxTag liquitag, we can use the –data parameter to provide a data-lightbox attribute. If you click any of the links below, the Lightbox2 modal will allow you to navigate through the images using the arrow buttons or the left and right arrow keys on your keyboard.