こんにちは、サイオステクノロジーの馬場です。
私の携わっているプロジェクトでは、
飲食店のオペレーション全般をサポートするシステムを、スクラッチから開発しています。
その中には、商品メニューを管理するためのウェブアプリもあるのですが、
このウェブアプリは、規定の寸法から外れる商品画像を「拒絶する」という硬派な仕様になっています。
商品メニュー編集を代行しているメンバー (私じゃない) は、この仕様がツラそうなんですが、
限られた予算の中で、このツラさが解決されるまでの道のりは遠く険しそう、という状況です。
この記事では、それを見た私がとった行動を時系列で晒しつつ、最後に、これによる成果を妄想します。
時系列
アイディア
以下のように考えました:
- サイズが違ったら、自動リサイズしてあげれば良いだろう。
- 「メニューに掲載するような写真を、システムで自動的に加工して良いの?」
という懸念が、現状の硬派な仕様を生んでいると思われるが、
そういうことを気にする場合は、きちんと手動リサイズしたものを上げれば良い。
(サイズが合っていれば自動リサイズされないようにする)
- 「メニューに掲載するような写真を、システムで自動的に加工して良いの?」
- そのリサイズは、クライアントサイドで済ませられるだろう。
- JavaScript ライブラリがあるはず。
- ブラウザの UserScript で試してみよう。
- まずは、 “Proof of Concept” として、やる。
- 本体に組み込むのは、担当エンジニアに任せる。
- 何なら、「運用上の工夫」として、やる。
- メニュー編集担当メンバーが UserScript を育て続けても良い。
- まずは、 “Proof of Concept” として、やる。
つまり「 UserScript で試すというステップを設ければ、世界が拡がるかもしれない」と考えました。
JS の画像処理ライブラリを探す
GitHub でのスター数とかを参考に、これにしました:
“Chrome Greasemonkey” でググる
チームの標準ブラウザは Chrome です。
ググってみると、Tampermonkey
というものがあるようです:
Tampermonkey:
https://www.tampermonkey.net/
Greasemonkey の後継で、主要なブラウザには大体対応しています。Chrome にインストールしました。
Tampermonkey を試す
簡単に試したところ、
ブラウザで閲覧中の任意のウェブページを、JavaScript (UserScript) で
好きなように加工できることが分かりました。
早速、上記 Jimp を使ったコードを書き始めました。
UserScript を書く
方針としては、
最初に説明したとおり、アップロードされたファイルをチェック & 拒絶している箇所があるわけなので、
その部分を丸ごと差し替えるイメージで進めました。
その箇所は、 input[type=file] の onChange のハンドラーとして実装されていたので、
元のハンドラーを解除 (off) して、書き換えたほうのハンドラーを登録 (on) すれば良いはずです。
最終的には、こんな感じになりました:
/* * コンセプトを伝えるために、簡略化しています。 * コピペで動作するものではありません。 */ // ウェブページ側のブロックスコープで隠蔽されていて、 UserScript から参照できない変数は、 // UserScript 側でも定義。 const sizes = { small: {width: 120, height: 120}, medium: {width: 510, height: 340}, large: {width: 1200,height: 800}, } const photoShadow = 'input[type=file]#photo-shadow'; $(document) .off('change', photoShadow) // 元のハンドラーを解除 (off) .on('change', photoShadow, function(){ // 新しいハンドラーを登録 (on) const fileReader = new FileReader(); fileReader.onload = function (event) { const data = event.target.result; const base64Prefix = 'data:image\\/jpeg;base64,'; const dataBase64 = data.substring(base64Prefix.length - 1); if (data.match(base64Prefix)) { const img = new Image(); img.onload = function() { EXIF.getData(img, function() { const colorSpace = EXIF.getTag(this, 'ColorSpace'); if (dataBase64.slice(0, 4) !== "/9j/") { // JPEG じゃない! window.alert("JPEGファイルであることを確認してください"); } else if (colorSpace && colorSpace != 1) { // カラープロファイルが sRGB じゃない! window.alert('カラープロファイルがsRGB(1)であることを確認してください'); } else if (img.width !== sizes[size].width || img.height !== sizes[size].height) { // サイズが違う! (size はグローバル変数) /* ▼ ここから書き換えた部分 */ Jimp.read(data) .then(function(image){ return image .cover(sizes[size].width, sizes[size].height) // sizes[size] どおりにリサイズ .getBase64(Jimp.MIME_JPEG, function(err, d){ $(dataForm).val(d.substring(base64Prefix.length - 1)); // リサイズ後のデータを保持 $(preview).attr('src', d); // プレビュー表示 }); }) .catch(function(err){ console.error(err); }); /* ▲ ここまで書き換えた部分 */ } else { // オールオッケー! $(dataForm).val(dataBase64); $(preview).attr('src', data); } }); }; img.src = data; } }; fileReader.readAsDataURL(this.files[0]); // $(photoShadow) はリセットして使い回す。 $(photoShadow).replaceWith($(photoShadow).val('').clone(true)); });
(Tips 1) 変数のスコープについて
これはTampermonkey のエディタのスクリーンショットです。
UserScript からウェブページ側の変数を参照している箇所で、
エディタにとっては未定義の変数なので、エディタがその旨をワーニングしています。
これを見て「// @grant unsafeWindow
が必要なのかな?」と思って、試したりしましたが、
結局は不要で、ウェブページ側のグローバル変数は普通に UserScript から参照できるようです。
unsafeWindow:
https://www.tampermonkey.net/documentation.php#unsafeWindow
それとは別に、
ウェブページ側で { let ... }
{ const ... }
とブロック内で宣言されている変数は、
言語仕様上(ブロックスコープ)、ブロック外である UserScript からは見えません。
これは、いずれ本体(ウェブページ)側に組み込まれれば解決する問題なので、
素直に UserScript 側にコピペしました。
(Tips 2) @match について
Tampermonkey では、
どのウェブページで UserScript を有効にするかを指定する @match
記法が用意されています。
@match
はワイルドカード記号 *
をサポートしていて便利ですが、
この *
は、正規表現とも(シェル, glob の)ワイルドカードとも違う独自のものなので、
注意が必要です: https://developer.chrome.com/extensions/match_patterns
チーム内でシェアして完了
ひとまず PoC としては完成したので、メニュー編集担当メンバーにシェアしました。
担当メンバーは「あの時、これがあれば…!」と言っていたので、PoC としては成功でしょう。
チーム内の課題管理システム上で、この件のチケットに UserScript を添付して、あとは託しました。
期待している成果
UserScript を活用することで、以下のような変化を期待しています:
- 担当メンバーが「手元で工夫すれば業務改善できる」という認識をもつ。
- 手元(こちら側)で試すというステップを経ることで、
本体(あちら側)に取り込まれるプロセスがスムーズになる。
つまり、「こちら側」と「あちら側」の架け橋となって、
この手のカユいツラいところの解決への道筋を示すものになれば、と期待しています。
次回予告
また、そのうち、
何かやってみた時の行動を時系列で晒しつつ、最後に成果物も晒し、「あるある」的な共感を得る
というフォーマットで、書こうと思います。 (=「◯◯てみた」シリーズ)