R Markdownで画像クリックしたら拡大表示させる機能(Lightbox)の実装

# R Markdownで画像クリックしたら拡大表示させる機能(Lightbox)の実装

先日r-wakalangで質問があがったのでやってみた。

## 画像を拡大表示させたい

R Markdownでhtml出力する際，plotを描くことが多いでしょう。ただ，このplotなどの画像出力はそのままではサイズ固定であり，これを拡大して見ようとするなら手間がかかります。

よくWebのあちこちで「画像をクリックするとその場でボワンっと拡大画像が出てくる」というのがあるかと思います。これはページ遷移もなく，またポップアップでもないので，読者にスムーズに拡大画像を提供することができます。

これらの多くはJavaScriptで実装されており，代表的なものが[Lightbox](http://lokeshdhakar.com/projects/lightbox2/)というjsライブラリです。どういうものかはリンク先に飛んでもらって画像をクリックしてもらえればすぐに理解できるでしょう。

というわけで，この記事では**RmdでこのLightboxを実装**してみます。

## 目標

- ページ内の画像をクリックすると，その画像が拡大して表示される
- mdによる画像差し込み，Rチャンクによる画像出力両方に対応
- できるだけドキュメントに手を加えない
- 汎用性が高い(他のRmdでもすぐに使える)コード

なかなかきつい…

## 実装

できました。サンプルとして，以下の2つのファイルを使います:
**2017/07/27追記: 指定セレクタを修正してゴミみたいなのが出てこなくしました**

````md:lightbox_test.Rmd
    ---
    title: "lightbox_test"
    output:
      html_document:
        includes:
          in_header: include_lightbox.html
    ---
    
    ```{r setup, include=FALSE}
    knitr::opts_chunk$set(echo = TRUE)
    ```
    
    ## ああああ
    
    ```{r}
    plot(1:5)
    ```
    
    hogehoge.
    
    ```{r}
    library(ggplot2)
    ggplot(iris, aes(Sepal.Length, Sepal.Width)) +
      geom_point()
    ```
    
    ```{js}
    $(function(){
      $("img:not(.lb-image)").wrap(function() {
        return "<a href='" + $(this).attr("src") + "' data-lightbox='" + $(this).attr("scr") + "'></a>";
      });
    });
    ```
````

```html:include_lightbox.html
<link href="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.9.0/css/lightbox.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.9.0/js/lightbox.min.js"></script>
```

Rmdファイルの行頭に4スペースありますが，実際にはこれを削除してください。この2つのファイルを同一ディレクトリに設置し，RmdファイルをKnitすればOKです。こんな感じになります:

![b1_2342.png](https://qiita-image-store.s3.amazonaws.com/0/35118/454e10d4-53ff-be24-a829-f1b028416138.png)

![b2_42342.png](https://qiita-image-store.s3.amazonaws.com/0/35118/15a455ec-13e5-ffda-392c-0dbc5c49f2dc.png)


検証してもらえるとわかりますが，先ほどの条件は全てクリアされます。またRStudioのViewerでも動作します。

## 実装のポイント

### lightboxのjsライブラリとcssを読み込む  

lightboxはjQueryの拡張ライブラリなので，jQueryを事前に読み込ませる必要があります。ただR Markdownのhtml_document出力では，特にいじってなければ自動的に読み込んでいます。なのであとはlightboxで必要なものだけを読みこませればOKです。

今回は手続き簡略化のため，CDNから読み込ませることにし，そのためのhtmlタグだけを含むhtmlファイル(`include_lightbox.html`)として準備しました。これをRmdのyaml front matter(冒頭のyaml部分)でheadタグ内に差し込ませるようにしてます。おそらくはこれが一番スマートだと思います。

### lightboxの仕様をチェック

lightboxの基本的な使い方ですが，`<a>`タグにlightbox向けのclass属性を付与させます。例えば，[公式サイトの説明](http://lokeshdhakar.com/projects/lightbox2/#getting-started)を使うならば，以下のような`<a>`タグを準備します:

```html
<a href="images/image-1.jpg" data-lightbox="image-1" data-title="My caption">Image #1</a>
```

この場合，この`Image #1`という文字にかかったリンクをクリックすると，Lightboxが反応して`images/image-1.jpg`のファイルがボワンっと出てきます。この例では`href`と`data-lightbox`と`data-title`の3の属性がありますが，**必須なのは`href`属性と`data-lightbox`です**。`href`属性は，ボワンっと表示させる画像への参照パスで，`data-lightbox`はlightbox内での識別子となります。この2つがあると，リンク先をlightboxで処理するようになります。

他にも色々設定できるのですが，最短実装を目指すため省略します。詳しくは公式サイトの説明を参照してください。

### Rmd内の画像タグ(`<img>`)をlightbox向けに変換

メインです。

R Markdownのhtml_documentで出力した場合，画像はimgタグで出てくるようになります。html_documentによる出力によって生成されるhtmlタグやclassなどは，以下のQiita記事にまとめています:

[R Markdownで生成するhtmlタグ要素のidとclassを確認 - Qiita](http://qiita.com/kazutan/items/807192a0449615dc8852)

なお，mdで差し込んだ画像は[こちら](http://qiita.com/kazutan/items/807192a0449615dc8852#%E7%94%BB%E5%83%8F)に，Rチャンクで出力したplot画像は[こちら](http://qiita.com/kazutan/items/807192a0449615dc8852#plot%E3%81%AE%E5%87%BA%E5%8A%9B)にサンプルがあります。

まあいずれにせよ，**基本`src`属性くらいしか与えられていない`<img>`タグが生成される**，ということです。ですが今回目指す機能を実装するためには，以下のような変換が必要となります:

変換前:

```html
<img src="hoge.png" />
```

変換後:

```html
<a href="hoge.png" data-lightbox="hoge.png"><img src="hoge.png" /></a>
```

つまり...

1. `<img>`タグより`src`属性の値を取得
1. `<img>`タグ単独を包み込むように`<a>`タグを生成
1. ラップした`<a>`タグの`href`属性に，`<img>`タグの`src`属性を引き渡す
1. ラップした`<a>`タグの`data-lightbox`属性に，何か識別できる値(今回は画像ファイル名を流用)を引き渡す

ができるようにすればOKです。しかもこれは**html生成後に行わないといけない処理**で，R Markdown側で実装しようとするなら**Pandoc処理後に正規表現でゴリゴリやるR処理を組み込む**という，厳しい戦いとなります。自分でR Markdownテンプレートパッケージを作成するというのならわかりますが，かなり仰々しいです。

そこで**jQueryを利用します**。以下のようなjsコードを今回作りました:
**2017/07/27追記: 指定セレクタを修正してゴミみたいなのが出てこなくしました**

```js
$(function(){
  $("img:not(.lb-image)").wrap(function() {
    return "<a href='" + $(this).attr("src") + "' data-lightbox='" + $(this).attr("scr") + "'></a>";
  });
});
```

jsのコードに関する説明は省略しますが，これで先ほどの処理をすべてやってくれます。全ては`.wrap()`メソッドのおかげで，まんまやりたいことをやってくれるのがあって楽ちんでした。

## 雑感

思ったより簡単にいけました。jQueryすごい。

でも実際にやるなら、jsの部分は見えないようにしたほうがいいと思います。一番いいのは、その該当箇所を別ファイル(html)で切り出し、その中で`<script>`タグで挟み込んだものを準備し、それをRmdのyaml front matterでhtmlファイルをincludesで指定する方法です。この方法なら、他のドキュメントへの使い回しも簡単ですし、コストがぐっと下がります。

ただ何分私がJavaScriptほとんどかけない人間なので，不十分な点があるかと思います。何かありましたらコメントでお知らせください。

**Enjoy!**