こんにちは、『スタディサプリ』の iOS エンジニアのヴァンサンです。
iOS でユーザーが文字サイズを調整できる機能を Dynamic Type と言います。iOS アプリ開発では UIKit や SwiftUI での Dynamic Type 対応に関する情報が多く見つかりますが、この記事ではウェブページでの実装方法を説明します。また、Android のフォントサイズ設定についても触れながら、マルチプラットフォームでの文字サイズ調整対応について解説します。
コンテキスト
『スタディサプリ小学・中学講座』iOS アプリは主に SwiftUI で書かれていますが、動画視聴や問題回答をする学習画面がウェブ技術で実装され、アプリ内ブラウザー(ウェブビュー)に表示されます。この複雑な画面の実装を iOS、Android、ウェブで共通化するためです。
ウェブビューにはブラウザーのような UI がないため、iOS ユーザーにとっては、SwiftUI で作成された画面も、ウェブビュー内で表示される画面も、どちらも同じ iOS アプリの一部として認識されます。アプリ全体の Dynamic Type 対応を進めていたので、一貫性を保つためウェブビュー内も対応する必要がありました。そこでウェブアプリケーションエンジニアと協力して、ウェブ側の実装に取り組むことにしました。
各プラットフォームでの文字サイズ調整
Dynamic Type 対応が主な目標ですが、対応するウェブページは Android でもブラウザーでも表示されます。ユーザーの文字サイズ調整はどのプラットフォームでも対応できるのが望ましいので、各プラットフォームでの文字サイズ調整について見ていきましょう。
iOS
Dynamic Type とは
Dynamic Type は、iOS でユーザーが文字サイズを自分の好みや視力に合わせて調整できる機能です。機能自体は「Dynamic Type」と呼ばれていますが、UI 上で設定が「テキストサイズ」(英語でも「Text Size」)という名前で表示されています。
元から選択できるサイズが以下の7つです。
- extra small
- small
- medium
- large(標準設定)
- extra large
- extra extra large
- extra extra extra large
アクセシビリティ設定で「さらに大きな文字」を ON にすると、アクセシビリティサイズと呼ばれる以下の5つのさらに大きいサイズを選べるようになります。
- accessibility 1(旧 accessibility medium)
- accessibility 2(旧 accessibility large)
- accessibility 3(旧 accessibility extra large)
- accessibility 4(旧 accessibility extra extra large)
- accessibility 5(旧 accessibility extra extra extra large)
因みに『スタディサプリ小学・中学講座』iOS アプリで計測した結果、現在、ユーザーのおよそ4分の1がフォントサイズ設定を標準から変更していることがわかりました。
- 13% が標準より大きくしています。
- 11.5% が標準より小さくしています。
- アクセシビリティサイズを使っているユーザーが 0.5% 未満です。
別のアプリでは数値は変わると思いますが、この結果から、設定変更を行うユーザーは決して少なくないと言えるでしょう。
ユーザーがテキストサイズ設定を変えても、対応していないウェブページには反映されません。SwiftUI や UIKit で書かれた iOS アプリでは、フォントの使い方によって設定が反映されないことも珍しくありません。
テキストサイズ設定を反映させるには、Apple が提供するテキストスタイルのいずれかを使用する必要があります。これは Apple のテキスト用デザインシステムとも言えます。各スタイルには weight(太さ)、フォントサイズ、そして leading(行送り)が設定されており、テキストサイズ設定に応じて変化します。Apple のヒューマンインターフェイスガイドライン(以下 HIG)には各スタイルの詳細な仕様が記載されています(日本語版もありますが、ここでは英語のスタイル名を使います)。
各スタイルは設定によって異なる倍率で変化します。設定を大きくすると、元々小さいスタイルほど大きく変化し、元々大きいスタイルは比較的小さい倍率で変化します。例えば:
- body スタイルは、標準(large)設定の 17pt から accessibility 3 設定では 40pt になり、約 2.35 倍に拡大されます
- large title スタイルは、標準設定の 34pt から accessibility 3 設定では 52pt になり、約 1.53 倍に拡大されます
Dynamic Type 設定の変更のやり方
iOS 端末で直接テキストサイズ設定を変える方法が2つあります:
- 設定アプリで変更します。「画面表示と明るさ」からでも、「アクセシビリティ」>「視覚サポート」カテゴリの「画面表示とテキストサイズ」>「さらに大きな文字」からでも変更できます。
- コントロールセンターにテキストサイズウィジェットを使います(表示されていない場合コントロールセンターの設定から追加できます)。アプリから離れずに変更できるので便利です。アクセシビリティ設定に「さらに大きな文字」が ON の場合、テキストサイズウィジェットで選べるサイズが増えます。
iOS Simulator では残念ながらコントロールセンターにテキストサイズウィジェットを追加できませんが、Simulator のメニューから「Features」>「Increase Preferred Text Size」(command+option+plus)と「Features」>「Decrease Preferred Text Size」(command+option+minus)で設定を変更できます。便利ですが、デフォルトに戻すメニューがありません。一番小さい設定にしてから3回大きくする必要があります。
また、Accessibility Inspector や Xcode の Environment Overrides からも Dynamic Type 設定を変更できます。
Android
Android の設定には、文字サイズの設定が「フォントサイズ」という名前で表示されています。iOS 同様、Android 端末で直接フォントサイズ設定を変える方法が2つあります:
- 設定アプリで変更します。「ディスプレイ」>「表示サイズとテキスト」または「ユーザー補助」>「表示サイズとテキスト」から変更できます。「表示サイズとテキスト」には、「表示サイズ」という設定もありますが、「表示サイズ」は文字に限らず、すべてを大きくします。多くのブラウザーの拡大機能に近いようです。ここでは「フォントサイズ」のことだけを話します。
- クイック設定で変更します。アプリから離れずに変更できるので動作確認に便利です。
この「フォントサイズ」設定も、Dynamic Type 同様に文字サイズだけを変更するものです。
Android ではサイズの段階が端末によって異なりますが、iOS の accessibility 3 以上のような非常に大きいサイズは基本的にありません。一番大きいサイズは基本的に国際基準である WCAG に準拠して標準の2倍となっています。
Android のネイティブアプリでは、フォントサイズを sp(scale-independent pixels)単位で指定している場合、フォントサイズはユーザー設定に合わせて自動的に変更されます。
ウェブページは特別な対応がなくても、アプリ内のウェブビュー内でも、Chrome でも、一部の要素でフォントサイズと行送り(line-height)が「フォントサイズ」設定の影響を受けます。一応実装の詳細な仕様がまとめられていますが、複雑で分かりにくい状況です。
なお、Chrome の設定にある「ユーザー補助機能」>「テキストの拡大と縮小」は、Android のフォントサイズ設定と掛け合わされて適用されます。
私たちのウェブページでは、ほとんどの要素がこの設定の影響を受けるため、以降はその前提で説明を進めます。
文字サイズがユーザー設定によって変更された場合、レイアウトが崩れないように設計する必要があります。この詳細については後ほど説明します。
ブラウザー
多くのブラウザーには拡大/縮小機能があります。この機能では CSS の 1px の実際のサイズが設定によって変わり、全ての要素がその影響を受けます。文字サイズのみを変更する場合と比べて、レイアウトが崩れるリスクが少なくなります。Safari iOS で ᴀ/A として表示される機能も、文字だけでなく全てのサイズに影響を与えます。
ただし、スマートフォンの画面が狭いので全てを大きくすると一度に見える情報量が大きく減ってしまいます。一番大きくする価値があるのはテキストだと考えられます。
Safari Mac では、通常のブラウザー拡大の他に、文字だけの拡大/縮小もできます。「表示」メニューで option を押すと「文字を拡大」(command+option+plus)と「文字を縮小」(command+option+minus)が出てきます。「実際のサイズ」(command+0)で標準設定に戻ります。文字といっても、font-size
だけではなく、line-height
(行送り)も影響を受けます。文字サイズ変更に耐えられる柔軟なレイアウトの開発にとても便利です。
直接ブラウザーの CSS を変更するユーザーもいますが、レイアウトが固定サイズにできるだけ依存せず柔軟に作られていれば、どのような方法で文字サイズが変更されても適切に表示されるはずです。
対応には柔軟なレイアウトが必要
目的はユーザーが文字サイズを調整できることです。Dynamic Type 対応に限らず、レイアウトが柔軟に崩れずに文字サイズの変更に耐えられる必要があります。
そのため、Dynamic Type 対応自体をやる前に、Android や Safari Mac でも役立つ大きい文字サイズ対応が必要です。開発を Mac で行なっているのであれば、Safari Mac を確認するのが一番簡単です。command+option+plus を何回か押して文字サイズを大きくし、レイアウトが崩れないか確認しましょう。
よく見かける対応の一つが CSS で height
を min-height
に変えることです。要素のサイズが文字サイズに応じて自然に広がるようになります。場面に応じて width
を min-width
に変えることもあります。
height
を min-height
に変えるだけだと、文字サイズを大きくした時に padding
が足りないことが珍しくありません。元々 height
が padding
の役目を担っていたためです。
このような場合、標準サイズで見た目が変わらない程度の padding
を追加するのが一般的な対処法です。ただし、padding
で元々の固定高さを全て埋める必要はなく、適度な空白を確保する程度で十分な場合が多いです。
こういうときは VRT(Visual Regression Testing)があると役立ちます。標準サイズでの見た目が変わらないことを確認できるためです。
height
を min-height
に変えるだけで済むことが珍しくないとはいえ、より大掛かりなレイアウトの変更が必要なケースもあります。CSS のレイアウト関連機能を活用して、できるだけ固定サイズを避けた実装を心がけましょう。フローレイアウト、flexbox、grid レイアウトをうまく活用すれば、要素のサイズをコンテンツから自動的に計算させることができます。
確かに、固定サイズを使うよりも実装の難易度は上がりますし、後からレイアウトを修正するのも最初から考慮するより難しくなります。しかし、結果としてより柔軟で壊れにくいレイアウトが実現できることが多いでしょう。
レイアウトの一部を JavaScript で実装する際、文字サイズ設定が変更されても要素の高さが自動的に再計算されないことがあります。ユーザーは頻繁に設定を変更しないため、ページをリフレッシュすれば解決する程度の問題であれば重大ではないかもしれません。しかし、設定変更時にページをリフレッシュせずともレイアウトが崩れないのが理想的です。
ここでは大きい文字サイズに対応するためのレイアウトについて説明してきましたが、実はこのような柔軟なレイアウトは多言語対応にも有効です。テキストの長さは言語によって大きく異なるため、同じような柔軟性が必要になるからです。
ウェブでの Dynamic Type 対応
それでは、本題のウェブでの Dynamic Type 対応について説明します。
本来の使い方
ウェブでの Dynamic Type の本来の使い方ですが、WebKit のブログで紹介されているように、CSS に以下のどれかの font:
指定で使います(注意:font-family
ではありません):
font: -apple-system-body;
font: -apple-system-headline;
font: -apple-system-subheadline;
font: -apple-system-caption1;
font: -apple-system-caption2;
font: -apple-system-footnote;
font: -apple-system-short-body;
font: -apple-system-short-headline;
font: -apple-system-short-subheadline;
font: -apple-system-short-caption1;
font: -apple-system-short-footnote;
font: -apple-system-tall-body;
上記のブログにはありませんが、WebKit のソースコードを見ると、以下の指定も使えます。
font: -apple-system-title0;
(HIG にある large title に相当するものです)font: -apple-system-title1;
font: -apple-system-title2;
font: -apple-system-title3;
上記の font
指定のついた要素のフォントサイズと行送りは、テキストサイズ設定に合わせて自動的に変わります。JavaScript で設定を直接確認する方法は現時点で提供されていないようです。
HIG には「short」や「tall」のついたスタイルは記載されていませんが、検証してみると、通常版と同じフォントサイズで leading(行送り)のみが異なることがわかりました。-short-
版は通常より行送りが狭く、-tall-
版は広くなっています。
HIG に記載のないものとして font: -apple-system-title4;
も存在しますが、これは Dynamic Type 設定を変更しても 38px のまま固定されるという不自然な挙動を示すため、使用は推奨されません。
注意点として、font: -apple-system-xxxxx;
の指定がある要素に font-size
を設定すると、Dynamic Type の影響を受けなくなります。そのため、これらを組み合わせて使用するべきではありません。Apple が用意したスタイル以外のフォントサイズを使用したい場合の方法については、次のセクションで説明します。
Mac では、この font: -apple-system-xxxxx;
指定は利用可能ですが、Dynamic Type 設定が存在しないためサイズは固定されます。さらに、HIG にも記載されている通り、iOS の標準サイズとは異なります(例:body が iOS では 17px なのに対し、Mac では 13px)。このため、iOS 以外での使用はお勧めできません。
もっと柔軟な使い方
Safari 以外では font: -apple-system-xxxxx;
を使うことはできません。Apple のデザインシステムを他のプラットフォームで活用するのは不自然であり、大きな制約となります。
では、Apple のデザインシステムを使わずに iOS だけで Dynamic Type を利用するにはどうすれば良いのでしょうか?基本的に CSS で rem
単位を活用することで実現できます。私たちの実装では、それに合わせてカスタムプロパティ(変数)を使用することにしました。
em
単位が現在の要素のフォントサイズを基準とした単位であるのに対して、rem
(root em)単位はルート要素(HTML では <html>
要素)のフォントサイズを基準とした単位です。
以下の CSS コードを見てみましょう。
:root { --font-scale: 1px; } /* -apple-system-body を iOS だけで使う。Mac では一応使えるが、小さすぎるし、Dynamic Type 設定は存在しないので使わない */ /* ウェブビュー以外で使うのをおすすめしないので、普通の Safari iOS の場合 User-Agent 等を見てこの @supports の中身をブラウザーに送らないのを推奨する */ @supports (font: -apple-system-body) and (-webkit-touch-callout: none) { :root { /* html も使えるが、優先度のもっと高い :root を使う方が確実 */ /* -apple-system-body が単なるフォントではなく、フォント、そして Dynamic Type 設定によって変わるフォントサイズと行送り。 他に存在するスタイル(-apple-system-headline など)もあるが、マルチプラットフォーム文脈で使う場合は使いづらい */ font: -apple-system-body; --font-scale: calc(1rem / 17); /* iOS では、Dynamic Type の標準設定で -apple-system-body のフォントサイズが 17px */ } body { /* Dynamic Type の標準設定でフォントサイズを 16px になるように */ font-size: calc(16 * var(--font-scale)); } }
@supports (font: -apple-system-body)
でそのブロックの中の評価を Safari だけに絞り、and (-webkit-touch-callout: none)
で Safari iOS にさらに絞ります。
『スタディサプリ小学・中学講座』の場合、この CSS が使われる画面はスマートフォンのブラウザーでは表示されません(開こうとすると、ネイティブアプリをインストールして使うように促されます)。ただし、ページが Safari iOS アプリでも表示される可能性がある場合で、他のウェブページのように Dynamic Type の影響を受けないようにしたい場合は、User Agent またはウェブビューの特定のパラメーターでアプリ内表示か Safari 表示かを区別し、Safari iOS の場合は @supports
ブロックがブラウザーに送られないようにする必要があります。
この CSS の中心は --font-scale
という CSS カスタムプロパティ(変数)です。iOS 以外のプラットフォームでは、:root { --font-scale: 1px; }
だけが評価されるため、--font-scale
は 1px
となります。
iOS では @supports
内も評価され、--font-scale
の値が上書きされます。値は 1rem / 17
となり、テキストサイズの標準設定では 1px になりますが、その他の設定では --font-scale
が設定に応じて変動します。
<body>
の標準フォントサイズは通常 16px ですが、iOS で :root { font: -apple-system-body; }
の評価により 17px に変更されるため、16px に戻すために font-size: calc(16 * var(--font-scale));
が必要です。
その後は全てのフォントサイズ指定を font-size: calc(NN * var(--font-scale))
で行います。var(--font-scale)
は標準設定で 1px なので、calc(NN * var(--font-scale))
は標準設定で NNpx となります。数式の NN
には単位をつけません。これは var(--font-scale)
が既に単位を含むためです。
全ての font-size
をこのような数式で指定するのは読みにくいかもしれません。私たちの場合、font-size
指定は基本的にデザインシステムで定義されたサイズを参照するため、直接書くことは稀です。以前デザインシステムのサイズを使用していたものの定数を参照していなかったコードは、今回の対応の前準備として修正しました。
実際の定数は以下のように定義されています。私たちのウェブアプリは React を使用していますが、他のフレームワークでも同様の定義が可能でしょう。
export const fontSize = { 10: 'calc(10 * var(--font-scale))', 12: 'calc(12 * var(--font-scale))', 14: 'calc(14 * var(--font-scale))', 16: 'calc(16 * var(--font-scale))', // ... } as const
React のコードで参照する際は font-size: ${fontSize[12]};
のように使います。デザインシステムから外れた値についてのみ font-size: calc(NN * var(--font-scale))
を直接指定します。
line-height
の指定は単位なしが推奨されていますが、pt 単位で指定されている箇所がある場合は、font-size
と同様の方法で指定する必要があります。長期的には単位なしの指定に変更することをお勧めします。
xxx { line-height: calc(NN * var(--font-scale)); }
em
単位が使われている要素がある場合も注意が必要です。文字サイズの設定を変えたときに iOS でも Android でも Safari Mac でも見た目が崩れないか確認が必要です。特にテキストが入っていない要素の場合は要注意です。
Tips
柔軟なレイアウトを作る負荷を減らすにはツールを活用するのが大切です。
iOS
ひとまず開発に使われる Mac の Safari の設定の「詳細」タブで「Webデベロッパ用の機能を表示」にチェックを入れます。
これでメニューに「開発」が表示され、このメニューから接続されている iOS 端末や起動中の iOS Simulator の Safari に接続できます。Safari のインスタンスが表示されない場合は、両方の Safari を再起動してみましょう。
Mac の Safari のインスペクタをアプリ内ブラウザー(ウェブビュー)に接続することもできます。そのためには、WKWebView
の isInspectable
プロパティを true
に設定する必要があります。
#if DEBUG if #available(iOS 16.4, *) { webView.isInspectable = true } #endif
#if DEBUG
を使用することで、リリース版での有効化を防ぐことができます。
Android
開発マシンの Chrome を Android 上の Chrome やアプリ内ブラウザーに接続できます。
Chrome で chrome://inspect/#devices
を開くだけです。Chrome DevTools の記事で詳しく説明されています。記事では実機の接続方法のみ触れていますが、Android Emulator にも接続できます(ただし Android Studio が起動中である必要があるようです)。
アプリ内ブラウザー(ウェブビュー)に接続できない場合は、リモートデバッグを有効にする必要があるかもしれません。ただし、setWebContentsDebuggingEnabled
のドキュメントによると、マニフェストで android:debuggable="true"
が設定されているアプリでは自動的に接続が可能なはずです。
Mac
前述の通り、Safari Mac では、通常のブラウザー拡大の他に加えて、文字だけの拡大/縮小もできます。
「表示」メニューで option を押すと「文字を拡大」(command+option+plus)と「文字を縮小」(command+option+minus)が出てきます。「実際のサイズ」(command+0)で標準設定に戻ります。
これらの機能は、文字サイズ変更に耐えられる柔軟なレイアウトの開発とデバッグに非常に便利です。
最後に
視力などの事情や文字サイズの好みは人それぞれ異なります。アプリがユーザーの設定に柔軟に対応することは、使いやすさの重要な要素となります。
また、アプリ内ブラウザーで表示される画面もアプリの一部として認識されているため、統一された動作を提供することでより良いユーザー体験を実現できます。
【お知らせ】2月にオンライン無料イベントを開催します!
2月にリクルートの開発事例・ナレッジを共有する技術イベントを2件開催します。
スタディサプリに関するセッションもありますので、ぜひご参加をお待ちしています!
①RECRUIT TECH CONFERENCE 2025 プレイベント - LT Night -
日時:2/4火 19:00~20:00
お申し込みはこちら:https://recruit-event.connpass.com/event/337843/
今年のイベントコンセプト"技術を活かす現場力"を体現する現役エンジニアが登壇し、『ホットペッパービューティー』『スタディサプリ』などの事例を紹介する個性豊かなLTを展開します。
②RECRUIT TECH CONFERENCE 2025 - 技術を活かす現場力
日時:2/19水 12:00~18:00、2/20木 12:30~19:00
お申込みはこちら:https://recruit-event.connpass.com/event/333019/
2日間にわたり、社会価値創造に向き合うエンジニアのリアルや取り組みをお伝えします。
今回は、和田 卓人 氏、登 大遊 氏、谷口 忠大 氏をゲストに迎えたセッションも開催予定です。