├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── jekyll-srcset.gemspec └── lib ├── jekyll-srcset.rb └── jekyll ├── srcset.rb └── srcset ├── tag.rb └── version.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in jekyll-srcset.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Mathias Biilmann Christensen, MakerLoop Inc 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jekyll Srcset 2 | 3 | This Jekyll plugin makes it very easy to send larger images to devices with high pixel densities. 4 | 5 | The plugin adds an `image_tag` Liquid tag that can be used like this: 6 | 7 | ```html 8 | {% image_tag src="/image.png" width="100" %} 9 | ``` 10 | 11 | This will generate the right images and output something like: 12 | 13 | ```html 14 | 15 | ``` 16 | 17 | ## Installation 18 | 19 | Add this line to your Gemfile: 20 | 21 | ```ruby 22 | gem 'jekyll-srcset' 23 | ``` 24 | 25 | And then execute: 26 | 27 | $ bundle 28 | 29 | Or install it yourself as: 30 | 31 | $ gem install jekyll-srcset 32 | 33 | Then add the gem to your Jekyll `_config.yml`: 34 | 35 | ```yml 36 | gems: 37 | - jekyll-srcset 38 | ``` 39 | 40 | ## Usage 41 | 42 | Use it like this in any Liquid template: 43 | 44 | ```html 45 | {% image_tag src="/image.png" width="100" %} 46 | ``` 47 | 48 | You must specify either a `width` or a `height`, but never both. The width or height will be used to determine the smallest version of the image to use (for 1x pixel density devices). Based on this minimum size, the plugin will generate up to 3 versions of the image, one that matches the dimension specified, one that's twice the size and one that's 3 times the size. The plugin never upscales an image. 49 | 50 | The plugin sets these as a srcset attribute on the final image tag, and modern browsers will then use this information to determine which version of the image to load based on the pixel density of the device (and in the future, potentially based on bandwidth or user settings). 51 | 52 | This makes it a really straight forward way to serve the right size of image in all modern browsers and in works fine in older browsers without any polyfill (there's not a lot of high pixel density devices out there that runs old browsers, so simply serving the smallest version to the ones that don't understand srcset is fine). 53 | 54 | To use variables for the image or the dimensions, simply leave out the quotes: 55 | 56 | ```html 57 | {% image_tag src=page.cover_image height=page.cover_image_height %} 58 | ``` 59 | 60 | ## Optipng 61 | 62 | If you have `optipng` installed and in your PATH, you can tell the plugin to run it on all generated png images. 63 | 64 | Just add: 65 | 66 | ``` 67 | srcset: 68 | optipng: true 69 | ``` 70 | 71 | To your \_config.yml 72 | 73 | Currently the plugin doesn't optimize other image formats, except for stripping color palettes. 74 | 75 | ## Caching images 76 | 77 | Optimizing and resizing can take a while for some images. You can specify a cache folder in your Jekyll config to let jekyll-srcset cache images between runs. 78 | 79 | ``` 80 | srcset: 81 | cache: "/tmp/images" 82 | ``` 83 | 84 | ## Contributing 85 | 86 | 1. Fork it ( https://github.com/[my-github-username]/jekyll-srcset/fork ) 87 | 2. Create your feature branch (`git checkout -b my-new-feature`) 88 | 3. Commit your changes (`git commit -am 'Add some feature'`) 89 | 4. Push to the branch (`git push origin my-new-feature`) 90 | 5. Create a new Pull Request 91 | 92 | ## Host your Jekyll sites with netlify 93 | 94 | This plugin is developed by netlify, [the premium hosting for static websites](https://www.netlify.com). 95 | 96 | You can use netlify if you're using custom plugins with your Jekyll sites. When you push to git, netlify will build your Jekyll site and deploy the result to a global CDN, while handling asset fingerprinting, caching headers, bundling, minification and true instant cache invalidation. A 1-click operation with no other setup needed. 97 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /jekyll-srcset.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'jekyll/srcset/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "jekyll-srcset" 8 | spec.version = Jekyll::Srcset::VERSION 9 | spec.authors = ["Mathias Biilmann Christensen"] 10 | spec.email = ["info@mathias-biilmann.net"] 11 | spec.summary = %q{This Jekyll plugin ads an image_tag that will generate responsive img tags} 12 | spec.description = %q{ 13 | This Jekyll plugin makes it very easy to send larger images to devices with high pixel densities. 14 | 15 | The plugin adds an `image_tag` Liquid tag that can be used like this: 16 | 17 | \{% image_tag src="/image.png" width="100" %\} 18 | } 19 | spec.homepage = "https://github.com/netlify/jekyll-srcset" 20 | spec.license = "MIT" 21 | 22 | spec.files = `git ls-files -z`.split("\x0") 23 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 24 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 25 | spec.require_paths = ["lib"] 26 | 27 | spec.add_dependency "rmagick" 28 | 29 | spec.add_development_dependency "bundler", "~> 1.7" 30 | spec.add_development_dependency "rake", "~> 10.0" 31 | spec.add_runtime_dependency 'jekyll', '> 2' 32 | end 33 | -------------------------------------------------------------------------------- /lib/jekyll-srcset.rb: -------------------------------------------------------------------------------- 1 | require "jekyll/srcset" 2 | 3 | Liquid::Template.register_tag('image_tag', Jekyll::SrcsetTag) 4 | -------------------------------------------------------------------------------- /lib/jekyll/srcset.rb: -------------------------------------------------------------------------------- 1 | require "jekyll/srcset/version" 2 | require "jekyll/srcset/tag" 3 | require "jekyll" 4 | 5 | module Jekyll 6 | module Srcset 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/jekyll/srcset/tag.rb: -------------------------------------------------------------------------------- 1 | require "RMagick" 2 | require "digest/sha1" 3 | 4 | module Jekyll 5 | class SrcsetTag < Liquid::Tag 6 | include Magick 7 | attr_accessor :markup 8 | 9 | def self.optipng? 10 | @optinpng ||= system("which optipng") 11 | end 12 | 13 | def initialize(tag_name, markup, _) 14 | @markup = markup 15 | super 16 | end 17 | 18 | def render(context) 19 | options = parse_options(markup, context) 20 | 21 | return "Bad options to image_tag, syntax is: {% image_tag src=\"image.png\" width=\"100\"}" unless options["src"] 22 | return "Error resizing - can't set both width and height" if options["width"] && options["height"] 23 | 24 | site = context.registers[:site] 25 | img_attrs = generate_image(site, options["src"], options) 26 | 27 | srcset = [] 28 | (1..3).each do |factor| 29 | srcset << {:factor => factor, :img => generate_image(site, options["src"], options.merge("factor" => factor))} 30 | end 31 | img_attrs["srcset"] = srcset.map {|i| "#{i[:img]["src"]} #{i[:factor]}x"}.join(", ") 32 | 33 | "" 34 | end 35 | 36 | def parse_options(markup, context) 37 | options = {} 38 | markup.scan(/(\w+)=((?:"[^"]+")|(?:'[^']+')|[\w\.\_-]+)/) do |key,value| 39 | if (value[0..0] == "'" && value[-1..-1]) == "'" || (value[0..0] == '"' && value[-1..-1] == '"') 40 | options[key] = value[1..-2] 41 | else 42 | options[key] = context[value] 43 | end 44 | end 45 | options 46 | end 47 | 48 | def config(site) 49 | site.config['srcset'] || {} 50 | end 51 | 52 | def optimize?(site) 53 | config(site)['optipng'] 54 | end 55 | 56 | def cache_dir(site) 57 | config(site)['cache'] 58 | end 59 | 60 | def generate_image(site, src, attrs) 61 | cache = cache_dir(site) 62 | sha = cache && Digest::SHA1.hexdigest(attrs.sort.inspect + File.read(File.join(site.source, src)) + (optimize?(site) ? "optimize" : "")) 63 | if sha 64 | if File.exists?(File.join(cache, sha)) 65 | img_attrs = JSON.parse(File.read(File.join(cache,sha,"json"))) 66 | filename = img_attrs["src"].sub(/^\//, '') 67 | dest = File.join(site.dest, filename) 68 | FileUtils.mkdir_p(File.dirname(dest)) 69 | FileUtils.cp(File.join(cache,sha,"img"), dest) 70 | 71 | site.config['keep_files'] << filename unless site.config['keep_files'].include?(filename) 72 | 73 | return img_attrs 74 | end 75 | end 76 | 77 | img = Image.read(File.join(site.source, src)).first 78 | img_attrs = {} 79 | 80 | if attrs["height"] 81 | scale = attrs["height"].to_f * (attrs["factor"] || 1) / img.rows.to_f 82 | elsif attrs["width"] 83 | scale = attrs["width"].to_f * (attrs["factor"] || 1) / img.columns.to_f 84 | else 85 | scale = attrs["factor"] || 1 86 | end 87 | 88 | img_attrs["height"] = attrs["height"] if attrs["height"] 89 | img_attrs["width"] = attrs["width"] if attrs["width"] 90 | img_attrs["src"] = src.sub(/(\.\w+)$/, "-#{img.columns}x#{img.rows}" + '\1') 91 | 92 | filename = img_attrs["src"].sub(/^\//, '') 93 | dest = File.join(site.dest, filename) 94 | FileUtils.mkdir_p(File.dirname(dest)) 95 | 96 | unless File.exist?(dest) 97 | img.scale!(scale) if scale <= 1 98 | img.strip! 99 | img.write(dest) 100 | if dest.match(/\.png$/) && optimize?(site) && self.class.optipng? 101 | `optipng #{dest}` 102 | end 103 | end 104 | site.config['keep_files'] << filename unless site.config['keep_files'].include?(filename) 105 | # Keep files around for incremental builds in Jekyll 3 106 | site.regenerator.add(filename) if site.respond_to?(:regenerator) 107 | 108 | if sha 109 | FileUtils.mkdir_p(File.join(cache, sha)) 110 | FileUtils.cp(dest, File.join(cache, sha, "img")) 111 | File.open(File.join(cache, sha, "json"), "w") do |f| 112 | f.write(JSON.generate(img_attrs)) 113 | end 114 | end 115 | 116 | img_attrs 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/jekyll/srcset/version.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module Srcset 3 | VERSION = "0.1.3" 4 | end 5 | end 6 | --------------------------------------------------------------------------------