├── .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 |
--------------------------------------------------------------------------------