「 Mildom(以下、ミルダム)は、コメント欄を非表示にできないの?」ライブ動画配信サービス「ミルダム」の視聴に際して、不便を覚えることがある。
ディスプレイサイズを贅沢に専有できる環境ならば思いつくこともないだろうが、ブラウザサイズをタイトにすることで動画が小さくなって視聴しにくい。
そもそもミルダムのサイトは、リキッドレイアウトよろしくメインカラムが可変してレイアウトを維持する。ところが固定幅のコメント欄に押しやられた動画はアスペクト比を保ってリサイズするから小さくなってしまうわけだ。
このような観にくい状態は、レスポンシブデザインの弱点のひとつで、ちょうど調整しにくい時空の狭間のようなもので、ミルダムにおいてもビューポート900px以下でコメント欄を非表示するように施している。
一方で、ヘッダー画像が配されているイベントページのようなレイアウトの場合に限り、コメント欄非表示タブが用意されている。これを通常レイアウトにも設置してもらいたいところだが、いまのところ実装の様子がみられない。
なんとしてもコメント欄が煩わしい場合は、Chrome デベロッパーツールでコメント欄カラムを非表示にしている次第だ。
それもいい加減めんどうくさい。そんなわけで Chrome 拡張機能をつくってみたい。
もくじ
Chrome 拡張機能とは?
Googleが開発したウェブブラウザ「Google Chrome」に追加するソフトウェアプログラムで、標準では備わっていない便利な機能を体験できる。
Chrome 拡張機能の入手は、Chrome ウェブストアに公開されているものをインストールする手段と、Chrome 拡張機能ポリシーに従って配布されたものをインストールする2パターンが考えられる。なお規約違反した外部拡張機能に関しては望ましくないソフトウェアとしてフラグが立てられるとのことだから留意しておきたい。
インストールしたものは、Chrome 拡張機能の管理画面(chrome://extensions/
)から確認できる。アンインストールしたり機能効力を切り替えたり、開発時におけるデベロッパーモードやパッケージ化されていない拡張機能を読み込む機能も備わっている。
開発環境を準備しよう
Chrome 拡張機能の開発にあたり「chrome-extension-webpack-boilerplate」を活用して効率化を図りたい。
当該パッケージは、Chrome 拡張機能を作るのに必要なソースコードを備えた雛形環境で、開発の着手が容易になっている。
Node.js のバージョンが6以上であることを確認する
以下コマンドで Node.js のバージョンを確認のうえ、6以上ではない場合は適宜アップデートする。
node -v
GitHub リポジトリからクローンする
作業するディレクトリで以下コマンドを実行する。なお git がインストールされていなければ 圧縮ファイルをダウンロードすればよい。
$ git clone https://github.com/samuelsimoes/chrome-extension-webpack-boilerplate.git
https://github.com/lxieyang/chrome-extension-boilerplate-react
パッケージをインストールする
パッケージマネージャのインストールコマンドを実行する。
$ npm install
# or
$ yarn
開発サーバーを起動する
フロントエンド開発で馴染みの webpack-dev-server を起動する。
$ npm run start
# or
$ yarn start
http://localhost:3000
にアクセスできれば無事 webpack サーバーが立ち上がっているものと考えられる。
それと同時に「build」フォルダが生成されていることを確認できる。
Chrome に読み込ませる
前節で触れた「build」フォルダを Chrome に読み込ませることで、開発中の拡張機能を適用できる。その手順を確認していきたい。
chrome://extensions
にアクセスする- デベロッパーモードを有効にする
- 「パッケージ化されていない拡張機能を読み込む」ボタンをクリックして「build」フォルダを選択する
開発環境の準備は以上で完了だ。
つづいて「ミルダムのコメント欄表示切り替え」が機能するように改修していこう。
Chrome 拡張機能を開発しよう
準備は整っただろうか。前節の「Chrome に読み込ませる」まで済んでいれば、あとは大きくつまずくことはないだろうと思う。
さて、それでは件の雛形環境に追加修正したファイルを列挙する。
webpack.config.js
を修正する
var webpack = require("webpack"),
path = require("path"),
fileSystem = require("fs"),
env = require("./utils/env"),
CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin,
CopyWebpackPlugin = require("copy-webpack-plugin"),
HtmlWebpackPlugin = require("html-webpack-plugin"),
WriteFilePlugin = require("write-file-webpack-plugin");
// load the secrets
var alias = {};
var secretsPath = path.join(__dirname, ("secrets." + env.NODE_ENV + ".js"));
var fileExtensions = ["jpg", "jpeg", "png", "gif", "eot", "otf", "svg", "ttf", "woff", "woff2"];
if (fileSystem.existsSync(secretsPath)) {
alias["secrets"] = secretsPath;
}
var options = {
mode: process.env.NODE_ENV || "development",
entry: {
popup: path.join(__dirname, "src", "js", "popup.js"),
options: path.join(__dirname, "src", "js", "options.js"),
background: path.join(__dirname, "src", "js", "background.js"),
content: path.join(__dirname, "src", "js", "content.js")
},
chromeExtensionBoilerplate: {
notHotReload: ["content"],
},
output: {
path: path.join(__dirname, "build"),
filename: "[name].bundle.js"
},
module: {
rules: [
{
test: /\.css$/,
loader: "style-loader!css-loader",
exclude: /node_modules/
},
{
test: new RegExp('.(' + fileExtensions.join('|') + ')$'),
loader: "file-loader?name=[name].[ext]",
exclude: /node_modules/
},
{
test: /\.html$/,
loader: "html-loader",
exclude: /node_modules/
}
]
},
resolve: {
alias: alias
},
plugins: [
// clean the build folder
new CleanWebpackPlugin(),
// expose and write the allowed env vars on the compiled bundle
new webpack.EnvironmentPlugin(["NODE_ENV"]),
new CopyWebpackPlugin([{
from: "src/manifest.json",
transform: function (content, path) {
// generates the manifest file using the package.json informations
return Buffer.from(JSON.stringify({
description: process.env.npm_package_description,
version: process.env.npm_package_version,
...JSON.parse(content.toString())
}))
}
}], {
copyUnmodified: true,
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "popup.html"),
filename: "popup.html",
chunks: ["popup"]
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "options.html"),
filename: "options.html",
chunks: ["options"]
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "background.html"),
filename: "background.html",
chunks: ["background"]
}),
new WriteFilePlugin()
]
};
if (env.NODE_ENV === "development") {
options.devtool = "cheap-module-eval-source-map";
}
module.exports = options;
src/manifest.json
を修正する
{
"name": "コメント欄消す?",
"description": "Mildom(ミルダム)のコメント欄を表示切り替え",
"options_page": "options.html",
"background": {
"page": "background.html",
"persistent": false
},
"page_action": {
"default_popup": "popup.html",
"default_icon": "icon-34.png"
},
"icons": {
"128": "icon-128.png"
},
"permissions": [
"tabs",
"declarativeContent"
],
"content_scripts": [
{
"matches": [
"https://www.mildom.com/*"
],
"js": [
"content.bundle.js"
]
}
],
"manifest_version": 2,
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}
src/js/background.js
を修正する
import '../img/icon-128.png'
import '../img/icon-34.png'
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostEquals: 'www.mildom.com', schemes: ['https'] },
})
],
actions: [
new chrome.declarativeContent.ShowPageAction()
]
}]);
src/js/popup.js
を修正する
import '../css/popup.css';
function handleChange(e) {
const checked = e.target.checked;
chrome.tabs.query({ active: true, currentWindow: true }, function callback(tabs) {
chrome.tabs.sendMessage(tabs[0].id, { checked });
});
}
document.querySelector('#toggle').addEventListener('change', handleChange);
src/popup.html
を修正する
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<label class="checkbox-primary">
<input type="checkbox" id="toggle" class="checkbox-origin">
<span class="checkbox-label">コメント欄を消す</span>
</label>
</body>
</html>
src/css/popup.css
を追加する
.checkbox-primary {
display: inline-block;
vertical-align: top;
min-width: 14px;
min-height: 14px;
font-size: 10px;
line-height: 1;
}
.checkbox-primary .checkbox-origin {
display: none;
}
.checkbox-primary .checkbox-origin:checked + .checkbox-label {
position: relative;
}
.checkbox-primary .checkbox-origin:checked + .checkbox-label::before {
background-image: url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2214%22%20height%3D%2214%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22%22%3E%20%3Cpath%20id%3D%22ic_check_box_24px%22%20d%3D%22M15.444%2C3H4.556A1.555%2C1.555%2C0%2C0%2C0%2C3%2C4.556V15.444A1.555%2C1.555%2C0%2C0%2C0%2C4.556%2C17H15.444A1.555%2C1.555%2C0%2C0%2C0%2C17%2C15.444V4.556A1.555%2C1.555%2C0%2C0%2C0%2C15.444%2C3Zm-7%2C11.667L4.556%2C10.778V7.667l3.889%2C4.021%2C7-7.132V7.667Z%22%20transform%3D%22translate%28-3%20-3%29%22%20fill%3D%22rgb%2850%2C50%2C58%29%22%2F%3E%20%3C%2Fsvg%3E);
}
.checkbox-primary .checkbox-label {
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: top;
min-width: 24px;
min-height: 24px;
font-size: 12px;
line-height: 1;
cursor: pointer;
white-space: nowrap;
user-select: none;
}
.checkbox-primary .checkbox-label::before {
content: "";
display: inline-block;
width: 14px;
height: 14px;
margin-right: 5px;
background: url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2214%22%20height%3D%2214%22%20viewBox%3D%220%200%2014%2014%22%20style%3D%22%22%3E%20%3Cg%20id%3D%22ic_check_box_24px%22%20transform%3D%22translate%28-3%20-3%29%22%20fill%3D%22none%22%3E%20%3Cpath%20d%3D%22M15.444%2C3H4.556A1.555%2C1.555%2C0%2C0%2C0%2C3%2C4.556V15.444A1.555%2C1.555%2C0%2C0%2C0%2C4.556%2C17H15.444A1.555%2C1.555%2C0%2C0%2C0%2C17%2C15.444V4.556A1.555%2C1.555%2C0%2C0%2C0%2C15.444%2C3Z%22%20stroke%3D%22none%22%2F%3E%20%3Cpath%20d%3D%22M%204.555560111999512%204%20C%204.24921989440918%204%204%204.24921989440918%204%204.555560111999512%20L%204%2015.44443988800049%20C%204%2015.75078010559082%204.24921989440918%2016%204.555560111999512%2016%20L%2015.44443988800049%2016%20C%2015.75078010559082%2016%2016%2015.75078010559082%2016%2015.44443988800049%20L%2016%204.555560111999512%20C%2016%204.24921989440918%2015.75078010559082%204%2015.44443988800049%204%20L%204.555560111999512%204%20M%204.555560111999512%203%20L%2015.44443988800049%203%20C%2016.30777931213379%203%2017%203.700000762939453%2017%204.555560111999512%20L%2017%2015.44443988800049%20C%2017%2016.29999923706055%2016.30777931213379%2017%2015.44443988800049%2017%20L%204.555560111999512%2017%20C%203.692220687866211%2017%203%2016.29999923706055%203%2015.44443988800049%20L%203%204.555560111999512%20C%203%203.700000762939453%203.692220687866211%203%204.555560111999512%203%20Z%22%20stroke%3D%22none%22%20fill%3D%22rgb%2850%2C50%2C58%29%22%2F%3E%20%3C%2Fg%3E%20%3C%2Fsvg%3E) no-repeat center/14px;
}
src/js/content.js
を追加する
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
const column = document.querySelector('.container > .content > div:last-child');
if (request.checked) {
column.style.display = 'none';
sendResponse('is hidden');
} else {
column.removeAttribute('style');
sendResponse('not hidden');
}
});
ビルドする
$ npm run build
# or
$ yarn build
まとめ
以上、ライブ動画配信サービス「ミルダム」のコメント欄表示切り替え拡張機能のつくり方の共有まで。
このたびのChrome 拡張機能の要件は、表示切り替えだったわけだけれど、レイアウト変更機能を拡充しても良さそうだった。またその場合にインタラクティブなUI設置が求められるようなら「chrome-extension-boilerplate-react」の選択が賢明かもしれない。余力があればトライしてみようと思う。
このエントリーが、あなたのクリエイティビティを刺激するものであると期待したい。