Middlemanでこのようなサムネイル画像を作る方法をご紹介します。このようなサムネイル画像をMiddlemanでも作ることができます。
サイドバーによくある小さなサムネイルももちろん可能です。
もくじ
サムネイルを生成するためのgem
Middlemanのサムネイル生成のgemで有名なのが次の2つです。
両方試したのですが、私は前者を採用しました。というのは、後者はサムネイルを生成する画像があるディレクトリを指定する必要があったためです。画像を特定のディレクトリにまとめている人はよいのですが、自分の場合は下のような記事ごとのディレクトリに画像を保存してありました。
source/articles/2016-11/02-tom-yum-goong-noodle/images/
なので、保存ディレクトリを気にしなくてよい、前者を採用しました。
middleman-simple-thumbnailerの使い方
導入方法
Gemfile
に以下を追記しbundle install
を実行します。
1 |
gem 'middleman-simple-thumbnailer' |
config.rb
に以下を追記すれば使えるようになります。
1 |
activate :middleman_simple_thumbnailer |
うまくbundle install
できなかったり、正常に動かない場合
お使いの端末にGraphicsMagickというモジュールをインストールする必要がある場合があります。
Macでhomebrewをお使いのかたは下のようにインストールできます。
1 |
brew install graphicsmagick |
使い方
View側でimage_tag
というヘルパーメソッドを次のように使うことができます。
1 |
= image_tag image, resize_to: '50x50', class: 'thumbnail' |
image_tag
はもともとMiddlemanにあるヘルパーメソッドですが、middleman-simple-thumbnailer
をインストールすると、resize_to
というオプションが追加されます。これに生成したいサムネイルのサイズを指定してあげるだけです。
image_tag
がやってくれることはざっくり次の2点です。
-
image
で指定した画像のresize_toで指定したサイズのバージョンを作る - src属性に生成したサムネイルのパスを指定したimgタグを出力する
縦と横の両方それぞれ指定できるわけではない
上の例では'50x50'
と指定していますが、縦と横のサイズが違う場合、どちらも50pxにしてくれるというわけではないようです。長い方を50にし、縦横の比率を変えずに短いほうの長さが決まるようです。
なので、縦横比を変えたい場合は、CSSのheigtやwidth属性で制御するのがよいでしょう。
記事の最初にでてくる画像をサムネイルにする方法
先ほどの例では、image
のところに、いちいちサムネイル化したい画像のパスを指定しなければなりません。通常のサイトやブログ運営では、記事の一番最初に出てくる画像を勝手にサムネイルにしてくれたら楽だと思います。その方法をご説明します。
まず、記事のオブジェクトを引数にとると、メインの画像のパスを返してくれるヘルパーメソッドを作ります。ヘルパーメソッドはconfig.rb
内で、helpers do
の中で定義することができます。
1 2 3 4 5 6 7 8 9 |
helpers do def main_img_path(article) imgs = Nokogiri::HTML.parse(article.body).xpath('//img') blank_thumbnail = "/images/blank_thumbnail.png" return blank_thumbnail if imgs.blank? img_src = imgs.first.attr('src') img_src =~ /^http.*$/ ? blank_thumbnail : img_src end end |
Nokogiri
というライブラリでhtmlをパースし、最初に出てくるimg
タグのsrc
属性を取得しています。また画像を使っていない場合や、外部サイトの画像を使っている場合には、空白用の画像のパスを返すようにしています。
View側では下のように、記事のオブジェクトを引数にして使います。
1 |
= image_tag main_img_path(article), resize_to: '300' |
スポンサーリンク
記事内の画像が表示されなくなってしまう場合
記事をKramdown
というMarkdownパーサーを使っている場合などに、上の手順を行うと記事内の画像が表示されなくなる場合があります。
Kramdownだと下のように相対パスを指定した場合、current_article
からの相対パスと解釈してくれて適切なパスに変換してくれます。
1 |
![サンプル画像](sample.jpg) |
下のように適切なパスを出力してくれる。
1 |
<img alt="サンプル画像" src="/articles/2016-11/09-middleman-thumbnail/images/sample.jpg"> |
ですが、上の手順で、記事の一番上の画像のパスを取り出す処理にて、対象の記事がcurrent_article
でない時に、記事を生成することになります。生成される画像のパスは下のようにサイト全体のimagesディレクトリからの相対パスにしてしまいます。
1 |
<img alt="サンプル画像2" src="/images/images/sample.jpg"> |
その記事のURLにアクセスした時に、current_article
がその記事になるので適切に生成し直してくれるのでは?と思うかもしれませんが、一度生成された記事はキャッシュの中に残りそれが出力されてしまいます。Middlemanではキャッシュのクリアを任意にすることができないようです。
なので、記事を表示するテンプレートにて、適切なパスに置換する処理を加えます。まずヘルパーメソッドに下のように追記します。
1 2 3 4 5 6 7 |
def render_with_correct_img(article) doc = Nokogiri::HTML(article.body) doc.xpath("//img[substring(@src, 1, 15) ='/images/images/']").each do |elm| elm["src"] = "/" + article.path.gsub(/index\.html/,'') + 'images/' + elm.attr("src").split("/").last end doc.css('body')[0].inner_html end |
記事を出力するView側にて下のようにします。
1 |
= render_with_correct_img article |
また、この問題が発生している人は、先ほど作ったmain_img_path
内で取得するパスもおかしい可能性が高いです。main_img_path
内でもrender_with_correct_img
を使って下のように修正します。2行目が修正されています。
1 2 3 4 5 6 7 |
def main_img_path(article) imgs = Nokogiri::HTML(render_with_correct_img(article)).xpath('//img') blank_thumbnail = "/images/blank_thumbnail.png" return blank_thumbnail if imgs.blank? img_src = imgs.first.attr('src') img_src =~ /^http.*$/ ? blank_thumbnail : img_src end |
100px以下のサイズを指定するとエラーが起きる
これは自分のパソコンだけかもしれませんが、resize_to
で100以下を指定すると、次のようなエラーが発生しました。
1 |
MiniMagick::Error at /bootcamp-vs-parallels-11-vs-fusion-8/`gm mogrify -resize 100 /var/folders/vc/3xzdspjn5_j53wlgmf23prsh0000gn/T/mini_magick20161108-56972-1ucpfe.gif` failed with error:Ruby/Users/taktak/blog/webfood/vendor/bundle/ruby/2.1.0/gems/mini_magick-4.5.1/lib/mini_magick/shell.rb: in run, line 18 WebGET localhost/bootcamp-vs-parallels-11-vs-fusion-8/ |
なので、小さくても150程度にし、さらに小さくしたい場合はcss側で調整するようにしたほうがいいです。
build時に元ファイルに更新があったものだけ作り直すようにする
build時に全サムネイルを作り直します。しかも、同じサムネイルファイルでも複数箇所から呼び出している場合、その回数分生成し直すという原始的な仕様なので非常に時間がかかります。
なので、毎回全サムネイルを作る直すのをやめ、サムネイルと元ファイルのタイムスタンプを保存する管理ファイルを作り、元ファイルのタイムスタンプが変更された場合にのみ、一度だけ生成するように細工をしました。
まず、config.rbに次のコードを入れ、middleman build --clean
とオプションをつけないとサムネイルを削除しないようにします。デフォルトだと逆で、--no-clean
とつけて削除しないようになっています。
1 2 3 4 5 6 |
Middleman::Cli::BuildAction.class_eval do protected def should_clean? ARGV.include?("--clean") end end |
そして、config.rb
からclass_eval
を使うことで、gemを直接修正しなくても挙動を変えることが可能です。元ファイルから問題のinitialize
メソッドをまるまるコピーし、コードを追加しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
MiddlemanSimpleThumbnailer::Extension.class_eval do def initialize(app, options_hash={}, &block) super app.after_build do |builder| conf_path = "#{build_dir}/thumbnails.yml" conf_update = false thumbnails = {} conf_exist = File.exist?(conf_path) thumbnails = YAML.load_file(conf_path) if conf_exist target_imgs = MiddlemanSimpleThumbnailer::Image.all_objects target_imgs.each do |img| org_timestamp = File.mtime( source + img.img_path) file_exist = File.exist?( build_dir + img.resized_img_path) if file_exist && thumbnails[img.resized_img_path] == org_timestamp next else img.save! if file_exist builder.say_status :update, "#{build_dir + img.resized_img_path}" else builder.say_status :create, "#{build_dir + img.resized_img_path}" end thumbnails[img.resized_img_path] = org_timestamp conf_update = true end end (thumbnails.keys - target_imgs.map(&:resized_img_path).uniq).each do |img| File.delete build_dir + img builder.say_status :delete, "#{build_dir + img}", :red thumbnails.delete img conf_update = true end if conf_update File.open(conf_path,'w') do |h| h.write thumbnails.to_yaml end if conf_exist builder.say_status :update, "#{conf_path}" else builder.say_status :create, "#{conf_path}" end else builder.say_status :identical, "#{conf_path}", :blue if conf_exist end end end end |
※ middleman-simple-thumbnailer (1.0.2)の場合
ちなみに、config.rb
の追記する場所は、acitivate
する次の部分より上に書かないと反映されません。
1 |
activate :middleman_simple_thumbnailer |
これで大幅にbuild時間が短くなりました。
middleman-s3_syncでサムネイル画像を同期してくれない
Amazon S3を利用していて、ファイルの同期にmiddleman-s3_sync
というgemを使っている場合は、デフォルトだとサムネイル画像は同期してくれません。
こちらもconfig.rbに細工することで同期することが可能です。
1 2 3 4 5 6 7 8 9 10 |
if ARGV[0] == "s3_sync" Middleman::S3SyncExtension.class_eval do def manipulate_resource_list(mm_resources) thumbnails = Dir.glob(["**/*.300.jpg", "**/*.150.jpg"]) ::Middleman::S3Sync.mm_resources = mm_resources + thumbnails.map do |t| Sitemap::Resource.new(app.sitemap, t.gsub(/^build\//,""), File.join(app.root, t)) end end end end |
こちらは、acitvate :s3_sync
よりも上に追記してください。
"**/*.300.jpg", "**/*.150.jpg"
という部分はサムネイルのファイル名をワイルドカードで指定しています。ファイル名の数字の部分はご自身の使い方で変わるので適宜修正してください。
さいごに
いかかでしたでしょうか?きれいにサムネイルを表示できましたでしょうか?
ちなみに、この作業で使ったそれぞれのライブラリのバージョンは以下です。
ライブラリ名 | バージョン |
---|---|
middleman-core | 3.4.0 |
middleman-blog | 3.6.0.beta.2 |
middleman-simple-thumbnailer | 1.0.2 |
middleman-s3_sync | 3.3.4 |