Webの画面開発でのフィードバックのやり取りを楽にするための拡張をつくりました

# まずはこちらをご覧ください
![Jan-17-2018 17-35-22.gif](https://qiita-image-store.s3.amazonaws.com/0/7193/10f1537e-e9ed-771f-1c83-5f7abaa619c5.gif)

保存した画像がこちらです
![image (1).jpeg](https://qiita-image-store.s3.amazonaws.com/0/7193/babadd20-1e1a-0c7d-a661-1481e0c1a86d.jpeg)

# ウェブページをキャプチャして すぐに描き込み・保存ができる拡張をつくりました
**画面の開発時に行われるフィードバックって、丁寧な文章で説明されても、はたまた口頭で伝えられても結局認識の齟齬が起こってしまいがちでコミュニケーションがしんどい**というシーン遭遇したことはないでしょうか。自分は結構な頻度であります。

その都度、スクショに直接描き込んでフィードバックをしてくれればわかりやすいのにな〜と思っていたのですが、現実問題として**フィードバックをする立場の人が必ずしも画像編集ソフトを使えるとは限らない**し、
そもそも自分がフィードバック用の資料を作るようなときも、**画像編集ソフトが使えてもいちいちフォトショやGIMPを立ち上げるのは面倒くさい**なーという微妙な問題を抱えていました。

そこで、難しさを排除して、誰でもすぐに使えるようにして、Web開発でのフィードバックのやり取りを幸せにしようという目的のもとこの拡張を作成しました。
同じ問題・悩みを抱えている方の助けとなれば幸いです。

# こちらで公開しています
https://chrome.google.com/webstore/detail/webpage-captrure-and-pain/mdglibpddhkadiadabckagelmmomoobn

フィードバックなどお寄せいただけますと幸いです。

# ライセンス表記
以下のライブラリを使用させていただきました

- download.js https://github.com/rndme/download
- html2canvas.js https://html2canvas.hertzen.com
- Spectrum Colorpicker https://github.com/bgrins/spectrum


# 拡張開発時のTIPS
今回の開発でちょっと頑張った点などについて、ナレッジの共有と自分の振り返りも兼ねてピックアップして書き残したいと思います。

### 画面キャプチャ
画面のキャプチャーを行うには[`chrome.tabs.captureVisibleTab`](https://developer.chrome.com/extensions/tabs#method-captureVisibleTab)を使用します。
このAPIメソッドはブラウザの表示領域内をキャプチャしてpngまたはjpegのbase64データURLを取得することができます。

ただし、このAPIメソッドはbackgroundまたはpopup内で動作しているスクリプトからのみ呼び出し可能で、contentsとして挿入しているスクリプト、またはexecuteScriptとして差し込んだスクリプト内から呼び出しすることはできません。

```js:captureVisibleTab記述サンプル
chrome.tabs.captureVisibleTab(null,{format:"jpeg"}, (base64Data) => {
  // ここで受け取ったbase64dataをstorageに保存したりとか
});
```

#### スクロール領域を含めた画面全体をキャプチャするには
上記項目で取り上げた`chrome.tabs.captureVisibleTab`はブラウザの表示領域内のみのキャプチャのため、これ単体だけでは全体をキャプチャすることはできません。
そこで、表示領域の幅・高さであるclientWidth,clientHeightと表示中のページのHTMLのスクロール領域の幅・高さであるscrollWidth,scrollHeightを取得し、縱橫ともに何回分スクロールすれば全領域をカバーできるのかを割り出します。

具体的に数値を例として表すと、
clientWidth 100, clientHeight 100, scrollWidth 200, scrollHeight 300の場合であれば、以下の座標にスクロール箇所を動かせば表示領域をすべてカバーできることになります。

```js
{left:0  ,top:0},
{left:100,top:0},
{left:0  ,top:100},
{left:100,top:100},
{left:0  ,top:200},
{left:100,top:200}
```

割り出しした座標に移動したらcaptureVisibleTabを使ってスクショを撮る→また移動してスクショを撮る→また移動する...という処理になるのですが、
ページ内での移動はcontentsのスクリプトからのみ操作可能、スクショの取得はbackgroundかpopupのスクリプトからのみ操作可能です。
contentsスクリプトとbackground/popupスクリプトの動作を連携させるためには次項で記載するメッセージパッシングを用います。

### メッセージパッシング
メッセージパッシングに関する公式ドキュメントは[こちら](https://developer.chrome.com/apps/messaging)です。
contentsスクリプトとbackground/popupスクリプトの間でオブジェクトのやり取りが可能になります。

#### background/popupスクリプトからのメッセージ送信
**[`chrome.tabs.sendMessage`](https://developer.chrome.com/extensions/tabs#method-sendMessage)**を用います。引数としてtabを渡す必要があるため、事前に[`chrome.tabs.getSelected`](https://developer.chrome.com/extensions/tabs#method-getSelected)を用いて表示中のタブを取得します。

また、contentsスクリプト側ではパッシングされた値を[`chrome.runtime.onMessage.addListener`](https://developer.chrome.com/apps/runtime#event-onMessage)を用いて受け取り、コールバック関数を用いてパッシング元に対してレスポンスをします。
パッシングをされる前にリスナーイベントを登録しておく必要がある点に注意です。

##### chrome.tabs.sendMessage サンプル
```js:popup.jsサンプル
chrome.tabs.getSelected(null, tab => {
    chrome.tabs.sendMessage(
        tab.id, // 引数にタブIDが必要
        {msg: 'Hello contents'}, // パスするオブジェクト
        response => { // コールバックは省略可能
            // contentsがパスしたオブジェクトをresponseで受け取れます
            console.log(response.msg) // Hi popup.
        }
    );
});

```

```js:content.jsサンプル
chrome.runtime.onMessage.addListener(
    (request, sender, sendResponse) => {
        switch (request.msg) { // 受け取った値で分岐
            case 'from_popup': 
                sendResponse({
                    msg: 'Hi popup.'
                });
                break;
            case 'from_background': 
                sendResponse({
                    msg: 'Hi background.'
                });
                break;
            default:
        }
    }
});
```
今回の全画面スクリーンキャプチャの実装ではこれらを用い popup「スクショ撮ったよ」→contents「次の座標に移動したよ」というやり取りを繰り返すという処理をやっていました。


#### contentsスクリプトからのメッセージ送信
contents側からbackground/popupに対してメッセージをパッシングする場合は
**[`chrome.runtime.sendMessage`](https://developer.chrome.com/apps/runtime#method-sendMessage)**を用います。
このメソッドは`chrome.tabs.sendMessage`と使い方はほぼ一緒です。
メッセージの受け取り方も同様に[`chrome.runtime.onMessage.addListener`](https://developer.chrome.com/apps/runtime#event-onMessage)を用いて受け取ります。
差異としては、タブ側からのメッセージ発信となるため引数にタブIDを設定する必要がありません。

##### chrome.tabs.sendMessage サンプル
```js:content.jsサンプル
chrome.runtime.sendMessage({
    msg:  'Hello background'
});
```

```js:background.jsサンプル
chrome.runtime.onMessage.addListener(function (message, sender) {
    if (message.msg === 'Hello background') {
        // Do something...
    }
});
```
今回の実装では、矩形選択によるキャプチャを行う場合に用いました。
popupは矩形選択時に閉じてやらなくてはいけないため、backgroundとのやり取りを行う必要があったためです。
なんかこの辺を書いてるあたりでコードがごちゃごちゃになり始めてしまったので、最初から処理はbackground.jsに纏めておくなど、より良いセオリーを学びたいところです。


# こだわったポイント
書かせて欲しい。

## 全画面キャプチャした画像の取扱い方
開発当初は馬鹿正直にキャプチャした画像を一枚のimgにしていたんですが、こういうものを作成していると**世の中にあるウェブページは凄く縦長である**ということに気づきます。
ピクセルにして20000pxを超えてくるようなページがざらにあり、これをまともに一枚の画像にしてウェブページの中に貼り付けしてしまうと**ブラウザがまともに動かなくなって落ちてしまいます。**

よしんば画像の貼り付けができてもお絵かき機能がまともに動かないし画像の保存もできません。
これの解決策として、スクロール時にキャプチャしたスクロール座標ごとの画面単位で画像を作成し、背景にパッチワーク状にして配置しています。
画像を小分けにすることで、動作を安定させ巨大な画像になっても扱えるようになりました。

## スクロールしたらstickyな要素を非表示化するようにした
よくある全画面キャプチャでstickyなヘッダが何回も映り込んでしまうのを回避しています。
それでもものによってはどうしようも出来ない場合があるのでその場合は表示部分キャプチャをつかっていただきたいところ。

## グラフィックソフトを使えない人でも何となく何をすればいいか分かるようにした（つもり）
グラフィックソフトを使ったことがない人や、ITリテラシーが高くない人にGIMPなどを教えても「なにこれどうつかったらいいの」ってなってしまうのは明白（自分がそうだった）なので、「線を書く」「消しゴムで線を消せる」「文字を入れられる」「保存できる」ということに特化させて戸惑うことがないようにしたつもりです。
ただ、やっぱりちょっと図形描画などがないと不便だなと思う要素もあるのでバージョンアップして改良していく予定です。

## WEB感が無い 子供っぽい雰囲気のUIにした
「とっつきづらそう」という印象を持たせないためと、
描画用の画面がぱっと開いた時に、元々の画面と この拡張のUIが入り混じってしまうことがないように、多くのウェブページでは採用されなさそうなテイストの色使いや縁取りなどをつけています。
ただ、正直に言えばカッコいいUIとかアイコンを作れないというところもあります。へへへ。


長くなってしまい何を書くべき記事なのかわからなくなってきたので、一旦ここまでにしておきたいと思います。
お読み頂きありがとうございました。