こんにちは、サイオステクノロジーの馬場です。
私の携わっているプロジェクトでは、
飲食店のオペレーション全般をサポートするシステムを、スクラッチから開発しています。
その中には、商品メニューを管理するためのウェブアプリもあるのですが、
このウェブアプリは、規定の寸法から外れる商品画像を「拒絶する」という硬派な仕様になっています。
商品メニュー編集を代行しているメンバー (私じゃない) は、この仕様がツラそうなんですが、
限られた予算の中で、このツラさが解決されるまでの道のりは遠く険しそう、という状況です。
この記事では、それを見た私がとった行動を時系列で晒しつつ、最後に、これによる成果を妄想します。
時系列
アイディア
以下のように考えました:
- サイズが違ったら、自動リサイズしてあげれば良いだろう。
- 「メニューに掲載するような写真を、システムで自動的に加工して良いの?」
という懸念が、現状の硬派な仕様を生んでいると思われるが、
そういうことを気にする場合は、きちんと手動リサイズしたものを上げれば良い。
(サイズが合っていれば自動リサイズされないようにする)
- 「メニューに掲載するような写真を、システムで自動的に加工して良いの?」
- そのリサイズは、クライアントサイドで済ませられるだろう。
- 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 を活用することで、以下のような変化を期待しています:
- 担当メンバーが「手元で工夫すれば業務改善できる」という認識をもつ。
- 手元(こちら側)で試すというステップを経ることで、
本体(あちら側)に取り込まれるプロセスがスムーズになる。
つまり、「こちら側」と「あちら側」の架け橋となって、
この手のカユいツラいところの解決への道筋を示すものになれば、と期待しています。
次回予告
また、そのうち、
何かやってみた時の行動を時系列で晒しつつ、最後に成果物も晒し、「あるある」的な共感を得る
というフォーマットで、書こうと思います。 (=「◯◯てみた」シリーズ)

