新しいタブページは自分で作ってしまおう

# はじめに

みなさんはブラウザの新しいタブで何が表示されるように設定してますか？ホームページ、空のページ、あるいはストアで手に入れた拡張機能で作られたページでしょうか？

ここで「ストアで手に入れた拡張機能で作られたページ」と答えた方に特に伝えたい。新しい選択肢として「自分で作ってしまう」というのはどうでしょうか？

というわけで、この記事では新しいタブページを自分でつくってみた話をしたいと思います。
（正直、何番煎じかわからないくらい手垢のついたネタだとは思いますが、経緯のところを特に話したいので...）

# 自作するに至った経緯

昔、[CaretTab](https://chromewebstore.google.com/detail/carettab-新しいタブダッシュボード/cojpndognjdcakkimaloeealehpkljna)という新しいタブをカスタマイズする拡張機能を使っていました。

これは新しいタブの背景を変えたり時計やカレンダーなどのウィジェットを配置したりすることができる拡張機能でした。

そんなある日のこと、というか忘れもしない2023年7月27日の出来事ですが、拡張機能のアップデートによってカスタム要素がガラッと変わり、また設定していた内容もすべてリセットされてしまう出来事がありました。
（アプリストアのレビューを見るとそのあたりで批判が噴出している模様が垣間見れます。）

まあ文句を言う人の気持ちも理解できますし自分もショックでしたが、むしろこれを機に「自分でつくったほうが破壊もされないし作りたいように作れるし最高なのでは？」と思ったわけです。

※このネタを2023年夏に思いついて2024年冬にやっと記事にする腰の重さには目をつぶってください...

# つくったもの

こんな感じです。

![自作した新しいタブのイメージ](https://www-tacos.github.io/qiita-images/20241201_browser_custom_tab/001.png)

コード構成はこんな感じです。たぶんですが、JS・CSSを含む新しいタブ拡張機能の最小構成だと思います。

```txt:コード構成
/root
├ /css
│ └ style.css
│
├ /js
│ ├ main.js
│ ├ particles.config.js
│ └ particles.min.js
│
├ index.html
└ manifest.json
```

拡張機能の作り方についてはいろんな記事があると思いますが、自分は以下を参考にさせてもらいました。

https://qiita.com/ut0n/items/f72fb5568ab831a9fd12

デザインとしては「幾何学模様の背景にいい感じの時計があるといいな～」と思っていたので、その方向で調べたところ particles.js を知りました。

https://vincentgarreau.com/particles.js/

設定できる項目が多いので、使ってみたい方は上記デモページでいろいろ試してから設定ファイルをエクスポートするといいと思います。

時計については以下サイトを参考、というかほぼぞのまま使わせてもらいました。

https://web-dev.tech/front-end/javascript/digital-clock/

いちおうコードも貼っておくので気になる方はどうぞ。

<details><summary>コード</summary>

```json:manifest.json
{
  "name": "my-new-tab-page",
  "manifest_version": 3,
  "description": "My new tab page.",
  "version": "0.0.1",
  "permissions" : ["management"],
  "chrome_url_overrides" : {
    "newtab": "index.html"
  }
}
```

```html:index.html
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="icon" href="data:,">
  <link rel="stylesheet" href="css/style.css">
  <script src="js/particles.config.js"></script>
  <script src="js/particles.min.js"></script>
  <title>newtab</title>
</head>

<body>
  <div id="div-particles"></div>
  <div class="container">
    <div class="clock">
      <p class="clock-date"></p>
      <p class="clock-time"></p>
    </div>
  </div>
  <script src="js/main.js"></script>
</body>

</html>
```

```css:style.css
/* Google Fonts 読み込み */
@import url('https://fonts.googleapis.com/css2?family=Proza+Libre:wght@600&display=swap');

/* 文字選択を禁止 */
* {
  user-select: none;
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
}

/* 背景色 */
html,
body {
  background-color: #3b3b3b;
}

/* particleの全画面化 */
div#div-particles>canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

/* 全体を囲うコンテナー */
.container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  pointer-events: none;
}

/* 時計の共通スタイル */
.clock {
  font-family: 'Proza Libre', monospace;
  color: #ffffff;
  text-shadow: 0 0 20px #f9c100;
  line-height: 1.2;
  text-align: center;
}

/* 日付のスタイル */
.clock-date {
  font-size: 48px;
  margin: 10% 0px;
}

/* 時間のスタイル */
.clock-time {
  font-size: 126px;
  margin: 10% 0px;
}
```

```js:main.js
const clock = () => {
  const d = new Date();

  // 年,月,日,曜日,時,分,秒
  const year = d.getFullYear();
  const month = `${d.getMonth() + 1}`.padStart(2, '0');
  const date = `${d.getDate()}`.padStart(2, '0');
  const day = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getDay()];
  const hour = `${d.getHours()}`.padStart(2, '0');
  const min = `${d.getMinutes()}`.padStart(2, '0');
  const sec = `${d.getSeconds()}`.padStart(2, '0');

  // 日付・時刻の文字列を作成
  const today = `${year}.${month}.${date} ${day}`;
  const time = `${hour}:${min}`;

  // 文字列を出力
  document.querySelector(".clock-date").innerText = today;
  document.querySelector(".clock-time").innerText = time;
};

// 1秒ごとにclock関数を呼び出す
clock();
setInterval(clock, 1000);

// パーティクルの表示
particlesJS('div-particles', PTCL_CONFIG);
```

```js:particles.config.js
const PTCL_CONFIG = {
  "particles": {
    "number": {
      "value": 150,
      "density": {
        "enable": true,
        "value_area": 800
      }
    },
    "color": {
      "value": "#ffffff"
    },
    "shape": {
      "type": "circle",
      "stroke": {
        "width": 0,
        "color": "#000000"
      },
      "polygon": {
        "nb_sides": 5
      },
      "image": {
        "src": "img/github.svg",
        "width": 100,
        "height": 100
      }
    },
    "opacity": {
      "value": 0.5,
      "random": true,
      "anim": {
        "enable": false,
        "speed": 1,
        "opacity_min": 0.1,
        "sync": false
      }
    },
    "size": {
      "value": 40,
      "random": true,
      "anim": {
        "enable": false,
        "speed": 40,
        "size_min": 0.1,
        "sync": false
      }
    },
    "line_linked": {
      "enable": false,
      "distance": 200,
      "color": "#ffffff",
      "opacity": 0.75,
      "width": 0.628960485557495
    },
    "move": {
      "enable": true,
      "speed": 6,
      "direction": "none",
      "random": false,
      "straight": false,
      "out_mode": "out",
      "bounce": false,
      "attract": {
        "enable": false,
        "rotateX": 600,
        "rotateY": 1200
      }
    }
  },
  "interactivity": {
    "detect_on": "canvas",
    "events": {
      "onhover": {
        "enable": false,
        "mode": "grab"
      },
      "onclick": {
        "enable": false,
        "mode": "repulse"
      },
      "resize": true
    },
    "modes": {
      "grab": {
        "distance": 400,
        "line_linked": {
          "opacity": 1
        }
      },
      "bubble": {
        "distance": 400,
        "size": 40,
        "duration": 2,
        "opacity": 8,
        "speed": 3
      },
      "repulse": {
        "distance": 200,
        "duration": 0.4
      },
      "push": {
        "particles_nb": 4
      },
      "remove": {
        "particles_nb": 2
      }
    }
  },
  "retina_detect": true
}
```

</details>

# さいごに

なんだかんだこの自作ページにしてから1年半立ちましたが、気に入って使い続けています。

また、これは後から気づいたのですが、フルスクリーン表示にするとスクリーンセーバーっぽくなるところも気に入ってます。

最後まで読んでいただきありがとうございました。