🚨

【初心者】Next.jsでホームページをつくろう⑦〜アイコンやボタンの実装〜

に公開

はじめに

こんにちは!「選挙×テクノロジー」で民主主義をアップデートすべく、個人開発をしながら勉強中です!今回はNext.jsでホームページを作成していくシリーズの第7回です。
前回はshadcn/uiでダークモードを実装しました。今回はアイコンやボタンを設置していきましょう。

前回の記事はコチラ
https://zenn.dev/yoshiki_a/articles/ca6689ce381412

対象読者

  • 初学者として同じ目線で学びたい方
  • 初学者の私にアドバイスしてくださる聖人

もくじ

  1. 顔アイコンとSNSアイコン(入れ子)
  2. 名前と概要テキスト
  3. SNSボタン

1.顔アイコンとSNSアイコン(入れ子)

まずはサイトの顔となるプロフィールアイコンとSNSアイコンです。
実装後は以下のような感じです。スマホとPCで以下のようにレスポンシブとなります。
スクショ
スクショ

「2つのブロック」で捉える

このプロフィール部分は、大きく分けると「A:顔写真」と「B:情報エリア」の2つしかありません。

【親:section】(全体を包む箱)

  ├─【子A:ProfileAvatar】(顔写真)

  └─【子B:div】(情報の袋)

       ├─ 名前とか自己紹介
       └─ SNSボタンたち

​ スマホとPCでの動き(図解)

スマホ↓

┌──────────────────┐
│ 【親:section】          │
│                  │
│  ┌────────────┐    │
│  │ A:顔写真           │     │
│  └────────────┘     │
│        ↓                    │
│  ┌────────────┐     │
│  │ B:情報            │    │
│  └────────────┘    │
│                              │
└──────────────────┘

PC↓

┌──────────────────┐
│ 【親:section】          │
│                  │
│  ┌─────┐ ┌─────┐  │
│  │A:顔写真 │  │B:情報│    │
│  └─────┘  └─────┘   │
│                              │
└──────────────────┘

【親】大きな枠(section)

<section className="flex flex-col items-start gap-8 md:flex-row md:items-center md:gap-10">
...
</section>

Tailwind CSSは「モバイルファースト」といって、何もついていないクラスはスマホに適用され、md: がついているクラスは画面が広がった時(タブレット・PC)に上書きされます。
​分解して見てみましょう。

​1. 共通のスイッチ
​flex:
「Flexboxを使うよ!」という宣言です。これを書かないと、並び替えの命令が効きません。

​2. スマホの時の命令(基本)
​まずはスマホで見た時のスタイルです。
​flex-col (縦並び):
要素を上から下へ、縦積みにします。「顔写真」の下に「名前」が来ます。

​items-start (左寄せ):
要素を左端に揃えます。

​gap-8 (隙間):
写真と名前の間に、少し狭め(32px)の隙間を空けます。

3.PCの時の命令(md:がついているやつ)
画面幅が 768px 以上(iPadやPCなど)になると、md: がついたクラスが発動して、スマホの命令を上書きします。
​md:flex-row (横並び):
ここが一番の変化です!縦積みだった要素を、横一列に並べ替えます。

​md:items-center (上下中央揃え):
横並びになった時、顔写真と名前の「高さの中心」を揃えます。これがないと、名前が上に寄ってしまって格好悪くなります。

​md:gap-10 (広い隙間):
画面が広いので、隙間も少し広め(40px)に調整して、ゆったり見せます。

<ProfileAvatar
  src="/icon-yoshiki.jpg"
  alt="渥美 嘉樹のプロフィール画像"
  initial="YA"
  className="self-start"
  imageClassName="animate-spin [animation-duration:12s]"
/>

【A子】顔写真

プロフィール画像です。今回は shadcn/ui の Avatar コンポーネントをベースに作成した、自作の <ProfileAvatar> コンポーネントを使っています。

<ProfileAvatar:
これはHTMLの標準タグではなく、自分で作ったカスタムコンポーネントの呼び出しです。
頭文字が大文字になっているからわかりますね。
通常、shadcn/uiでアイコンを表示するには <Avatar><AvatarImage /><AvatarFallback /></Avatar> と3つもタグを書く必要がありますが、それをひとまとめにして、「画像のパスと名前だけ渡せばいい便利セット」にしてあるわけです。

​src="/icon-yoshiki.jpg":
​画像の「場所」を指定しています。
Next.jsのルールでは、/ から始まるパスは public フォルダの中身 を指します。つまり、プロジェクトの public フォルダ直下に icon-yoshiki.jpg というファイルを置いているということです。

alt="渥美 嘉樹のプロフィール画像":
​これは 「アクセシビリティ(誰にでも使いやすく)」 のための記述です。
もし画像が読み込めなかった時や、目の不自由な方が使うスクリーンリーダー(読み上げソフト)がここを通った時に、「ここは渥美嘉樹の画像ですよ」と言葉で伝えてくれます。

initial="YA":
画像の読み込みに失敗した時や、画像が表示されるまでの「一瞬の間」に表示される文字です。
「Yoshiki Atsumi」のイニシャルを取って「YA」としています。これがあることで、画像が出なくても誰のアイコンかなんとなく伝わります。

className="self-start":
​これは レイアウト崩れを防ぐための重要なTailwind CSS です。
​self-start: 「自分自身を上端に揃える」という意味です。
もしこれを書かないと、隣の自己紹介文が長くなった時に、アイコンが変に縦に伸びたり、真ん中に浮いたりしてしまいます。どんなに文章が長くなっても、アイコンは常に「左上」にピシッと固定されます。

imageClassName="animate-spin [animation-duration:12s]":
​ここが今回の 「遊び心」 ポイントです!
​imageClassName: 外側の枠ではなく、中の「画像そのもの」にスタイルを適用するための設定です。※このimageClassNameは<ProfileAvatar> コンポーネントで定義しています。

​animate-spin: くるくると回転させるアニメーションです。

​[animation-duration:12s]: 通常の回転だと速すぎて目が回ってしまうので、「1周するのに12秒かける」 という指定をして、ゆっくり優雅に回るようにカスタマイズしています。

2.名前と概要テキスト

次に、以下のテキストとSNSアイコンの部分を実装します。
テキストとSNSアイコン

 <div className="flex flex-1 flex-col items-start gap-5 text-left">

まずはテキストとSNSアイコンが入る箱を作ります。

flex-1: 親要素(section)の中で、余ったスペースをいっぱいに使うように指示しています。これにより、顔写真の横のスペースを綺麗に埋めることができます。

flex-col: 中の要素(テキスト群とSNSアイコン群)を縦並びにしています。

​gap-5: テキスト部分とSNSアイコンの間に、均等な余白(20px)を作ってくれます。

            <div className="space-y-2">
              <h1 className="text-2xl font-semibold text-foreground md:text-3xl">
                渥美 嘉樹(あつみ よしき)
              </h1>
              <p className="text-sm text-muted-foreground md:text-base">
                政治×テクノロジーの仕事をしてます。現在、Next.jsの勉強中。
              </p>
            </div>

次ちテキストを実装します。

  1. 魔法の余白調整 space-y-2
    ​一番外側の <div className="space-y-2"> に注目してください。
    通常、見出し(名前)と段落(自己紹介)の間に隙間を作りたいとき、「見出しの下に margin-bottom をつける」といった指定をしがちです。
    ​しかし Tailwind CSSの space-y-2 を親要素に指定すると、「中に入っている要素同士の縦の隙間を、自動で均等(今回は8px)に空けてくれる」 という魔法のような働きをします。要素が増えても勝手に間隔を調整してくれるので、コードがとてもスッキリします。

​2. ダークモード対応の文字色(shadcn/uiの恩恵)
​文字色には、特定のカラーコード(黒やグレーなど)ではなく、shadcn/ui特有のクラスを指定しています。
​text-foreground(名前部分):
メインの文字色です。ライトモードでは「ほぼ黒」、ダークモードでは「ほぼ白」に自動で切り替わります。
​text-muted-foreground(自己紹介部分):
少し控えめな(Mutedな)文字色です。自己紹介文をグレーがかった色にすることで、名前(メイン)との視覚的な優先順位(メリハリ)をつけています。もちろんこれもダークモードに対応しています。

​3. スマホとPCで文字サイズを変える(レスポンシブ対応)
​名前の h1 タグには text-2xl md:text-3xl、自己紹介の p タグには text-sm md:text-base と指定しています。
​名前:基本は 2xl(少し大きめ)、PC画面(md:)では 3xl(さらに大きく)。
​自己紹介:基本は sm(小さめ)、PC画面(md:)では base(標準サイズ)。
​画面サイズに合わせて文字の大きさが変わるため、スマホで見ても文字が大きすぎて圧迫感が出たり、逆にPCで見たときに小さすぎて貧相になったりするのを防いでいます。font-semibold は文字を少し太字(半太字)にして、名前をくっきり見せるためのクラスです。

なお、以下からフォントサイズを確認できます。
https://tailwindcss.com/docs/font-size

3.SNSボタン

            <div className="flex flex-wrap gap-3">
              <Button asChild size="icon" variant="ghost">
                <Link
                  href="https://github.com/yoshiki-atsumi"
                  target="_blank"
                  rel="noopener noreferrer"
                  aria-label="GitHub"
                >
                  <FaGithub className="size-5" />
                </Link>
              </Button>
              <Button asChild size="icon" variant="ghost">
                <Link
                  href="https://x.com/atsumi_yoshiki"
                  target="_blank"
                  rel="noopener noreferrer"
                  aria-label="X (旧Twitter)"
                >
                  <FaXTwitter className="size-5" />
                </Link>
              </Button>
              <Button asChild size="icon" variant="ghost">
                <Link
                  href="https://www.facebook.com/atsumi.yoshiki"
                  target="_blank"
                  rel="noopener noreferrer"
                  aria-label="Facebook"
                >
                  <FaFacebook className="size-5" />
                </Link>
              </Button>
            </div>

​3つのSNSアイコン(GitHub、X、Facebook)が並んでいる部分です。ただアイコンを置くだけでなく、押しやすさや安全性に配慮した設計になっています。

​1. スマホでもはみ出さない flex-wrap
​アイコンを囲む親要素には <div className="flex flex-wrap gap-3"> を指定しています。
​flex-wrap: もし画面が極端に狭くなって横幅が足りなくなった時、アイコンが画面外にはみ出すのを防ぎ、自動で「下の行に折り返して」くれます。
​gap-3: アイコン同士の隙間を均等に(12px)空けています。

​2. shadcn/ui の魔法 asChild(超重要!)
​ここが今回のハイライトです!アイコンをリンクにする際、shadcn/uiの <Button> コンポーネントを使っています。

<Button asChild size="icon" variant="ghost">
  <Link href="...">

HTMLのルールとして、「<button> タグの中に <a> タグ(リンク)を入れてはいけない」 という決まりがあります。
しかし、shadcn/ui の Button に asChild を付けると、「ボタンの見た目(ホバー時のエフェクトなど)だけを、中身の <Link> にそのまま合体させる」 ことができます!

​variant="ghost": 背景が透明で、マウスを乗せるとフワッと背景色が変わる「ゴースト」スタイル。
​size="icon": ボタンを綺麗な正方形(アイコン用)に整えてくれます。
​これにより、ルール違反をせずに、綺麗で押しやすいリンクボタンが完成します。

<Button asChild size="icon" variant="ghost">
<Link href="https://github.com/yoshiki-atsumi" target="_blank" rel="noopener noreferrer" aria-label="GitHub">
<FaGithub className="size-5" />
</Link>
</Button>

​target="_blank"
リンクをクリックしたときに、今のページを移動せずに「新しいタブ」で開くための指定です。外部サイトへ誘導するときによく使います。

​rel="noopener noreferrer"
target="_blank" を使うときの必須の相棒です。これをつけないと、移動先のサイトから元のサイト(自分のサイト)のURLを勝手に書き換えられてしまう「タブナビング(Tabnabbing)」というセキュリティ上の弱点が生まれてしまいます。悪意のあるサイトから身を守るための大切なお守りです。

​aria-label="GitHub"
画面が見えない方などが使う「音声読み上げソフト」のための設定です。アイコン画像だけだと、ソフトは「何のボタンか」を読み上げられません。ここで「これはGitHubへのリンクですよ」と裏側で教えてあげることで、誰にでも優しいサイトになります(アクセシビリティの向上)。

​​<FaGithub />
​<FaGithub /> は、HTMLの <img> タグで画像ファイル(.pngなど)を読み込んでいるわけではありません。Next.js(React)で定番の react-icons というライブラリ(アイコンの詰め合わせパック)から呼び出したコンポーネントです。
​Fa は「Font Awesome(フォントオーサム)」という世界中で使われている有名なアイコンブランドの略です。
​画像ではなく「ベクターデータ(SVG)」として描画されるため、どれだけ拡大しても画質が荒くならず、文字色と同じようにCSSで簡単に色を変えることができます。

className="size-5"
アイコン(<FaGithub>)の大きさを指定しています。Tailwind CSSの size-5 は、「縦幅(height)と横幅(width)を同時に20pxにする」という便利な省略の書き方です。

Discussion