🐦

React × TypeScriptでポートフォリオを作った。

2024/07/22に公開

はじめに

2023年10月ごろから本格的にプログラマー転職を考え、ドットインストールにてHTML、CSS、JavaScriptを学んだのち、YoutubeやZennの記事、そしてドキュメントを通してReactを勉強しました。
何かポートフォリオを作成したく、好きな写真家さんである石田真澄さんのポートフォリオサイトを模倣したサイトを作成しようと考え、作り終えたはいいものの何かレガシーとして文章化した方が、就活や今後のために良いだろうと思い、ブログ化することにしました。文章を書くのはとても好きな作業なので、今まではObsidianで疑問点の解決方法などをアウトプットしていましたが、ローカルではなく、きちんと見られる環境に置くことでより客観性がまし、またプログラミング学習がより楽しくなるような気がしています。

石田真澄さんはポカリスウェットのCMムービー作成など数々の広告ビジュアルを作り上げてこられた写真家さんです。既存のサイトを模倣した理由については、ある一定の制約がある方が他のやり方(デザイン)で誤魔化したりすることができず、業務で求められることに近いと感じたからです。プログラミングの記事を書くこと自体初めてで、コーディングも探り探りで、間違った部分やもっといい方法が多分にあると思いますが、どうか温かい目で見ていただくと幸いです。

模倣させてもらったポートフォリオサイト↓
http://masumiishida.com/

主な使用技術と概要

技術 使用バージョン 説明
React 18.2.0 javaScriptのフレームワーク
react-intersection-observer 9.10.2 特定の要素がビューポート(表示領域)内に入ったり出たりしたときを監視するためのAPI
react-router-dom 6.22.2 ページ移動のために使う
TypeScript 5.2.2 あらかじめ変数の型を定義する静的型付けすることでJSの脆弱性を補う
Material UI 11.11.4 Google産の簡単にスタイリッシュにCSSが当てられるReactコンポーネントライブラリ
microcms-js-sdk 2.7.0 日本産のヘッドレスCMS
vite 5.1.4 高速で動作するビルドツール
day.js 1.11.10 現在の日付を取得するために使用
html-react-parser 5.1.8 JSONデータを文字列に変換する
vercel - webサイトの高速かつシンプルなホスティングサービス

作成したサイト

https://ishida-masumi-clone-app-p2up.vercel.app/

https://github.com/interprecord/ishida-masumi-clone-app.git

※いくつかの機能、ページが未実装です。

開発で意識したこと

クライアント操作と表示

今回はクライアントの載せたい写真や情報に応じてサイトの中身が変化するように作りたかったので、microCMSを使用しました。難しかったのはmicroCMSのスキーマやフィールドID、表示名をどう設定するかです。
CMSを使用するからにはクライアントができるだけ出来上がった見た目を意識できるようにそれらを設定する必要があったのですが、意識したもののなかなかうまくできませんでした。カスタムフィールドを使いながらやりましたが、これで正しい使い方なのだろうか?の連続でした。
また、microCMSはリッチエディタ等で豊富にカスタマイズが可能なことが特徴ですが、どこでリッチエディタを使用するか等の選択が難しかったです。

レスポンシブデザインとUI

今回の模倣サイトではMUIを使用しており、その特徴の一つである画面表示の大きさの違いによるデザイン調整のやりやすさを活かしました。

 <Grid
              key={index}
              item
              xs={6}
              sm={6}
              md={4}
              lg={2.29}
              sx={{
                position: "relative",
                paddingRight: "0",
                overflow: "hidden",
              }}
            >

gapやspacingがうまく当たらなかったりして、MUIは便利な一方でその中身の構造がブラックボックスであるため、既存のCSSとどう影響し合っているのかがわからないのが難点だと感じました。

主なページの機能及び苦労した点

Timeページ

時間によって背景の色を変化させる

いわゆるダークモードの実装です。
useStateでthemeの真偽値を管理し、それの真偽値に応じてuseRefで参照したdiv要素の背景色を変えます。

Time.tsx
  //時間によって背景色を変える
  useEffect(() => {
    if (divRef.current) {
      divRef.current.style.backgroundColor = theme ? "#FFFFFF" : "#000000";
    }
  }, [theme]);

themeの状態管理はApp.tsxで行い、それをpropsでTimeページを記述したTime.tsxと、写真をおしたらその写真が拡大表示されるモーダルを実装するためのTimeMordal.tsxに渡しました。

実際のthemeの真偽値を時間によって変化しないといけないのですが、これはTime.tsxで行っています。
なぜApp.tsxではなく、Time.tsxでやっているかというと、一つは後述するTime.tsxのintersectionObserverで監視する要素に応じてthemeの真偽値を変えなくてはならないこと、もう一つはHeaderの背景色が変わるのがTime.tsxだけだからです。今回はHeaderコンポーネントをApp.tsxで書いてまとめて表示させるのではなく、各モジュールにHeader.tsxを置いて背景色をTime.tsxだけ変わるようにしてあります。

Time.tsx
 const [localTheme, setLocalTheme] = useState(theme);

  ///背景の変更 監視する領域の写真が朝5時以降、18時までならtrueになるisWithRangeを設定。
          const updateTime = Number(time.slice(2, 5));
          const isWithinRange: boolean = updateTime >= 5 && updateTime < 18;

上のコードでTime.tsxのthemeを変えて、以下でTimeページのHeaderを時間によって変えています。

Time.tsx
  //isWithinRangeによってHeaderのthemeを変える。
          const checkTime = () => {
            setLocalTheme(isWithinRange); //朝昼ならtrue
          };
          checkTime();

ここはReactの特性をあまりいかせておらず、もっといい書き方がありそうです。

画像やサイドバーを現在の時間に近い順に並べる

AMが深夜0時から朝の10時、PMが朝の11時から夜の11時までという、単純な数字の並びではないものを「現在の時間に近い順に並べる、現在の時間が過ぎたら配列の一番後ろに移動させる」という実装に悩みました。

これは現在の時間との差分をgapTimeとしてだし、

Time.tsx
 gapTime: Number(time) - nowHnM,

これがプラスのものとマイナスのものをfilter関数で分別し、それぞれをsort関数を使って昇順に並べ替え、後でそれらを合体させる方法で解決しました。

Timeページの背景色とサイドバーのCSSを現在のviewportの写真(写真の時間)に応じて変化させる

背景色の変化の実装については軽く上で触れましたが、特にサイドバーの変化がこのポートフォリオサイトを作るにあたっての一番の鬼門でした。

元サイトのスクロールによる変化↓
Image from Gyazo

実装するにあたり単純なjavaScriptの機能でできないかなど色々と調べましたが、結局intersectionObserberを使って監視する領域を決め、それに応じて背景とサイドバーの時間の左右の文字の表示・非表示を切り替えるようにしました。

intersectionObserberについては長くなるので別記事にてまとめようと思います。
流れとしては、

監視する要素と、変更したい要素を取得、コンストラクタを作成、コールバック関数で監視する要素(entries)が入ったかどうか、そしてそれが起きたらどうするかの設定を書いておく。

というような感じですが、

Time.tsx
   const options = {
      root: null,
      rootMargin: "-20% 0px -80% 0px",
      threshold: [
        0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6,
        0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1,
      ],
    };

このoptionにてrootMarginで監視する領域を決め、thresholdでターゲットがどのくらいの割合で見えている場合にオブザーバーのコールバックを実行するかを示しますが、これをいくら調整してもサイドバーの表示がスムーズにできませんでした。
また、サイドバーの左右の文字はopacityを0→1に変化させることで実現しようと考えたのですが、
「監視する領域に入った写真の時間とサイドバーの時間」が合致した時にサイドバーの左右のopacityを変えるという実装が大変でした。

Exhibitionsページ

写真をクリックしたらその写真に応じて、ページ遷移させる

これがmicroCMSを使用した実装の難しい点で、あらかじめ写真を決めておいてそれにurlを埋め込むのなら簡単なのですが、クライアントがCMSで設定した写真に応じてページ遷移させないといけません。

やり方としては、onClickにindexを仕込んで、navigate(/exhibition/${index})でページ移動。App.tsxでreact-router-domを使い、exhibition.tsxに遷移するようにします。exhibition.tsxではuseParamsを使ってidを抜き出して、それぞれのidのページに遷移するようにしました。
Exhibition.tsxが押された写真のindex番号に応じて表示が変わる、言わばhubコンポーネントとして作用していて、なかなかユニークな手法だと思いました。以下はFigmaを使って図示してみたものです。

[参考記事]https://www.commte.co.jp/learn-nextjs/useParams

反省点と今後

  • CSSの設定
    MUIを使ってできないこと(afterやhover)を知らなくて、CSSをSASS、MUI、レガシーCSSと三つ使ってかなり読みにくいCSS管理になってしまいました。
  • レスポンシブ対応を想定した丁寧な設計
    最初レスポンシブ対応を想定せず作ったので、divのまとまりなどをもっと見やすくしたいと思います。
  • Next.js
    ルーティングの不便さからnext.jsを作った実装をしてみたいです。
  • CSSの理解
    最初、hoverなどのcss機能を使わずにその要素に触れたらtrue、falseが切り替わるしくみにしていたので、falseが切り替わらずに最初にhoverしたものがずっと拡大されたままみたいな状態が発生していて、それに気づいて作り直したので、余分な時間がかかりました。
    →次はもっとどういう設計にするかを入念に練ってから実装したいです。
  • typeScript
    typeScriptに関してはこの記事で触れていませんが、参考書を読みながら少しずつ試せることを増やしながら学習しました。基礎的なことはわかってきましたが、ジェネリック型など少し上級者向けの記述なども今後学んで使っていきたいと考えています。

この記事にまとめられないもっと数多くの悩んだ点や工夫した点があります。そしてもはや当たり前のこととして脳の中で自然と処理できるようになったため、わざわざ書く必要がなくなったものもあります。
現在学んでいることも未来から見ると当たり前のこととして処理されているのでしょう。

この記事を書いてみてコードを文章化するのは書き手にとってはいい練習になりますが、読み手にとってはイメージが難しく、コードを目にした方が処理が楽なんだろうなと感じました。また、書き手にとっては当たり前として処理されている部分が多くて、かといって全部を文章化するのは難しいです。
よく目にする、エンジニア方の記事を最初の方は、なぜ分かりやすく言語化しないでコードでほとんど説明しているのだろうと疑問に思いましたが、時間の制約とコーディングに慣れ、読み手のことを考えるとそういう書き方がベストプラクティスになるのかもしれません。拙い文章でしたが、ここまで読んでいただき、ありがとうございました。

Discussion