12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Chrome拡張でサイトのHTMLを書き換える

Last updated at Posted at 2022-05-22

概要

  • 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.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を作ってもいいです。

f12Ctrl+Shift+I (またはF12)で開発者ツールを開き、左上のカーソルマークを押してからフォームの取得したい要素をを見つけていきます。

一つの要素に複数のclassが割り当てられていることもあるので、同じclass名をもつ要素を比較したりして、Googleがどんなページの作り方をしているのか探していかなければいけません。

めっちゃメンドイ...
スクリーンショット (68).png
結果として、テキストの回答枠(input要素とtextarea要素)のclass名はすべてhhhで、質問文のclass名はすべてG4EHhcで、それぞれの親要素のclass名はすべてgeS5nだとわかりました。

ちなみにGoogleがページの構造変えたら使えなくなる可能性大です(苦笑)

上記をまとめると、フォームは大まかにこういう構造になっていることが分かります。

サイトのHTMLを超簡略化したイメージ
<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>みたいな要素を付け足します。

初めページを読み込んだ時にその要素を付け足したら、あとは入力があるたびに文字数を上書きしていく、というのが大まかな流れです。

メインのコード

content-script.js
'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());

ここで出てくる意味不明な文字列geS5nG4EHhcは取得したい要素の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()なら初めにヒットした一つを取得。

参考:要素の取得方法まとめ - Qiita

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をクリックすると、拡張を管理する画面を開けます。
image.png
ページ右上のDeveloper modeをオンにし、左上のLoad Unpackedから、今回作成したプログラムの親フォルダーを開けば完了です。プログラムを書き換えた場合でも、再読み込みすればよく、アップロードしなおす必要はありません。

結果

いい感じ。
スクリーンショット (66).png

ただ、サイトのJavaScriptの実行完了のタイミングとcountWord()の実行開始のタイミングがシビアだからか、読み込み直後、0文字でなくても表示が0文字になってしまうことが時々あります。setIntervalで監視すればいいのかな?めんどくさいので不便を感じないのでこのままでいきます。

以上、超基本的なChrome拡張でした。
Googleのサイトの構造は複雑なので気を付けましょう。

12
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?