懐かしい気持ちになれる Web サイトをつくった話

先日、こんな Web サイトをリリースしました。

私や私の同世代には2000年前後のインターネットで知識を得たりリテラシーを学んだりした人が多く、その懐かしさを何かしら形として残しておきたいなと思ったことがきっかけでした。

こういうサイト自体は他にもありそうですが、せっかくエンジニアとして仕事しているので

技術的に自分が興味を持った・面白そうと感じたものを使って作りたい

と思いました。
というわけでここからが本題です。

使ったもの・やったこと

  • Svelte
  • 懐かしい機能の再現
  • Firebase Hosting

開発用のエディタには Visual Studio Code を使いました。
リポジトリはこちらです。

Svelte

以前知った Svelte という JavaScript フレームワークを使っています。

昨今は React/Vue でコードを書くことが増えてきましたが、それと同じようにコンポーネントベースで書けるフレームワークです。
ただ「ファイルサイズが小さい」「仮想 DOM を使わない」「書くコードがかなりシンプル」など、色々面白い特徴を持っています(詳細は公式ドキュメントや比較記事があるのでそちらを御覧ください)。
Visual Studio Code にも拡張機能があります。

以下実装例です。

  • 親コンポーネント
<script>
  // 利用するコンポーネント
  import ChildComponent from './ChildComponent.svelte';

  // コンポーネント内で保持する(値が書き換わる)プロパティ
  let value = 1;

  // コンポーネント内で利用する関数
  const increment = () => value += 1;
</script>

<!-- React/Vue と異なり、テンプレートの親要素に複数の DOM 要素が書けます -->
<button class="button" on:click={increment}>Increment</button>
<!-- 子要素に increment という名で props を渡す -->
<ChildComponent value="{value}" />

<style>
  /* スタイルはデフォルトで scoped(コンポーネント内にのみ適用される)になります */

  .button {
    background-color: #987;
  }

  /* 未使用のスタイルはデフォルトで警告を出してくれます */
  .unused-style {
    display: none;
  }
</style>
  • 子コンポーネント
<script>
  // 親 props
  export let value;

  // props の値が変わった場合に再評価させたいので $ を使って書きます
  $: isOdd = value % 2 === 1;
  $: displayClass = isOdd ? 'odd' : 'even';
  $: displayValue = `${value}は${isOdd ? '偶数' : '奇数'}です`;
</script>

<div class={displayClass}>{displayValue}</div>

<style>
  .odd {
    color: blue;
  }

  .even {
    color: red;
  }
</style>

Event Forwarding や Store についても仕組みがあるので、気になった方は公式チュートリアルを確認してみてください。
React でいう Next.js、Vue でいう Nuxt.js に該当するフレームワークとして、Sapper というものもあります。

懐かしい機能の再現

最初は非推奨になったタグもガンガン使ってやろうかと思っていました(実際、上記サイトで <center> を使っているのはこの名残です)が、さすがにエディタがガッツリ警告を出してくるので別の方法で実装することにしました。

まずは現在非推奨になった marquee タグの代替実装です。
css でやるとなると少しコード量が増えるんですが、以下のようにアニメーションを使って実現しました。

.marquee {
  margin: 5px auto;
  overflow: hidden;
}

.marquee-inner {
  animation-name: marqueeAnimation;
  animation-timing-function: linear;
  animation-duration: 20s;
  animation-iteration-count: infinite;

  margin: 0;
  padding: 0;
  display: block;
  white-space: nowrap;
}

/*
 * 0% が開始位置、100% が終了位置
 * コンテンツの長さによって微調整が必要
 */
@keyframes marqueeAnimation {
  0% { transform: translate(65%); }
  100% { transform: translate(-65%); }
}

次にマウスの追尾です。

<script>
  let stalker;

  document.addEventListener("mousemove", (e) => {
    stalker.style.transform =
      "translate(" + e.clientX + "px, " + e.clientY + "px)";
  });
</script>

<div class="stalker" bind:this={stalker} />

<style>
  .stalker {
    pointer-events: none;
    position: fixed;
    top: -8px;
    left: -8px;
    width: 16px;
    height: 16px;
    background: rgba(128, 0, 128, 0.5);
    border-radius: 50%;
    transform: translate(0, 0);
    transition: transform 0.2s;
    transition-timing-function: ease-out;
    z-index: 999;
  }
</style>

React/Vue でいう ref に該当するものとして bind:this を使っており、mousemove イベントをハンドルすることでそこに設定したオブジェクトに対して位置情報を設定しています。

そして文字色反転です。

.hint-label {
  margin-top: 100px;
  color: transparent;
}

.hint-label::selection {
  background-color: #876;
  color: #ddd;
}

基本は文字色を透明にして、選択された場合に背景色と文字色を変更するようにしています。

他にも要素を並べるのに display: flex を使ってるとか、レスポンシブ対応してるのでスマホでも問題なく見えるとか細かいところも色々やっています。
アクセスカウンターとキリ番は現状ダミーなので、ここはいずれ機能を実装できればと思っています。

Firebase Hosting

2年ぐらい前につくった Waaru というサービスでも使っていましたので、今回も使ってみようと思いました。
ただ、以前に比べて CLI がかなりバージョンアップしていた印象です。

基本的な手順は以下です。

$ # Firebase CLI をインストール
$ npm install -g firebase-tools
$ # Google アカウントを利用してログイン
$ firebase login
$ # 初期設定
$ firebase init

前回と変わっていたのは初期設定のところで、必要であれば自動で GitHub Actions にデプロイ用の設定ファイルを用意してくれます。
デプロイのトリガーになるブランチやタイミング(PR 作成時にもデプロイするかなど)も CLI で設定できました。
このとき GitHub Actions の secrets にトークンを設定してくれるので、質問に答えていくだけで GitHub への Push 時にデプロイされる状態が出来上がりました。

最後に

というわけで、無事「古(いにしえ)」というこれまた微妙な名前のサイトができました。
ありがたいことに、友人たちにたくさんフィードバックをいただけたので、実装したい機能は山積みです。
掲示板とかチャットとか色々やってみたいことはあるので、今後も仕事の合間にメンテナンスしていきたいと思います。
ご覧になった方もぜひフィードバックいただけると嬉しいです。