LoginSignup
2
0

More than 1 year has passed since last update.

はじめに

今年もアドベントカレンダーの時期になりましたね!
結構前から気になってたchromeの拡張機能を自作してみたので、それについて書こうと思います

何を作るのか

image.png
(今年のアドベントカレンダーは何を書こう...)
(Qiitaのアドベントカレンダーのヘッダーがとても可愛いな...)
(どうせなら雪でも降らしたろか!)

というわけで、ページに雪を降らせるchromeの拡張機能を作りたいと思います!

ファイル構成

src/
 ┣ manifest.json・・・拡張機能の設定ファイル
 ┣ popup.html・・・拡張機能のアイコンをポチったときに表示されるやつ
 ┣ style.css・・・htmlの装飾用
 ┣ index.js・・・ブラウザに対して操作する用のjs
 ┣ index.css・・・ブラウザに対して操作する用のcss
 ┗ event.js・・・backgroundで動く用のjs

細かいファイル構成の情報は@sakaimoさんがまとめている記事がすごくわかりやすいので、そちらを読んでください
https://qiita.com/sakaimo/items/416f36db1aa982d8d00c

仕様

コンテキストメニューにボタンを追加して、そこで雪を降らせたり止ませたりできるようにしたい

※なお雪を降らせるソースについては、いろいろな人が書いているので省略します
今回は下記の記事を参考にさせていただきました
https://web-dev.tech/front-end/javascript/snow-falling-effect/

ソース

manifest.json
{
  "manifest_version": 3,
  "name": "test app",
  "version": "1.0",
  "description": "test app",
  "action": {
    "default_title": "test app",
    "default_popup": "popup.html"
  },
  "content_scripts": [{
    "matches": ["http://*/*", "https://*/*"],
    "js": [
      "index.js"
    ],
    "css": [
      "index.css"
    ]
  }],
  "background": {
    "service_worker": "event.js"
  },
  "permissions": [
    "contextMenus",
    "activeTab",
    "tabs",
    "scripting"
  ]
}

manifestファイルのバージョンが3になってからbackgroundの記述が他と変わってきたようなので注意です


popup.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
</head>
  <body id="body">
    <div class="container">
      <h1>何が降るかな</h1>
    </div>
  </body>
</html>

まじで大したこと書いていないです


style.css
body {
  width: 300px;
}

.container {
  text-align: center;
}

ただサイズ指定して中央寄せしただけです


index.js
const container = document.createElement('div');
container.classList.add('snow-container');
document.body.appendChild(container);

雪を降らせる用のdivを用意してあげます
無理やりbodyに追加します


index.css
/* ===== 雪を表示するコンテナのスタイル ===== */
.snow-container {
  position: fixed;
  top: 0;
  left: 0;
  background-color: transparent;
  height: 100vh;
  width: 100%;
  overflow: hidden;
  z-index: 9999;
  display: none;
}

/* ===== 雪のスタイル ===== */
.snow {
  background-color: #fff; /* 雪の色 */
  border-radius: 50%;
  position: absolute;
  animation: animate-snow 10s linear;
}

/* ===== 雪のアニメーション ===== */
@keyframes animate-snow {
  0% {
    opacity: 0;
    top: 0;
  }

  10% {
    opacity: 1;
  }

  90% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    top: 100vh;
  }
}

雪関係のスタイルです


event.js
chrome.runtime.onInstalled.addListener(function(){
  // 親階層のメニューを生成
  const parent_menu = chrome.contextMenus.create({
    type: "normal",
    id: "parent",
    title: "雪を降らせるメニュー"
  });
  //子階層のメニューを親(parent_menu)に追加
  chrome.contextMenus.create({
    id: "0",
    parentId: parent_menu,
    title: "降らせる"
  });
  chrome.contextMenus.create({
    id: "1",
    parentId: parent_menu,
    title: "止める"
  });
});

/**
  * メニューが選択されたときの処理
  * 選択されたアイテムはこちらの関数の引数に入ってくる(今回は item)
  * menuItemIdでmenuのidが文字列で取得できる
  */
chrome.contextMenus.onClicked.addListener(function(item, tab){
  function fallingSomething(item) {
    const snowConteiner = document.querySelector('.snow-container');
    // 雪を生成する関数
    const createSometing = () => {
      snowConteiner.style.display = 'block';
      const snowEl = document.createElement('span');
      snowEl.className = 'snow';
      const minSize = 10;
      const maxSize = 30;
      const size = Math.random() * (maxSize - minSize) + minSize;
      snowEl.style.width = `${size}px`;
      snowEl.style.height = `${size}px`;
      snowEl.style.left = Math.random() * 100 + '%';
      snowConteiner.appendChild(snowEl);
      // 一定時間が経てば雪を消す
      setTimeout(() => {
        snowEl.remove();
      }, 10000);
    }
    // 繰り返し処理の制御
    function exec_interval() {
      let timerArray = (JSON.parse(localStorage.getItem('timerArray')) !== null) ? JSON.parse(localStorage.getItem('timerArray')) : [];
      const timerId = setInterval(createSometing, 100);
      timerArray.push(timerId);
      localStorage.setItem('timerArray', JSON.stringify(timerArray));
    }
    function stop_interval() {
      let timerArray = (JSON.parse(localStorage.getItem('timerArray')) !== null) ? JSON.parse(localStorage.getItem('timerArray')) : [];
      if (timerArray.length > 0) {
        for (let i = 0; timerArray.length > 0; i++) {
          clearInterval(timerArray.shift());
        }
        snowConteiner.style.display = 'none';
      }
      localStorage.setItem('timerArray', JSON.stringify(timerArray));
    }

    // コンテキストメニューの選択によって処理
    switch(item.menuItemId) {
      case "0":
        exec_interval();
        break;
      case "1":
        stop_interval();
        break;
      default:
        stop_interval();
    }
  }

  // scriptを実行する
  if(!tab.url.includes("chrome://")) {
    chrome.scripting.executeScript({
      target: { tabId: tab.id },
      function: fallingSomething,
      args: [item]
    });
  }
});

ここが一番のキモです
簡単に説明します

  1. chrome.runtime.onInstalled.addListenerは拡張機能がインストールされたときに呼ばれるイベント
    ここでコンテキストメニューを追加します
    image.png
    (追加したコンテキストメニュー)

  2. chrome.contextMenus.onClicked.addListenerはコンテキストメニューがクリックされたときに呼ばれるイベント
    自作したコンテキストメニューの選択によって、雪を降らせるかどうかを処理しています

  3. コンテキストでスクリプトを実行したい場合は、chrome.scripting.executeScriptを使います
    tabIdchrome.contextMenus.onClicked.addListenerの引数、functionは自作の関数を指定、argsに引数を指定してあげることで選択したコンテキストメニューに応じた処理を作ることができます

  // scriptを実行する
  if(!tab.url.includes("chrome://")) {
    chrome.scripting.executeScript({
      target: { tabId: tab.id },
      function: fallingSomething,
      args: [item]
    });
  }

こうすることで雪を降らせることができるようになりました!
image.png

さいごに

本当は月ごとに降ってくるものを変えようと思ったのですが、時間が足りず...
後ほど更新するか、来年のアドベントカレンダーの課題にしようと思います(😇)

参考文献

https://developer.chrome.com/docs/extensions/mv3/
https://qiita.com/sakaimo/items/416f36db1aa982d8d00c
https://web-dev.tech/front-end/javascript/snow-falling-effect/

2
0
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
2
0