概要
- Google Formsで自分の回答の字数を知りたい!
- Chrome拡張ってどうできているの?何ができるの?
という思いからGoogle Formsの自分の回答に字数を表示する拡張機能を作りました。
外部(Chrome Web Store)に公開するとなるとお金がかかったり別な手続きが必要みたいですが、自分のChromeで使うだけの拡張であれば、フォルダーをアップロードするだけです。
Chrome拡張の作り方
今回くらいシンプルな拡張であれば、
- 必要事項を書いた
manifest.json
ファイル - プログラムを書いた
content-script.js
ファイル(名前は何でもよい)
以上の2つの入ったフォルダをGoogle Chromeにアップロードするだけです。基本的にJavascriptで書きます。
manifest.jsonファイルについて
Chromeの拡張機能を作るうえで必須なのがmanifest.json
ファイルです。アップロードするフォルダー(ディレクトリ)の直下に作ります。今回は大したことをしないので書くことはあまりありません。
{
"manifest_version": 3,
"name": "Word Counter for Google Forms",
"version": "1.0.0",
"description": "This extension shows the number of letters you are writing on a Google Form.",
"content_scripts": [{
"matches": ["https://docs.google.com/forms/*"],
"run_at": "document_end",
"all_frames": true,
"js": ["content-script.js"]
}]
}
-
manifest_version
によってChrome拡張の仕様が少し異なるので、ググるときは気を付けましょう。 -
["content-script.js"]
の箇所は、プログラムを書いたファイル名と一致させてください。
URLがhttps://docs.google.com/forms/*
にマッチするWebサイトを開くとcontent-script.js
ファイルが実行されますよ~、というだけの内容です。*
はワイルドカードの一つで、任意の文字列にヒットします。
コードを書く前に
Formの構造を調べる
この拡張を作成するうえで一番面倒な作業があります
HTMLを書き換えるためにはあらかじめページの構造がわかっていないといけません。
というわけで調べます。
字数を表示したい対象のFormをChromeで開きます。自分で適当なFormを作ってもいいです。
f12
かCtrl
+Shift
+I
(またはF12
)で開発者ツールを開き、左上のカーソルマークを押してからフォームの取得したい要素をを見つけていきます。
一つの要素に複数のclassが割り当てられていることもあるので、同じclass名をもつ要素を比較したりして、Googleがどんなページの作り方をしているのか探していかなければいけません。
めっちゃメンドイ...
結果として、テキストの回答枠(input
要素とtextarea
要素)のclass名はすべてhhh
で、質問文のclass名はすべてG4EHhc
で、それぞれの親要素のclass名はすべてgeS5n
だとわかりました。
ちなみにGoogleがページの構造変えたら使えなくなる可能性大です(苦笑)
上記をまとめると、フォームは大まかにこういう構造になっていることが分かります。
<div class='geS5n'>
<span class='G4EHhc'>質問文</span>
<textarea class='KHxj8b'>複数行の回答内容</textarea>
</div>
<div class='geS5n'>
<span class='G4EHhc'>質問文</span>
<input class='whsOnd' value='一行の回答内容'>
</div>
回答内容の文字数を取得して、その上の質問文の末尾に<span class='word-count'>8文字</span>
みたいな要素を付け足します。
初めページを読み込んだ時にその要素を付け足したら、あとは入力があるたびに文字数を上書きしていく、というのが大まかな流れです。
メインのコード
'use strict';
const countWords = () => {
// 入力欄をすべて取得
const textareas = document.querySelectorAll('.geS5n textarea.KHxj8b, .geS5n input.whsOnd');
// 文字数表示枠をすべて取得
let counts = document.querySelectorAll('.geS5n .word-counter');
if (counts[0]) {
// 文字数表示枠がすでに作られているとき
for (let i = 0; i < textareas.length; i++) {
counts[i].innerHTML = ` ${textareas[i].value.replace(/\s/g, '').length}文字`;
}
} else {
// 文字数表示枠がまだ作られていないとき
for (const textarea of textareas) {
// 回答欄の親要素の子要素(兄弟要素?)である質問文の要素を取得
const countDisplay = textarea.closest('.geS5n').querySelector('.G4EHhc');
// 文字数表示枠の要素を作る
const count = document.createElement('span');
count.className = 'word-counter';
count.style.color = '#00f';
count.innerHTML = ` ${textarea.value.replace(/\s/g, '').length}文字`;
// 作った要素をcountDisplayの子要素として追加
countDisplay.appendChild(count);
}
}
};
// ページ読み込み後に実行するよう設定
window.addEventListener('load', () => countWords(), false);
// 入力があったら実行するよう設定
window.addEventListener('input', () => countWords());
ここで出てくる意味不明な文字列geS5n
やG4EHhc
は取得したい要素のclass属性の値です。この後詳しく説明します。
コードの補足
基本的な文法のはなし
'use strict'; //厳格モードを使用。意図しないエラーやミスを減らすため。
const func = () => {
}; // アロー関数
const func = fundtion() {
}; // ほぼ同じ(正確にはちょっと違う)
const func = () => 'Hello World'; // 一行で書ける(この時returnがいらない)
// バッククォートでくくると文字が扱いやすい。`${この中はjavascript}ここはテキスト`
// テンプレートリテラルとかいったか
const t = 'もじれつ';
const string = `これは${t}です。` // >> これはもじれつです。
const text = 'これは' + t + 'です。' // 同じ
document.querySelectorAll('.geS5n .word-counter');
クラス名がgeS5n
の要素の子要素でword-counter
クラスを持つ要素をすべて取得する。カンマで区切ればいづれかにヒットするものすべてを取得します。
document.qruerySelector()
なら初めにヒットした一つを取得。
counts[i].innerHTML = ` ${textareas[i].value.replace(/\s/g, '').length}文字`;
.length
で文字数がわかるのですが、改行や空白なども含まれているので先に.replace()
で空白文字を消します。
if (counts[0]) {}
counts
自体はnullにならないのでcounts[0]
で一つ目の有無を見るか、counts.length
で存在確認する必要があります。つまづきポイント。
window.addEventListener('input', () => countWords());
"change"
のイベントリスナーもあるのですが、こちらは入力内容を確定しないと動きません。リアルタイムで変更したいのでinput
を使います。
アップロード
Chrome右上のボタンからManage Extensionをクリックすると、拡張を管理する画面を開けます。
ページ右上のDeveloper modeをオンにし、左上のLoad Unpackedから、今回作成したプログラムの親フォルダーを開けば完了です。プログラムを書き換えた場合でも、再読み込みすればよく、アップロードしなおす必要はありません。
結果
ただ、サイトのJavaScriptの実行完了のタイミングとcountWord()
の実行開始のタイミングがシビアだからか、読み込み直後、0文字でなくても表示が0文字になってしまうことが時々あります。setInterval
で監視すればいいのかな?めんどくさいので不便を感じないのでこのままでいきます。
以上、超基本的なChrome拡張でした。
Googleのサイトの構造は複雑なので気を付けましょう。