開発Tips

掲載画像を横長にし、生成器を1本にする — コピーと配色を単一情報源から引く

2.0.0 でこども画面をまるごと刷新したので、ストア掲載画像を撮り直しました。撮り直しに合わせて、掲載画像の 構成そのもの と、素材を作る仕組みを作り直しています。

作り直す前の掲載画像は、7枚すべてが同じ骨格でした。同じクリーム色の背景、中央に2行の見出し、同じ幅の端末フレーム。スクロールしても情報が増えません。見出しの文字サイズもキャンバス幅の 5.6%(1080px 幅で 60px)しかなく、ストアのサムネイル状態では読めていませんでした。

以前に書いた 実機なしで掲載スクショを撮る仕組み は「撮る」側の話です。今回は「見せ方を決める」側の設計です。

スマホを横長にする

決め手はストアの表示ロジックでした。App Store は縦長の画像を3枚ずつ並べて表示し、横長の画像は1枚で全幅表示 します。同じ画素数の画像を「幅の1/3」に置くか「全幅」に置くかで、見出しの実効サイズが2〜3倍変わります。

両ストアとも横長を正式に受け付けます(App Store 6.9 インチ枠は 2868×1320、Play のスマホ枠は 16:9 で最小 1920×1080)。そこでスマホ掲載を横長5枚に組み替え、1枚ごとに骨格を変えました。端末を右に置く・左に置く・中央に大きく置く、切り出した部分画面を重ねる、といった具合です。隣り合って並ぶ前提なので、構図を繰り返さないこと自体が情報量になります

タブレットは縦長のまま残しました。iPad は 0.75:1、Android タブレットは 0.625:1 で、スマホ横長の 2.17:1 / 1.78:1 とは骨格がまるで違います。比率が違うものを同じレイアウトに押し込むと、どちらかが破綻します。共通化するのは部品とトークンまでで、骨格は分けます。

ダークテーマ版の生成はやめました。ストアは閲覧者のテーマで画像を出し分けません。掲載しないものを作り続ける理由がありません。

コピーは原典から読む

以前は生成器が自分でコピー(見出し文)を持っていました。結果、宣材文の原典と乖離し、いちばんの差別化であるキャラクター育成の訴求が、原典の2枚目から4枚目へずれていました。

そこで、宣材ドキュメント内の YAML ブロックを単一情報源にして、生成器がそれを読む形にしました。

// 見出しは「行の配列」。改行位置は原典が決める
export const lines = (arr) => (arr ?? []).map(accent).join('<br>');

改行位置まで原典に持たせているのがポイントです。自動折り返しに任せると「おばあちゃん/も、」のような位置で折れます。掲載画像の見出しは読み物ではなく看板なので、改行はコピーの一部 です。文字数ではなく意味の区切りで折る判断は、書いた人しかできません。

配色も単一情報源から

アプリのテーマは、Markdown の frontmatter に書いたトークンを正として生成しています。掲載画像の生成器が独自に色コードを持つと、アプリの配色を変えたときに画像だけ古い色で残ります。同じ frontmatter を読ませました。

唯一の直書きは、紙色から起こす淡いハイライト1色だけです。アプリ側に相当するトークンがなく、掲載画像でしか使わないためです。例外を1つに抑えて、それがどれか分かるようにコメントを残す ところまでを設計に含めます。

寸法は高さ基準の相対値に、幅側の上限を添える

キャンバスの比率が iOS 横長 2.17:1 と Play 横長 1.78:1 で違うため、固定 px でも幅基準でも片方が破綻します。高さ基準の相対値で書き、幅側にも上限をかけました。

// 端末フレームの幅: 高さ基準の比率と、幅基準の比率の小さいほう
export const phoneW = (W, H, kH, kW) => Math.round(Math.min(H * kH, W * kW));

縦積みの要素は、絶対配置とパーセント指定で「見出しの下端」を手計算しません。コピーの行数はいつでも変わるので、変わった瞬間に重なります。縦に積むものは素直にレイアウトのフローへ入れます。変わる前提のものを座標に変換しない ということです。

端末フレームも端末ごとに分けました。すべてを iPhone のモックアップで描くと、内側スクリーンの比率が iPhone に固定され、iPad(0.75)と Android(0.56)の生スクショが左右を切り取られます。

切り出し位置は生成時に実測する

画面の一部(やることの追加シート)を切り出して重ねる構図があります。シートが画面に占める割合は端末で大きく違い、iPhone で y=60%、iPad で y=74%、Android スマホで y=41% あたりから始まります。

この座標を表に固定すると、アプリの UI を変えた時点で切り出しがずれ、意味を持たない断片が掲載画像に焼き込まれます。そこで、生スクショの画素から位置を測ることにしました。

// 主ボタンの色を条件にして、横に広い帯のうち最も下のものを探す
const isOrange = (r, g, b) => r > 215 && g > 110 && g < 170 && b < 80;

主ボタンの帯を見つけてシートの下端を決め、そこから上へ、地の色が続く範囲をシートの上端とします。生成物が元データの座標を参照するなら、座標は測る。書き写した座標は、元データが動いても警告を出しません。

撮影条件は装飾の前段

もう1つ直したのが撮影側の端末条件です。Android スマホの撮影が論理解像度 360×640 相当になっており、実機よりかなり狭い10年前の端末に相当していました。

幅 360dp ではスタンプ一覧のグリッドが1列に潰れます。「100種類以上」が売りの画面に、スタンプが1個しか写らない掲載画像になるわけです。現行の端末に合わせた論理解像度に変えました。

掲載画像の説得力は装飾ではなく中身の絵で決まります。フレームや見出しをどう作るかの前に、その中に何が写るかを決める工程があります。

生成器を1本にする

最後に、ストア用と SNS 用で分かれていた2系統の生成器を1本にまとめました。別デザイン・別コピー源で独立していたため、アプリ名を変えたときに SNS 画像とフィーチャーグラフィックが旧名のまま取り残されていました。

トークン・部品・コピー源を共通化し、SNS 側の旧実装を退役させました。いまは掲載スクショ・フィーチャーグラフィック・SNS 3種・LP の OG 画像を含む25枚を、1コマンドで出せます。

一般化すると、同じコピーと同じ配色を使う出力は、同じ生成器から出す。「用途が違うから別スクリプト」は、単一情報源を2つに割る言い換えでしかありません。改名のような横断的な変更が来たときに、割れているぶんだけ取りこぼしが出ます。

まとめ

  • 掲載画像の構成は、ストア側の表示ロジックから逆算する。全幅で1枚出るなら横長が有利
  • 比率が違うキャンバスは骨格を分ける。共通化するのは部品とトークンまで
  • コピーは原典を単一情報源にし、改行位置まで原典に持たせる
  • 変わる前提のものを座標に変換しない。縦積みはレイアウトのフローへ
  • 生成物が元データの座標を参照するなら、測る。書き写した座標は、元データが動いても追随しない
  • 同じコピー・同じ配色を使う出力は、同じ生成器から出す