「Gatsby x TypeScript」でブログを作りました (2. レスポンシブルデザインのコツ)

Gatsbyでブログを作った人の最初の記事と言えば、だいたい「Gatsbyでブログ作りましたー」でしょう。なので許してください。

「Gatsby x TypeScript」でブログを作る時に役に立った情報などを自分用のメモというか棚卸しというかまとめていきたいと思います。

このブログ一見シンプルに見えて、ワードプレスで作られたブログと引けを取らないくらい実はいろんな機能が盛り込まれてます。

  • Disqusによるブログコメント投稿機能
  • Algoliaによる全文検索機能
  • Mailgunによる自動返信メール(お問い合わせ時)
  • Google reCAPTCHA v3 (サーバーサイドバリデーションもあり)
  • Google Analytics
  • Iframelyによる記事へのiframeの埋め込み機能
  • JSONLDを使った構造化マークアップ(SEO対策)

全4回に分けて、gatsbyのプラグインで一瞬で実装が終わったものもあればそうでないものもあるので実装する上で参考になったサイトと要点を軽くまとめなながら紹介できたらいいかと思います。

「Gatsbyでのブログ作成手順」は他のブログにあるので手順に関しては紹介しません。

この投稿では 2.レスポンシブルデザインのコツ について書こうと思います。

Responsively app

レスポンシブル対応の動作確認をするのって大変ですよね。でもその問題を鮮やかな方法で解決しているツールがあります。 最近、GitHubで人気急上昇になっている色んな端末で一気に動作確認できるツールmanojVivek/responsively-appがすごく便利です。

responsively app

良さそうでしょう。 たまにスクロールが連動してくれなくなる不具合とかがありますが、なかなか使えそうなプロダクトです。 一気に確認する時には役にたつかと思います。

インストールは簡単なので使ってみたらいいと思います。

brew cask install responsively

ですが連続した画面幅の変更によるデザインの確認に関しては各ブラウザの検証ツール(DevTools)を使ったがいいでしょう。

htmlタグのfont-size

各端末でfont-sizeを整えていくのは非常に辛い作業の一つだと思います。 フォントサイズの調整のやり方は px・em・rem と色々ありますが rem が使ったがいいと思います。 最終的には emrempx に変換されるから px でいいじゃないかと思われるかもしれないですが px だと以下の問題があるからよくないです。

ただし、こうした絶対値による文字サイズ指定はブラウザによってはユーザー操作による文字サイズの拡大/縮小に対応できなかったり、タブレットやスマートフォンなど、高解像度のディスプレイを備える端末で表示をした場合に、端末ごとに見た目の文字サイズが大きく異なって表示されたりする場合があります。

パソコン工房さんの記事がすごくわかりやすかったです。

remを使ってCSSの文字サイズ指定|パソコン工房

rem は親要素の影響を全く受けないので htmlタグ に指定されたフォントサイズを基準として何倍かを指定していくやり方 です。

このパソコン工房さんの図をみたら一発で腑に落ちました。

pc-koubou-em-rem.png 出典: remを使ってCSSの文字サイズ指定|パソコン工房

rem を使っていく上でぜひ設定して置きたい設定は、htmlタグの font-size10px になるように以下を指定しておく事です。主要なブラウザのデフォルト(初期設定)の文字サイズは16pxとなっているため、それを10pxにするためには 10/16で62.5%にすればいいという事です。

html {
  // htmlタグのfont-sizeを10pxに設定する
  font-size: 62.5%
}

コンポーネントのfont-size

コンポーネントのfont-sizeですが、レンダリングするものにクラス(css)の指定ができるかできないかで 少しだけ設定の仕方が変わってくると思います。

クラス指定ができる場合

コンポーネントでfont-sizeを設定していく時は、% を使いましょう。 絶対値で指定してしまうと、コンポーネントの再利用性を損なってしまいます。

あと、文字がつまりすぎてるようだったら、line-height も一緒に % で指定したがいいかもしれません。

例えば、このPostLineコンポーネントの場合で説明すれば、

post-line-ipad.png

このようにfont-sizeに % を指定してあります。

コードで書けばこんな感じです。

src/components/molecles/PostLine/index.tsx
import React from 'react'
import ClassNames from 'classnames'
import styles from './PostLine.module.scss'

type Props = {
  className?: string
}

const PostLine: React.FC<Props> = ({ className }) => {
  const rootClassName = ClassNames({
    [styles.root]: true,
    [className]
  })

  return (
    <div className={rootClassName}>
      <div className={styles.title} />
      <div className={styles.info} />
      <div className={styles.excerpt} />
    </div>
  )
}

export default PostLine
src/components/molecles/PostLine/PostLine.module.scss
.root { font-size: inherit; }
.title { 
  font-size: 150%;
  line-height: 200%; // お好みで%で指定します。
}
.info { font-size: 80%; }
.excerpt { font-size: 100%; }

それで実際のfont-sizeは className というpropsを通して設定します。

<PostLine className={styles.post_line} />
.post_line {
  font-size: 2rem;
}

これで利用する側から自由にfont-sizeを指定できるようになります。

クラス指定ができない場合

クラス指定ができない場合、どういう場合があるかというと、markdownなファイルをパースしてhtmlに変換したものを

import React from 'react'
import ClassNames from 'classnames'
import styles from './BlogPost.module.scss'

type Props = {
  className?: string
}

const BlogPost: React.FC<Props> = ({className}) => {
  const rootClassName = ClassNames({
    [styles.root]: true,
    [className!]: className
  })

  return (
    <div className={rootClassName}>
      // markdownの内容がrenderingされる
      <div dangerouslySetInnerHTML={{ __html: node.html }} />
    </div>
  )
}

export default BlogPost

HTMLタグに直接クラスを付与していく必要があります。 うまく適用されない場合は、!important の指定がいるかもしれません。

<BlogPost className={styles.blog_post} />
.blog_post {
  h1 { font-size: 150% !important; }
}

まとめ

ではここまでの内容をまとめます。

  • コンポーネントのfont-sizeの指定
  • 方針としてfont-sizeに絶対値を使わない。再利用性が下がるので
  • 内部rootクラスに font-size: inherit を書く。

    • ■クラス指定ができる場合

      • その他のクラスには、font-size: **% で指定する。
      • 外部rootクラス(className)で font-size: 2rem とか指定する。
    • ■クラス指定ができない場合(dangerouslySetInnerHTMLで結果をレンダリングする場合)

      • 内部rootクラスの中でHTMLタグに直接 h2 { font-size: 150% !important; } などとしていく。
      • 外部rootクラス(className)で font-size: 2rem とか指定する。

画面幅に合わせて変動するfont-size

これはテクニックとして覚えておいたら便利だと思うので紹介しておきます。

例として、画面幅1280pxから画面幅300pxまででfont-sizeが40pxから10pxに変化する場合を考えてみます。

responsible-font-size.png

これをscssで使えるmixinとして作っておいたらいいと思います。

こちらの記事を参考にして以下のように作りました。内容としてはこれは中学生の問題ですね。「2点を結ぶ直線の方程式問題」です。

ですが、油断するとcalcの中で変数を展開する時にハマるという苦い思いをします。

// [ref]
// https://neos21.hatenablog.com/entry/2018/02/22/080000
@function strip-unit($num) {
  @if type-of($num) == 'number' and not unitless($num) {
    @return $num / ($num * 0 + 1);
  }
  
  @return $num;
}

@function gradient($fromFontSize, $toFontSize, $fromWidth, $toWidth){
  @return ((strip-unit($toFontSize) - strip-unit($fromFontSize))/((strip-unit($toWidth) - strip-unit($fromWidth))));
}

@function rem($k, $toWidth) {
  @return (1rem / $k) * strip-unit($toWidth);
}

// [ref]
// https://note.com/nomen_machine/n/n089fae61dd00
// $fromFontSize(rem)
// $toFontSize(rem)
// $fromWidth(px)
// $toWidth(px)
@mixin responsible-font-size($fromFontSize, $toFontSize, $fromWidth, $toWidth) {
  $k: 10; // 10px = 1rem
  $toWidthRem: rem($k, $toWidth);
  $gradient: gradient($fromFontSize, $toFontSize, $fromWidth, $toWidth);
  font-size: calc( #{$fromFontSize} + ((100vw - #{$toWidthRem} ) * #{$gradient} ));
}

使い方はこんな感じです。

.root {
  @include responsible-font-size(1.6rem, 0.8rem, 1280px, 375px)
  // もしくは
  @include responsible-font-size(1.6rem, 0.8rem, 1280, 375)
}

$fromFontSize<p><a><li> などのフォントサイズを100%と考えて設定したら
Typographyになる気がします。そして<h2> とかのフォントサイズを150%とかにすればいい感じになるでしょう。

ですが実際にこのresponsible-font-size mixinを使う時に渡す引数の値はよほど理由がないかぎり変わらないと思うので、具体的な値を入力した共通パーツを作っておくことをお勧めします。

@mixin common-responsible-font-size {
  @include responsible-font-size(1.6rem, 0.8rem, 1280px, 375px);
}

仮に<p>のLaptopでのサイズを1.6remから変えたくなったらここを変えれば済むので修正が楽になります。

これでコンポーネントでレスポンシブルなフォントサイズを扱えるようになり、レスポンシブルデザインに苦しまなくなるかと思います。

breakpointで見た目を変える

breakpointでデザインを変えたい場合ってあると思います。 例えば、コンポーネントを変えたい場合だとこのブログの投稿リストの要素コンポーネント(PostCard or PostLine)だったり、適用したいスタイルを変えたい場合だったら、このブログの前のページ・次のページ(PrevNext) だったりがあると思います。

それでは各例について具体的に説明していきます。

まず画面幅がbreakpointより大きいか小さいかでtrueかfalseを返すためのReact.hooksを作っておきます。

src/hooks/useHandleResponsible.tsx
import { useState, useEffect } from 'react'
import throttle from 'lodash.throttle'

const THROTTLE_DELAY_RESIZE = 100;

type Props = {
  breakPoint: number
}

const useHandleResponsible = ({ breakPoint }: Props) => {
  const [changeDesign, setChangeDesign] = useState(false)

  useEffect(
    () => {
      const handleResize = () => {
        setChangeDesign(window.innerWidth < breakPoint)
      }

      const throttleHandleResize = throttle(handleResize, THROTTLE_DELAY_RESIZE)

      if (typeof window !== 'undefined') {
        handleResize()
        window.addEventListener('resize', throttleHandleResize)
      }

      return () => {
        if (typeof window !== 'undefined') {
          window.removeEventListener('resize', throttleHandleResize)
        }
      }
    },
    []
  )

  return {
    changeDesign
  }
}

export default useHandleResponsible

使い方はこんな感じです。

// コンポーネントを変えたい場合
const { changeDesign: changeComponent } = useHandleResponsible({ breakPoint: 650 })
// 適用したいスタイルを変えたい場合
const { changeDesign: changeStyle } = useHandleResponsible({ breakPoint: 650 })

コンポーネントを変えたい場合

change-component.gif

まぁこっちも適用するスタイルを変える場合で対応できるのですが、最新記事一覧のパーツとしても使っていたので それを再利用しました。

latest-post-line-list

コードはこんな感じになります。

src/components/organisms/PostList.tsx
import React from 'react'
import { PostCard, PostLine } from '@/components/molecules'
// @はsrcへのパスを解決する@エイリアス
import { useHandleResponsible } from '@/hooks'

type Props = {}

const PostList: React.FC<Props> = (props) => {
  // 650px以下だったらchangeComponentがtrue
  const { changeDesign: changeComponent } = useHandleResponsible({ breakPoint: 650 })

  if(changeComponent){
    return <PostLine props />
  } else {
    return <PostCard props />
  }
}

export default PostList

直感的にわかるので説明はいらないかと思います。

適用したいスタイルを変えたい場合

change-design.gif

コードはこんな感じです。

src/components/molecules/PrevNext/index.tsx
import stlyes_lg from './PrevNextLarge.module.scss'
import styles_sm from './PrevNextSmall.module.scss'
import { useHandleResponsible } from 'src/components/hooks'

type Props = {
  className?: string
}

type MakeProps = Props & {
  styles: any
}

const MakePrevNext: React.FC<MakeProps> = ({ styles, className }) => {
  return (
    <div className={styles.root}>
      # something
    </div>
  )
}

const PrevNext = React.FC<Props> = ({ className }) => {
  // 画面幅が650px以下だとchangeDesignはtrue
  const { changeDesign: changeStyle } = useHandleResponsible({ breakPoint: 650 })

  if(changeStyle) {
    return <MakePrevNext {...{ styles: styles_sm, className }} />
  } else {
    return <MakePrevNext {...{ styles: styles_lg, className }} />
  }
}

export default PrevNext

こちらでの注意点は用意するスタイルのクラス名は完全に合わせる必要があるという点です。
それさえ守れば大丈夫です。

まとめ

レスポンシブルデザインをやっていく上で便利なテクニックを紹介しました。

  • 「responsibly app」で複数端末で一気に確認できて便利。
  • htmlタグのfont-sizeを62.5%(10px)にして、全体ではpxではなくremを使うと便利。
  • コンポーネントのfont-sizeはrootはfont-sizeはinherit、あとは<p><a><li>などのフォントサイズを100%として%を使って指定するのが便利。
  • 画面幅に合わせてfont-sizeを変化させる事ができるmixinの responsible-font-size を作って置くとと便利。
  • breakpointで見た目を変えるために useHandleResponsible を作っておくと便利。

    • コンポーネントを差し替えたい時に役に立つ。
    • コンポーネントに適用したいデザインを変えたい時に役に立つ。

以上です。次は、3.サードパーティーの使う上での豆知識 です。

yukihirop

この記事を書いた人

フロントエンド兼バックエンドなWEBエンジニアです。 既存の考え方に囚われずに自由にツールを作るのが好きです。

いつかBioに「Creator of ...」と書けるようなプログラマを目指している人のブログ

関連記事