究極に便利な検索サイトを作った【Ultimate search】

Qiita初投稿失礼します、たぬきっつぁんと申します。一応高校生です。

2年ほど前から勉強し始めたHTML/CSS/JavaScriptを使って初めて作った、ネット検索を便利にするために作ったサイトについて紹介します。初めて技術系の記事を書いたので読みづらい点もあると思いますが、よろしくお願いします。

更新（2021/07/01）：[Instagramボタンの機能](#instagramボタンの機能)と[検索機能](#検索の仕組み)を一部修正したため記事を修正しました

更新（2021/04/22）：[Ultimate searchのキーワード選択メニューが自分でカスタマイズできるようになりました！](#キーワード選択メニューが自分でカスタマイズできるようになりました)

## Googleっぽいサイトを作ってみたい！

最初の動機はそんな感じでした。Googleのサイトってとてもシンプルなのでデザインだけなら自分のような初心者でも作れるのではないかと思った次第です。

しかし完全に同じサイトを作るのはただのコピーサイトになってしまうので、どうせだったらGoogle以外のサイトも一緒に検索できるサイトを作ることにしました。

ここからはUltimate searchができるまでに作った4つの検索サイトを紹介していきます。

## GoogleAmazonTwitterYouTubeサーチ、略して「GATY search」

![app.tanukizzan.com_gaty-search_ (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535595/933f723f-38a8-b31a-5d9d-74e5a180bf00.png)

リンク：https://app.tanukizzan.com/gaty-search/

初めて作ったサイトがこちらです。使い方は[Google](https://www.google.co.jp/)と基本的には一緒で、キーワードを入れて検索したいサイトのボタンを押すとそのサイトで検索されます。いつもの感覚でEnterキーを押すとGoogle検索になります。もしも検索キーワードを入れずにボタンを押した場合、サイトのURLだけを出力します。読み方は**「ガーティーサーチ」**です。

最初はGoogle, Amazon, Twitter, YouTubeのみでしたが、現在は楽天市場とウィキペディアも追加しています。I'm Feeling Luckyボタンを押すと本家Googleとは違い、この中のサイトからランダムに検索されます。

## よく使うツールの検索を便利にするために作った「HowTo search」

![app.tanukizzan.com_howto-search_ (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535595/ed325ee6-fd6f-b488-aae8-e300eff729a7.png)

リンク：https://app.tanukizzan.com/howto-search/

GATY searchを作っていたときに、「javascript 入門」や、「javascript 配列 書き方」など、特定のキーワードと調べたい語句を組み合わせて調べることがたくさんありました。そこで作ったのが「HowTo search」です。読み方は**「ハウトゥーサーチ」**です。

プルダウンメニューから選択する形で、普段使うツールや学校の授業で使用するであろうツールをたくさん載せました。（アドビツールやプログラミングツールなど）選んだら右が検索ボックスになっているのでそこにキーワードを入れれば検索できます。またボタンがGoogle検索しかないので見た目的にはこれが一番Googleに近いかもしれません。

## Googleから浮気した「Simple search」

![simple-search](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535595/31e06248-c271-954a-3ddf-c21ef54330a0.png)

リンク：https://app.tanukizzan.com/simple-search/

今まではGoogle的なデザインで作ってきましたが、Microsoftの[Bing](https://www.bing.com/)のような美しい背景画像のサイトに興味があったため、今度はBingっぽいサイトを作ってみることにしました。読み方は**「シンプルサーチ」**です。

背景画像は[PlaceIMG](https://placeimg.com/)から読み込んでいます。画像のカテゴリーを指定して使えるため、Simple searchでは自然の画像を毎回ランダムに表示させています。検索ボックスのデザインもできるだけシンプルに、検索ボックスの周りで全ての操作ができるようになっています。

日本でメジャーなGoogleやYahoo!、元ネタとなったBing、プライバシーに特化したDuckDuckGoで検索できます。

## 若者はGoogleよりハッシュタグ検索を使うと聞いて作った「HashTag search」

![app.tanukizzan.com_hashtag-search_ (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535595/c03821fa-cfe5-87d0-ef52-015b41b5c742.png)

リンク：https://app.tanukizzan.com/hashtag-search/

ふとテレビを見ていたらGoogle検索は使わず各種SNSのハッシュタグ検索ばかり使うという若者の話を聞きました。そういえば今まではGoogleやBingなど検索エンジンばかり意識してSNS検索の機能をあまり搭載していなかったと思い、一旦Google検索から離れてハッシュタグ検索限定のサイトを作りました。

自分の中でハッシュタグ検索と聞いて思い浮かんだ、Twitter, Instagram, Facebook, noteの4サイトで検索できます。デザインはGATY風のデザインに戻って虫眼鏡マークが代わりに#マークになっています。

ただ、自分自身はあまりハッシュタグ検索をしないことに**作ってから気が付きました。**（若者のくせに）せっかく書いたコードを活用しないのはもったいないと思い、次のサイトの制作につながります。

## 全部組み合わせた究極の検索サイト「Ultimate search」

![スクリーンショット 2021-04-22 19.01.43.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535595/74b4e83e-9283-a058-1e91-507789a873cc.jpeg)

リンク：https://app.tanukizzan.com/ultimate-search/
GitHub：https://github.com/tanukizzan/ultimate-search/

**ここからが本題です。**今まで作ってきたGATY, HowTo, Simple, HashTagの全てを組み合わせた究極のサイト、**「Ultimate search」**を作りました。これでもう新しい検索サイトを作ることはないでしょう。（ultimate=究極）読み方は**「アルティメットサーチ」**です。

サイトボタン部分の今までにない特徴として、丸いアイコンの列がその行のボタンの説明になっています。Googleアイコンの行はGoogleサービスが、虫眼鏡マークの行はGoogle以外の検索サービスが、買い物カゴマークの行はECサイトが、ハッシュタグマークの行はHashTag searchのサイトが、シャッフルマークにはI'm Feeling Luckyボタンが配置されています。過去にI'm Feeling Luckyボタンの意味がよくわからないと周りの人に言われたことがあったので、シャッフルマークをつけることで多少わかりやすくなったかと思います。これによって新たに検索サイトを増やしたくなっても横に増やしやすくなりました。

またアイコン列の中でも**ハッシュタグマークだけ押せる**ようになっていて、押すと検索ボックスにハッシュタグが追加されます。これによってHashTag searchの機能を実装しています。ハッシュタグ検索に対応しているサイトも同じ行のサイトなのでわかりやすいですね！（行外ですがYouTubeも最近ハッシュタグ機能ができたのでYouTubeでも使えます）それからこの行のサイトのみ@マークを先頭に入れてSNSアカウントのID名を入力すると直接プロフィールに飛ぶ仕様になっています。

これを作ったおかげで今まで作った4つのサイトを行き来する必要なく1ページで済むようになりました。ちなみに私はPCでもスマホでもUltimate searchをブラウザのトップページに設定しています。

ちなみに左下の`日本`はただの飾りです。機能は特にありません。本家Googleにも書いてあったため載せてみました。

### Instagramボタンの機能

Instagramではハッシュタグ検索のみ利用できます。~~最近一部の国でキーワード検索が[できるようになった](https://www.gizmodo.jp/2020/11/instagram-145.html)ようですが、まだ日本では使えないためハッシュタグがついていない状態でボタンを押すとアラートが出ます。~~

この記事を書いてから半年近く経ちますがInstagramのキーワード検索が一向に日本国内で解禁されないため、自動でハッシュタグの付与とキーワード間のスペースが除去されるように変更しました。解禁されたらできるだけ早めに対応します。

### キーワード選択メニューが自分でカスタマイズできるようになりました！

![スクリーンショット 2021-04-22 16.27.57.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535595/e5eb1881-9829-2a18-71f6-44accaf47ec1.jpeg)

`キーワードを選択...`改め、キーワードボックスを自分でカスタマイズできるようにアップデートしました！よく使うキーワードをブラウザに保存でき、またタブを閉じてしまっても利用できます。

使い方は左下の`検索設定`をクリックします。

![スクリーンショット 2021-04-22 18.56.21.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535595/0c30889b-b4fd-f4be-06da-d268e326827d.jpeg)

編集メニューが出てきたら**カンマ区切りでスペースを開けずに**編集します。「ブラウザに保存」を押すと編集後もずっとブラウザに保存されます。また削除したい場合やデフォルトの設定に戻したい場合は「ブラウザから削除」を押してください。その後再読み込みするとデフォルトのリストがまた設定されます。

またアップデートに伴って挙動が若干変わりました。今まではキーワードボックスと検索ボックスを別々に処理していましたが、選択したら検索ボックスに入力するように変更したためコードが以前より単純になりました。またこの仕様に変更したおかげで何個でもキーワードを入力できるようになりました。

詳しい仕組みは「検索の仕組み」で解説します。

## 検索の仕組み

本家Googleでも[検索の仕組み](https://google.com/search/howsearchworks/)というページがあったので、Ultimate searchでも仕組みを解説してみます。コードだけ見たい方は[GitHub](https://github.com/tanukizzan/ultimate-search/)をご覧ください。

ざっくり言うと、ボタンを押したら__サイトのURL__と__検索クエリ__と__キーワード__の組み合わせを出力するような仕組みになっています。

追記：検索キーワードの前後の空白を取り除く`.replace()`の処理を追加しました。このコードは[こちら](https://gray-code.com/javascript/trim-blank-of-string/)のサイトを参考にさせていただきました。

```javascript
const keyword = 'キーワード';
window.open('https://www.google.co.jp/' + 'search?q=' + encodeURIComponent(keyword.value.replace(/^[\p{C}\p{Z}]+|[\p{C}\p{Z}]+$/gu, '')));
```

これをサイトボタンごとに作っています。例としてGoogle検索ボタンのコードはこんな感じです。

```javascript
let wordInput = document.getElementById('window'); // 検索ボックス
const googleSearch = document.getElementById('google'); // Google検索ボタン
const google = 'http://www.google.co.jp/'; // Googleリンク
const query = 'search?q='; // 検索クエリ

googleSearch.onclick = () => {
  if (wordInput.value.length === 0) {
    window.open(google);
  } else {
    window.open(google + query + encodeURIComponent(wordInput.value.replace(/^[\p{C}\p{Z}]+|[\p{C}\p{Z}]+$/gu, '')));
  }
}
```

仕組み自体はシンプルで、検索ボックスが空の場合はGoogleのトップページに遷移して、1文字でも入力されていた場合は検索される仕組みになっています。ハッシュタグ検索に対応しているサイトについては次の項目で解説します。

### ハッシュタグマークの行のみの機能

ハッシュタグボタンを押してハッシュタグをつけた場合、対応しているサイトのみハッシュタグ検索ができます。そのまま出力してしまうとエラーが出てしまうので`.replace()`を使って#マークを除去しています。ここでは例としてTwitterボタンで解説します。

``` javascript
if (keyword.value.match(/^#/) {
  window.open('https://twitter.com/' + 'hashtag/' + encodeURIComponent(keyword.value.replace(/^#+|[\p{C}\p{Z}]/gu, '')));
}
```

アットマーク（@）をつけた場合は直接そのID名のユーザープロフィールに直接アクセスすることができます。

``` javascript
if (keyword.value.match(/^@/) {
  window.open('https://twitter.com/' + encodeURIComponent(keyword.value.replace(/^@+|[\p{C}\p{Z}]/gu, '')));
}
```

先ほどの検索プログラムと合体するとこうなります。

```javascript
const twitter = document.getElementById('twitter'); // Twitterボタン

twitter.onclick = () => {
  const link = 'https://twitter.com/';
  if (wordInput.value.length === 0) {
    window.open(link);
  } else if (wordInput.value.match(/^@/)) {
    window.open(link + encodeURIComponent(wordInput.value.replace(/^@+|[\p{C}\p{Z}]/gu, '')));
  } else if (wordInput.value.match(/^#/)) {
    window.open(link + 'hashtag/' + encodeURIComponent(wordInput.value.replace(/^#+|[\p{C}\p{Z}]/gu, '')));
  } else {
    window.open(link + query + encodeURIComponent(wordInput.value.replace(/^[\p{C}\p{Z}]+|[\p{C}\p{Z}]+$/gu, '')));
  }
}
```

普通の検索ボタンに新たに

- 先頭に@マークがついていたらそのID名のプロフィールにアクセス
- 先頭に#マークがついていたらハッシュタグ検索

の機能を追加しました。

### I'm Feeling Luckyボタンの仕組み

先ほどの例ではサイトボタンごとに変数を用意していましたが、ランダム検索機能を作るには面倒なので実際は配列にまとめて配列の数だけランダムに選択するという仕組みになっています。

```javascript
const button = [
  document.getElementById('google'),
  document.getElementById('gimages'),
  document.getElementById('gmaps'),
  document.getElementById('gtrans'),
  document.getElementById('youtube'),
  document.getElementById('yahoo'),
  document.getElementById('bing'),
  document.getElementById('duck'),
  document.getElementById('wiki'),
  document.getElementById('amazon'),
  document.getElementById('rakuten'),
  document.getElementById('y-shopping'),
  document.getElementById('twitter'),
  document.getElementById('instagram'),
  document.getElementById('facebook'),
  document.getElementById('note')
];
const random = document.getElementById('random'); // I'm Feeling Luckyボタン

// google検索
button[0].onclick = () => {
// 省略
}

// 省略

// note
button[15].onclick = () => {
// 省略
}

random.onclick = () => {
  const randomLink = button[Math.floor(Math.random() * button.length)];
  randomLink.onclick();
}
```

サイトボタンの変数を配列にまとめることで、配列の中身の数だけランダムに1つ取り出すことが簡単にできます。それぞれサイトボタンごとに関数でまとめているため、ランダムに選んだ数に`.onclick()`を付けて呼び出せば完成です。サイトボタンの数が増えても`button`の配列に追加するだけで対応できます。

### キーワードボックスの仕組み

Web Storage APIを使用して、ブラウザの`localStorage`に保存する仕組みになっています。主に[こちらの記事](https://www.granfairs.com/blog/staff/local-storage-01)を参考にしたため、詳しく知りたい方はこちらをご覧ください。

大雑把に言うと、ブラウザに保存されているリストをサイト読み込み時にキーワードボックスにセットするような仕組みになっています。そのため初めてサイトに訪れた際にlocalStorageに書き込む必要があります。まずは初回アクセス時のコードとキーワードボックスに出力するコードを解説します。

```javascript
let defaultList = [
  'Photoshop', '省略' , 'Linux'
];
// サイト読み込み時の設定
window.onload = () => {
  // localList = localStorageのキー
  if (localStorage.getItem('localList') === null) {
    let setJson = JSON.stringify(defaultList);
    localStorage.setItem('localList', setJson);
  }
  pulldownCreate();
  editList.value = JSON.parse(localStorage.getItem('localList')); // 後述するキーワードボックスの編集画面にlocalListを入力
}
// pulldownに中身を出力
function pulldownCreate() {
  let list = JSON.parse(localStorage.getItem('localList'));
  for (var i=0; i<list.length; i++) {
    let option = document.createElement('option');
    option.value = list[i];
    option.text = list[i];
    pulldown.appendChild(option);
  }
}
```

サイトを読み込んだときにlocalStorageに何も設定されていなかったらデフォルトのリストを設定して、キーワードボックスに中身を出力しています。配列は`localStorage`に保存できないためJSON形式に変換する必要があります。

次に検索設定のキーワードボックスの編集部分のコードを解説します。ダイアログになっているため開閉のコードも載せています。（開閉に使用しているクラス名closeにはdisplay:noneを、クラス名openにはdisplay:blockを設定してあります）

```javascript

const edit = document.getElementById('edit'); // 検索設定を開くボタン
const dialog = document.getElementById('dialog'); // 検索設定画面
const dialogBg = document.getElementById('dialog-bg'); // 検索設定画面の背景
const editList = document.getElementById('edit-list'); // リストの編集
const saveList = document.getElementById('save-list'); // ブラウザに保存ボタン
const closeDialog = document.getElementById('close-dialog'); // キャンセルボタン
const ressetList = document.getElementById('resset-list'); // ブラウザから削除ボタン

// 検索設定を開く
edit.addEventListener('click', () => {
  dialog.classList.remove('close');
  dialog.classList.add('open');
})
// キャンセルボタンで検索設定を閉じる
closeDialog.addEventListener('click', () => {
  dialog.classList.remove('open');
  dialog.classList.add('close');
})
// 背景クリックで検索設定を閉じる
dialogBg.addEventListener('click', () => {
  dialog.classList.remove('open');
  dialog.classList.add('close');
})
// 編集したリストをlocalListに保存
saveList.addEventListener('click', () => {
  const listItem = editList.value.split(','); // 編集内容をカンマ区切りで配列に変換
  let setJson = JSON.stringify(listItem); // JSONに変換
  localStorage.setItem('localList', setJson); // ブラウザのlocalStorageに保存
  location.reload(); // 設定内容を更新するため再読み込み
})
// 検索設定をブラウザから削除
ressetList.addEventListener('click', () => {
  if (confirm('キーワードボックスをブラウザから削除します。再読み込みするとデフォルトの設定が適用されます。よろしいですか？')) {
    localStorage.removeItem('localList'); // localStorageからリストを削除
    editList.value = ''; // 編集フォームから削除
    while (pulldown.firstChild) {
      pulldown.removeChild(pulldown.firstChild); // キーワードボックスから削除
    };
    dialog.classList.remove('open');
    dialog.classList.add('close');
    alert('ご利用ありがとうございました！');
  }
})

```

注意点として、編集内容を**localStorageに保存する前**に配列に変換しないと、再読み込みした時になぜか1文字ずつキーワードボックスに出力されてしまうため注意してください。私はここでハマってしまいました。。

それ以外はコメントアウトのとおりです。

### 音声入力機能

今までのsearchサイトでは**Web Speech API**を使用した音声入力機能を実装していましたが、Ultimate searchでは削除しました。理由はChrome以外で実装される気配がないことと、そもそも音声入力するときはOS標準の機能を使うだろうということと、検索ボックスの`input[type="search"]`要素に勝手についてくるクリアボタンがFirefoxでは表示されなかったため常時表示させたかったからです。

音声入力機能自体は[こちらの記事](https://qiita.com/hmmrjn/items/4b77a86030ed0071f548)を参考に作りました。めちゃくちゃ参考になりました。

## CSSのポイント

全部は紹介しきれないため一部こだわったポイントをまとめます。

### キーワードボックス

`select`タグのスタイルは無効にしています。選択内容を表示する必要がないことと「キーワードボックス」という文字を中央寄せにしたかったためです。ただ`display:none`などで消してしまうと機能も消えてしまうためボタンの大きさに合わせて透明に表示してます。

```css

/* キーワードボックスのエリア */
.select-box {
  display: flex;
  justify-content: center;
  margin: 1rem auto;
}
/* キーワードボックスのスタイル */
.select-box .pulldown-button {
  position: relative;
  border: solid 1px #dfe1e5;
  background-color: #fff;
  cursor: pointer;
  padding: 0.8rem 1rem;
  border-radius: 4px;
  transition: .3s;
}
/* pタグのmarginの除去 */
.select-box .pulldown-button p {margin: 0;}
/* セレクトボックスのスタイル */
.select-box .pulldown-button #pulldown {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: none;
  cursor: pointer;
  background-color: transparent;
}

```

### I'm Feeling Luckyのアニメーション

I'm Feeling Luckyボタンをホバーするとアニメーションします。グラデーションの表示を拡大して背景の表示する位置を左右に移動させることで実装しています。グラデーションの色はサイトボタンをホバーしたときの色を使用しています。（ロゴのグラデーションも同様です）

アニメーション部分については[こちら](https://gokansoichiro.com/blog/css-gradation-animation/)のサイトを参考にしました。

```css
#random:hover{
  color: #fff;
  background: linear-gradient(45deg, #bf0000, #ff0000, #ff0033, #e37151, #ff9900, #FBBC05, #1da1f2, #41C9B4, #34A853, #008274, #3B5998, #4285F4);
  background-size: 1200% 1200%;
  animation: gradation 10s ease infinite;
}
   
@keyframes gradation {
  0% {background-position: 0% 50%;}
  50% {background-position: 100% 50%;}
  100% {background-position: 0% 50%;}
}
```

### ダークモード

ブラウザやOSの設定に合わせてダークモードで表示されます。CSSだけで実装していて、簡単に背景色や文字色を変えることはもちろんロゴ画像に`filter: brightness(0) invert(1);`を適用することでカラフルなロゴをダークモードに合わせて白抜き文字にすることができます。（参考元は[こちら](https://chuckwebtips.hatenablog.com/entry/2017/05/09/174410)）

```css
@media (prefers-color-scheme: dark) {
  body {background-color: #1F1F1F;}
  img {filter: brightness(0) invert(1);}
  .search-area #window {border-color: #333;}
  .select-box .pulldown-button, .button-area .icon, .button-area button {
    color: #fff;
    background-color: #444;
  }
  .select-box .pulldown-button:hover, .select-box .pulldown-button:focus, .button-area button:hover, .button-area #hashtag:hover {
    color: #5F6368;
    background-color: #F2F2F2;
  }
  footer {
    background-color: #282828;
    outline-color: #333;
  }
  footer p, footer p a {color: #fff;}
}
```

## まとめ

[Ultimate searchはオープンソースで公開している](https://github.com/tanukizzan/ultimate-search)ので、これをベースに自分でよく使うサイトを追加するなどカスタマイズしても面白いと思います！

その他に今まで作ったサイトは[こちら](https://app.tanukizzan.com/)で公開しています。興味があればこちらもご覧ください。

最後まで閲覧ありがとうございました。