はじめに
今年もアドベントカレンダーの時期になりましたね!
結構前から気になってたchromeの拡張機能を自作してみたので、それについて書こうと思います
何を作るのか
(今年のアドベントカレンダーは何を書こう...)
(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_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の記述が他と変わってきたようなので注意です
<!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>
まじで大したこと書いていないです
body {
width: 300px;
}
.container {
text-align: center;
}
ただサイズ指定して中央寄せしただけです
const container = document.createElement('div');
container.classList.add('snow-container');
document.body.appendChild(container);
雪を降らせる用のdivを用意してあげます
無理やりbodyに追加します
/* ===== 雪を表示するコンテナのスタイル ===== */
.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;
}
}
雪関係のスタイルです
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]
});
}
});
ここが一番のキモです
簡単に説明します
-
chrome.runtime.onInstalled.addListener
は拡張機能がインストールされたときに呼ばれるイベント
ここでコンテキストメニューを追加します
(追加したコンテキストメニュー) -
chrome.contextMenus.onClicked.addListener
はコンテキストメニューがクリックされたときに呼ばれるイベント
自作したコンテキストメニューの選択によって、雪を降らせるかどうかを処理しています -
コンテキストでスクリプトを実行したい場合は、
chrome.scripting.executeScript
を使います
tabId
はchrome.contextMenus.onClicked.addListener
の引数、functionは自作の関数を指定、argsに引数を指定してあげることで選択したコンテキストメニューに応じた処理を作ることができます
// scriptを実行する
if(!tab.url.includes("chrome://")) {
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: fallingSomething,
args: [item]
});
}
さいごに
本当は月ごとに降ってくるものを変えようと思ったのですが、時間が足りず...
後ほど更新するか、来年のアドベントカレンダーの課題にしようと思います(😇)
参考文献
https://developer.chrome.com/docs/extensions/mv3/
https://qiita.com/sakaimo/items/416f36db1aa982d8d00c
https://web-dev.tech/front-end/javascript/snow-falling-effect/