葛山 雅大
葛山 雅大

necco note

BEMを用いたコーディングに便利なChrome拡張機能の作り方

  • Web Development

こんにちは!エンジニアの葛山です。
早速ですが、皆さんはCSSの命名規則やフレームワーク、何を使用されていますか?
私はBEMを使用する機会が多いです。(neccoに入ってからはtailwindを勉強中です)
たくさんclass名を考えて、それをHTMLとCSSファイルに書くのは大変ですよね。
その分、手軽に導入できたりと、メリットもたくさんあるので採用されている方も多いかと思います。

使っていく内にclass名を考えるのは慣れたけれど、CSSファイルに書き写すのは何度やっても一苦労…。
その手間を短縮すべく、表示しているページからclassを抽出し、CSSセレクタに整形するChrome拡張機能を作りました!
(生成されるのはclassセレクターのみで、自動でスタイルまで生成されるものではないのでご注意ください)

この記事では上記の拡張機能を実際に作ってみて「Chromeの拡張機能って意外と手軽に作れるんだ!」という気付きを体験してもらえればと思います!
エンジニアではない方でも手順に沿って作業を行えば簡単につくることができると思うので、楽しく取り組んでください!

※ここでのBEMは命名規則や概念であり、Yandex社が開発しているBEMツールではないのでご注意ください。

今回つくるChrome拡張機能の紹介

完成形はこちらのストアで配信しています。

BEM Like CSS Select Generator

HTMLの情報を取得して、モーダルウインドウでCSS(classセレクターのみ)を出力する拡張機能を作成します。
使い方は簡単!ポップアップ内の入力欄にCSSセレクターを入れるだけです!例えば、bodyと入力すると、body要素を含むclassセレクターがアルファベット順で出力されます。

このように表示されるので、コピーボタンを押してエディターに貼り付けて使用します。

エディターに貼り付けると、行数を見ることでどれぐらいのスタイルを書く必要があるのか分かります。ゴールが明確になると、先が見えない長いLPなどでも心理的負担が軽減されると思います!

では作っていきましょう!

Chrome拡張機能の作り方

予備知識

今回の拡張機能はこちらの2つの記事を参考に作成していきます。

【公開終了しました】STUDIOユーザー向けのChrome拡張機能を制作&公開しました

とほほのChrome拡張機能開発入門

この後、5つのステップで拡張機能を作成します。
コピー&ペーストで作成できるように記載していますので、プログラムの文字列が苦手な方も安心して作業を進めてください!

1.作業する場所を用意する

まずは作業するフォルダをお好みの場所に作ってください。
例では以下のディレクトリ構成で作業します。
“任意のディレクトリ/chrome-extentions/bem-select-generator/“

任意のディレクトリに「chrome-extentions」を作り、その中に「bem-select-generator」のフォルダを作っています。今回はこの”bem-select-generator”フォルダに拡張機能のファイルを作ります。

2.雛形のファイルを用意する

今回の拡張機能のファイル構成はこちらです。

  • manifest.json(拡張機能の設定)
  • popup.js(主要な機能)
  • popup.html(アイコンをクリックすると表示されるポップアップ)
  • style.css(見た目)
  • icon48.png(拡張機能のアイコン)

早速ファイルを作成しましょう。
以下の内容をコピーして「bem-select-generator」フォルダの中にファイルを作成してください。

manifest.json

{
  "manifest_version": 3,
  "name": "BEM Like CSS Select Generator",
  "version": "1.0",
  "description": "BEMコーディング向けの拡張機能です。ページ内のClassを取得してCSSセレクターを出力します。",
  "permissions": ["activeTab", "scripting"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon48.png"
  },
  "icons": {
    "48": "icon48.png"
  }
}

style.css

* {
  box-sizing: border-box;
}

body {
  margin: 0;
}

.popup-wrapper {
  display: block;
  width: 100%;
  background-color: #fff;
  padding: 15px;
  overflow: hidden;
}

.popup-text {
  display: block;
  color: #111111;
  font-family: YuGothic, "Yu Gothic", "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
  font-weight: 500;
  font-size: 16px;
  margin: 0 0 6px;
}

.popup-input {
  display: block;
  font-family: YuGothic, "Yu Gothic", "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
  font-size: 16px;
  border: solid 1px #111111;
  border-radius: 0;
  background-color: #ffffff;
  padding: 1px 6px;
}

.popup-button {
  display: block;
  width: 64px;
  font-family: YuGothic, "Yu Gothic", "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
  font-size: 14px;
  border: solid 1px #111111;
  border-radius: 2px;
  background-color: #ffffff;
  margin-top: 10px;
  padding: 2px 8px;
  cursor: pointer;
}

icon48.png
48px×48pxの画像ならどのようなものでも大丈夫です!
お急ぎの方は以下のものを使用してください。

以下の2つのファイルは中身を空の状態で作成してください。
popup.js
popup.html

3.各々のPCのChromeに拡張機能を登録してみる

早速先ほど用意したファイルを、各々のパソコンのChromeに拡張機能として登録してみます。
まずはChromeの拡張機能のアイコンをクリックして、「拡張機能を管理」を選択し、拡張機能の管理ページに移動します。

次に「デベロッパーモード」をONにします。
そうすると開発者向けのメニューが表示されるので、「パッケージ化されていない拡張機能を読み込む」を選択して、先ほど用意した「bem-css-generator」フォルダを読み込ませます。

これで拡張機能がブラウザに登録されました!
以下のようにピンマークを押すと一覧に表示されるようになります。
この時点ではアイコンをクリックしてもまだ何も起きません。

4.簡単な機能を付けて試してみる

せっかくなので試しに簡単な機能を作成して、動作確認をしてみましょう!
Webサイトの色を箱猫ちゃんカラーに変更する機能を追加してみます。
コピペするだけなので、プログラムにアレルギーがある方もご安心ください。

popup.htmlpopup.jsの2つのファイルを修正します。
以下のコードを順番にファイルに貼り付けてください。

popup.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>背景色変更</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="popup-wrapper">
    <button id="popup-button" class="popup-button">実行</button>
    <script src="popup.js"></script>
  </div>
</body>
</html>

popup.js

document.getElementById('popup-button').addEventListener('click', () => {
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    chrome.scripting.executeScript({
      target: { tabId: tabs[0].id },
      func: () => {
        const elements = document.querySelectorAll('*');
        elements.forEach(element => {
          element.style.color = '#ffffff';
          element.style.backgroundColor = '#1e2c45';
          element.style.borderColor = '#ffffff';
        });
      },
    });
  });
});

ファイルを修正したら、登録した拡張機能の情報を更新する必要があります。
拡張機能一覧画面のこちらの更新ボタンを押して、修正内容を反映してください。

更新ができたら、お好みのWebサイトで実行すると色が変わります!
試しに箱猫ちゃんの紹介ページで使用した様子がこちらです。

これが…

こうなります!
すべての要素が箱猫ちゃんカラーになって、箱猫ちゃんもどこか嬉しそうです。

このようにして機能を付け加えることが可能です!
コードも数行なので、プログラミングの経験がない方でもAIに説明をお願いすれば理解しやすいかと思います。
次は今回の目玉機能を作成していきます。

5.CSSセレクターを出力する

待ちに待ったCSSセレクターを出力する機能を作成しましょう。
さきほどと同じようにpopup.htmlpopup.jsの2つのファイルを修正します。
以下のコードを順番にファイルに貼り付けて、中身をまるっと差し替えてください。

popup.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>BEM Like CSS Select Generator</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="popup-wrapper">
    <p class="popup-text">CSSセレクターを入力</p>
    <input type="text" id="popup-input" class="popup-input">
    <button id="popup-button" class="popup-button">実行</button>
    <script src="popup.js"></script>
  </div>
</body>
</html>

popup.js

document.getElementById("popup-button").addEventListener("click", async () => {
  const cssSelector = document.getElementById("popup-input").value;

  let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    args: [cssSelector],
    function: cssSelectorGenerate
  });
});

function cssSelectorGenerate(args) {
  let cssSelector = '';
  const classList = [];
  const element = document.querySelector(args);
  const elements = [element, ...element.querySelectorAll('*')];

  for (var i = 0; i < elements.length; i++) {
    var classNames = elements[i].getAttribute('class');
    if (classNames) {
      var classes = classNames.split(' ');
      for (var j = 0; j < classes.length; j++) {
        var className = classes[j].trim();
        if (className && !classList.includes(className)) {
          classList.push(className);
        }
      }
    }
  }

  classList.sort().forEach(function (val, i) {
    cssSelector += '.' + val + ' {\n  \n}\n\n';
  });

  const modal = `
    <div id="modal1" class="bemlcsg-modal js-modal">
      <div class="bemlcsg-modal-inner">
        <div class="bemlcsg-modal-box">
          <button type="button" class="bemlcsg-modal-close-button js-modal-close">x</button>
          <p class="bemlcsg-modal-title">CSSセレクター</p>
          <button type="button" class="bemlcsg-modal-copy-button js-copy-button">コピー</button>
          <pre class="bemlcsg-modal-css-box js-css-box">${cssSelector}</pre>
          <button type="button" class="bemlcsg-modal-bottom-close-button js-modal-close">閉じる</button>
        </div>
        <span class="bemlcsg-modal-bg js-modal-close"></span>
        <style>
        .bemlcsg-modal {
          display: none;
          width: 100%;
          height: 100%;
          font-family: YuGothic, "Yu Gothic", "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif !important;
          margin: auto;
          padding: 0;
          overflow-y: auto;
          position: fixed;
          top: 0;
          left: 0;
          z-index: 5000;
          box-sizing: border-box !important;
        }
        .bemlcsg-modal * {
          font-family: YuGothic, "Yu Gothic", "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif !important;
          box-sizing: border-box !important;
        }
        .bemlcsg-modal-inner {
          display: flex;
          flex-wrap: wrap;
          justify-content: center;
          align-items: center;
          width: 100%;
          height: auto;
          min-height: 100vh;
          padding: 20px 20px 100px;
          position: relative;
          z-index: 2;
        }
        .bemlcsg-modal-box {
          display: block;
          width: 100%;
          color: #111111;
          background-color: #ffffff;
          border-radius: 8px;
          max-width: 700px;
          margin-top: 80px;
          padding: 40px;
          position: relative;
          z-index: 10;
        }
        .bemlcsg-modal-title {
          font-size: 18px;
          line-height: 1.5;
        }
        .bemlcsg-modal-css-box {
          display: block;
          font-size: 15px;
          line-height: 1.2;
          background-color: #f1f1f1;
          border-radius: 6px;
          margin-top: 20px;
          padding: 20px;
        }
        .bemlcsg-modal-copy-button {
          display: block;
          width: auto;
          font-size: 16px;
          line-height: 1.2;
          margin-right: auto;
          background-color: #ffffff;
          border: solid 1px #111111;
          border-radius: 2px;
          margin-top: 15px;
          padding: 8px 12px 6px;
          cursor: pointer;
        }
        .bemlcsg-modal-bg {
          display: block;
          width: 100%;
          height: 100%;
          background-color: rgba(0, 0, 0, .5);
          margin: auto;
          position: absolute;
          top: 0;
          left: 0;
          z-index: 5;
        }
        .bemlcsg-modal-close-button {
          display: block;
          width: 36px;
          height: 36px;
          color: #333333;
          background-color: #333333;
          border-radius: 999px;
          position: absolute;
          top: 15px;
          right: 15px;
          z-index: 2;
          cursor: pointer;
        }
        .bemlcsg-modal-close-button::before, .bemlcsg-modal-close-button::after {
          content: '';
          display: block;
          width: 20px;
          height: 2px;
          background-color: #ffffff;
          margin: auto;
          position: absolute;
          inset: 0;
        }
        .bemlcsg-modal-close-button::before {
          transform: rotate(45deg);
        }
        .bemlcsg-modal-close-button::after {
          transform: rotate(-45deg);
        }
        .bemlcsg-modal-bottom-close-button {
          display: block;
          width: auto;
          color: #111111;
          font-size: 14px;
          line-height: 1.2;
          background-color: #ffffff;
          border-bottom: solid 1px #111111;
          border-width: 0 0 1px;
          margin: 30px auto 0;
          padding: 0 .25em .275em;
          cursor: pointer;
        }
        </style>
      </div>
    </div>
  `;

  document.querySelector('body').insertAdjacentHTML('beforeend', modal);

  const modalItem = document.querySelector('.js-modal');
  modalItem.style.display = 'block';

  const closeButtons = modalItem.querySelectorAll('.js-modal-close');
  closeButtons.forEach(function (closeButton) {
    closeButton.addEventListener('click', function () {
      modalItem.style.display = 'none';
      document.body.removeChild(modalItem);
    });
  });

  const copyButton = document.querySelector('.js-copy-button');
  const cssBox = document.querySelector('.js-css-box');

  copyButton.addEventListener('click', () => {
    const textToCopy = cssBox.textContent;

    modalItem.style.display = 'none';
    document.body.removeChild(modalItem);

    navigator.clipboard.writeText(textToCopy)
      .then(() => {
        alert('内容をコピーしました!');
      })
      .catch(err => {
        console.error('コピーに失敗しました:', err);
      });
  });
}

これで機能が備わりました!

完成!早速拡張機能を使ってみよう!

完成しましたね!🎉
せっかくなので早速使ってみましょう。
ファイルを修正した後は、拡張機能一覧画面の更新ボタンを忘れずに押しましょう。

お好きなページで拡張機能のアイコンをクリックして、セレクターを入力すると…

無事に動作しました!🎉

もしカスタマイズなどして、ストアで配信されたい場合には申請の手続きが必要になります。
ご興味がある方はこちらの公式のページをご確認ください。

おわりに〜気軽に拡張機能をつくろう!〜

このように、ちょっとした機能は比較的簡単に実装することができます!
今ならエンジニア職ではない方でも、AIに質問しながら進めれば、痒いところに手が届く孫の手のような機能を簡単につくることができると思います。
ストアで配信までしなくても、知人や部署内などで便利なツールを共有し合えば、生産性アップ間違いなしです!

今回の機能、最初はChromeの拡張機能ではなく、ブラウザの開発者ツール内で使用する形式のものでした。
個人で使用する分には問題なく使用できていたのですが、せっかくなので世界のどこかにいるかもしれない、同様の機能を求めている方にも使ってみてもらいたい。きっとこういうちょっとした便利なツールは世の中にたくさん眠っていて、それをみんなで気軽にシェアできれば作業がもっと楽しくなるはず…!
と思いChromeの拡張機能を作成するに至りました。

この記事を読んでくださった方も、こんな機能をつくってみた!普段何気なくやってるルーティンワークをChromeの拡張機能にしてみた!などなど、ぜひ取り組んでみてもらえたら嬉しいです!

葛山 雅大

葛山 雅大

Masahiro Katsuyama

愛知県豊橋市生まれ、田原市育ち。電気工事士→車の部品工場→ウェブデザイナー兼コーダーを経験し、2024年5月よりneccoにアシスタントフロントエンドエンジニアとして入社。アニメやゲームなどのサブカルなエンタメが生きがい。モーショングラフィックスやWebGLを使用したアニメーションの表現が好物。好きなバンドはamazarashi。犬・猫・箱猫ちゃん・おばけちゃんのような可愛い生き物が好き。

SHARE

Other Note

necco note

一つの制作会社にデザインと実装を一貫して依頼するメリットとは?