こんにちは。KDDIアイレットの黒木です。
INSIDE UI/UXと題して、所属メンバーがデザイン・SEO・アクセシビリティ・UI/UXなどそれぞれスペシャリティのある領域に対する知見を幅広く発信しています。
最近は、AI駆動開発の広がりによって実装のスピードが上がってきました。一方で、でき上がったものがデザイン通りかを確認する工程には、まだ人の目に頼る部分が残っているように感じています。
そこで今回は、「デザインと実装のズレを画像だけで自動判定できるか」を、社内で使う検証ツールを実際に作りながら確かめてみました。その過程で分かったことをご紹介します。
1. 今回の前提
見比べる相手はデザインカンプです。Figmaのデザインと、実装された画面を並べて、違っている箇所を機械的に見つけられるかを試しました。
なお今回のツールは、デザインとの差分を見つけることに加えて、文字の読みやすさやタップ領域の大きさといったアクセシビリティの検査も同時に行う作りにしています。どちらも「実装された画面を機械的に確認する」という点で共通しているためです。
そのうえで、デザイン側の情報は画像だけに限定しています。FigmaのAPIを使えばデザインの数値を直接取得できますが、あえて使っていません。理由は2つあります。
- クライアントのデザインデータを外部に送りたくない: APIを使うにはアクセストークンが必要で、対象ファイルを読み取れる権限を持つことになります。案件によっては、この時点で確認が必要になります。
- 画像さえあれば動く状態にしたかった: Figmaに限らず、他のツールで作られたデザインでも、スクリーンショットでも使えます。
そのため本記事の「できる・できない」は、画像1枚から読み取れる範囲での話になります。APIを使えば解決するものも含まれますが、今回はその手前で何がどこまで分かるかを確かめました。
なお、ツールの実装にはClaudeを使いました。ディレクターである筆者が「こう動かしたい」と伝え、コードを書いてもらう進め方です。実装そのものは想像よりずっと速く進みました。ただし、動かして確かめる工程はまったく減りませんでした。詳しくは6章で触れますが、そこが今回いちばんの収穫だったかもしれません。
※このツールはまだ改善の途中で、いまは社内で試しながら、外れた指摘を見つけては直している段階です。
2. 【検証】色・余白・要素サイズは画像から取れるか
まずは、既知の値を持つページを描画し、その画像から値を読み取れるかを確認しました。
①色
宣言 #2F80DE → 画像 #2F80DE 宣言 #D9877A → 画像 #D9877A 宣言 #BBBBBB → 画像 #BBBBBB (ほか2色も含め、5色すべて一致)
②要素サイズと余白
ボタン 真値 200 × 48 px → 画像から測定 200 × 48 px 誤差 0 左余白 真値 32 px → 画像から測定 32 px 誤差 0
③角丸
指定 測定 誤差 0px 0px 0px 4px 3px 1px 8px 7px 1px 16px 15px 1px 24px 23px 1px
いずれも十分な精度でした。「色が違う」「余白が違う」「ボタンの大きさが違う」は、画像だけで判定できることが確認できました。ここまでは想定通りです。
3. 【検証】文字サイズは画像から取れるか
ところが、文字だけは同じようにいきませんでした。
画像に写っているのは、実際に描かれた文字の形です。「この文字は24pxで指定されている」という情報そのものは、画像のどこにも入っていません。
同じ24pxの文字で比較したところ、次のようになりました。
指定 実際に描かれた高さ 「日本語のサンプル」 22px 「国国国国」 21px 「一一一一」 2px ← 同じ24px指定なのに 「xxx」(ラテン) 12px

「一」は横棒1本のため、2pxしか描かれません。描画される高さは「文字が何か」で決まるため、そこからfont-sizeを逆算することはできません。書体によっても比率が変わり、同じ24pxで0.08〜0.92の開きが出ました。
「だいたい何pxくらい」という推定値を出すこともできますが、今回はやめました。推定値が出てくると、それが合っているかどうかを人が毎回確かめることになり、かえって手間が増えてしまうためです。
4. 【気づき】両方を画像から測る必要はなかった
ここで、そもそもの前提を見直しました。
当初は「デザインの画像」と「実装の画像」を2枚並べて、どちらも画像から測って比べるつもりでいました。だから「文字サイズが測れない」ことが致命的に思えたのです。
しかし、よく考えると実装側は画像にする必要がありません。 実装はブラウザ上で動いているので、値をそのまま取得できます。
具体的には、JavaScriptからgetComputedStyle()を使うと、その要素に最終的に適用されている値が取れます。
実装側のボタンから取得した値 font-size : 15px padding : 12px 28px border-radius : 8px background : #2F80DE
これは画像から推測した値ではなく、ブラウザが実際にその値で描画しているという事実です。つまり比較の片側は、最初から正確に分かっていたわけです。
つまり、こういう状態になります。
デザイン側 画像しかないので、測るしかない
→ 色・余白・大きさは測れる(2章の通り)
→ 文字サイズだけ測れない(3章の通り)
実装側 ブラウザで動いているので、測らなくていい
→ すべて確実な値が手に入る
比べる2つのうち、片方は常に正確という状態を作れるわけです。測ることで誤差が入るのは、デザイン側だけになります。
残った文字サイズについても、実装側の値は確実に出せます。そこで「デザインは分かりませんが、実装は15pxです」と表示し、判断は人に任せる形にしました。ここに気づいてから、設計がかなり整理されました。
5. 判定できた違いは7種類と、その限界
最終的に、画像から見分けられる違いは次の7つに落ち着きました。実際の出力はこのような形です。

※掲載している画面は、本記事のために作成した架空の管理画面を対象にしたものです。実在のサイトやサービスではなく、文字はすべてダミー、ロゴ・社名・実在の文言は含まれていません。
| 種類 | 出力される内容 |
|---|---|
| 欠落 | デザインにあるものが実装で表示されていない |
| 余分 | デザインにないものが実装で表示されている |
| 色 | #2F80DE → #D9877A |
| 位置・余白 | 実装が16px下にずれている |
| 大きさ | 300×100 → 240×100(幅 −60px) |
| 画像 | 画像の内容が違う |
| 書体・字形 | 文字列の幅が+22px違う/実装のほうが太く見える |
「7種類しか分からないのか」と思われるかもしれませんが、これ以上増やせない理由があります。
画像には「結果」しか写っておらず、「原因」は写っていません。
たとえば「2つの要素のあいだが16px空いている」という見た目は、いくつもの書き方で作れます。

どれを使っても、でき上がった画面はまったく同じに見えます。そのため画像からは「16pxずれている」までしか言えず、「marginの指定が違う」とは言えません。判定の種類を増やしても、この壁は越えられませんでした。
では原因はどうやって知るのか。ここで、4章の「実装側の値はそのまま取得できる」が効いてきます。
画像を見比べて「このあたりが違う」と分かったら、その要素の値を取得して、実際の指定を確認すればいいわけです。
ステップ1 画像を見比べる
→ ボタンの下の余白が、デザインより狭いように見える
ステップ2 その要素の値を取得する
→ 実装は padding-bottom: 16px
ステップ3 デザイン仕様と照らす
→ 仕様は 24px。ここが原因だった
画像だけでは「なんとなく狭い」までしか分かりませんが、値まで見れば原因が確定します。
実際には、画面上の要素をクリックするとその値が表示されるようにしました。

そこで、「どこが違いそうか」は画像で見つけ、「何がそうさせているか」はクリックして値を表示する、という形に分けました。1つの機能で両方やろうとすると、どちらも中途半端になってしまいます。
6. つまずいた点と対処
ここからは失敗の記録です。どれも、実際に動かしてみるまで気づけませんでした。6つあります。
- ①見つけるのは簡単だが、必要な指摘だけに絞るのが難しい
- ②ブラウザの中からは見えないものがある
- ③動きのあるページは1回でスクリーンショットを撮らないと崩れる
- ④裏に回ったタブでは撮影が進まない
- ⑤指定した幅でスクリーンショットが撮れていなかった
- ⑥配布したものと違うものをテストしていた
①見つけるのは簡単だが、必要な指摘だけに絞るのが難しい
最初にコーポレートサイトへ当てたところ、三桁の指摘が出ました。
一見すると「たくさん見つかってよかった」ように思えますが、実際にはまったく使えません。この件数では誰も読まないからです。 本当に直すべき箇所が、どうでもいい指摘に埋もれてしまいます。
そこから減らす作業を始めて、最終的に二桁前半になりました。減らした分は、大きく2種類でした。
- そもそも間違って指摘していたもの(ツールのバグ)
- 同じ1つの問題を、何度も別々に報告していたもの(まとめ方の問題)
順に説明します。
a.間違って指摘していたもの
このツールでは、デザインとの差分に加えて、文字が背景に対して十分に見分けられる濃さかどうかも一緒に検査していました。文字色と背景色を比べて、差が小さければ「読みにくい」と指摘する仕組みです。
その指摘が、実際には問題のない箇所に何件も出ていました。ツールが出した数値を見ると、原因はすぐに分かりました。
文字色と背景色の差 : ほぼゼロ(まったく同じ色として計算されていた)
文字色と背景色が、同じ色として扱われていました。 これでは「まったく読めない文字」という判定になります。
該当箇所を見ると、グラデーションのかかった文字でした。この手の文字は、文字そのものに色を塗るのではなく、背景のグラデーションを文字の形に切り抜いて表示しています。 文字自体は透明なので、色を調べると背景と同じ値が返ってきていたわけです。
正しい対処は、計算をやめることでした。 グラデーションの文字は場所によって色が変わるため、そもそも濃さを1つの数値で表せません。無理に数値を出すのをやめ、「自動では判定できないので目で見てください」という別枠に分けました。
b.同じ問題を何度も報告していたもの
リンクのタップ領域に関する指摘が大量に並んでいたのですが、よく見ると横幅が違うだけで、問題はすべて「高さが20px」でした。
リンクのタップ領域が 116×20px で 24px 未満です リンクのタップ領域が 109×20px で 24px 未満です …(以下、すべて「高さ20px」)
「高さが20pxしかないリンクが、たくさんある」という1つの問題です。それを場所の数だけ並べていたわけです。まとめてしまえば1件で済みます。
ただし、何でもまとめればよいわけでもありません。たとえばalt属性が抜けている画像は、画像ごとに入れるべき文言が違うので、1件にまとめると何をすればよいか分からなくなります。
「これは同じ1つの問題か、それとも別々の問題か」を項目ごとに決める必要があり、ここは自動化できませんでした。地味ですが、使えるツールになるかどうかはこの判断で決まったように思います。
②ブラウザの中からは見えないものがある
CSSには、一部のブラウザ向けに-webkit-という接頭辞を付けて書く指定があります。これが書かれていないと古い環境で効かないことがあるため、「書き忘れがないか」を検査しようとしました。
結果は、原理的に不可能でした。
CSSの内容は、JavaScriptから読み取ることができます。そこで、接頭辞を書いた場合と書かなかった場合で、読み取れる内容を比べてみました。
実際に書かれている CSS
接頭辞なし backdrop-filter: blur(4px);
接頭辞あり backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
JavaScript から読み取った結果
接頭辞なし backdrop-filter: blur(4px);
接頭辞あり backdrop-filter: blur(4px); ← 接頭辞の行が消えている
Chromeは-webkit-付きの指定を「同じものの別名」として扱うため、読み取れる情報からは消えてしまいます。書かれていた場合と書かれていない場合が、まったく区別できません。
ブラウザ拡張機能はブラウザの中で動くため、ブラウザが整理した後の状態しか見られないということでした。これはツールの作り方を変えても解決しません。
③動きのあるページは1回でスクリーンショットを撮らないと崩れる
ページ全体のスクリーンショットは、最初はスクロールしながら何枚か撮って、縦に繋ぎ合わせていました。これが盛大に崩れます。原因は3つ重なっていました。
- スクロール連動アニメーション: 段ごとに表示状態が異なるため、繋ぐと食い違う
- 遅延読み込み: 撮影中にページの高さが変わり、位置がずれる
- 固定・追従要素: 隠す処理が、かえってレイアウトを動かしてしまう
最終的に、スクロールせず一度で撮る方式に変えました。撮る前にいったん最後までスクロールして戻すことで、遅れて読み込まれる画像やアニメーションを先に済ませてしまう、という手順です。
この方式に変えたことで、次の問題が出てきました。
④裏に回ったタブでは撮影が進まない
対象のページを開いたまま別のタブを見ていると、撮影がいつまでも終わらないという現象にも遭遇しました。
撮影では、少しずつスクロールしながら「描画が終わるまで待つ」という処理を繰り返します。この待ち時間に、requestAnimationFrameという仕組みを使っていました。画面が描き直されるタイミングに合わせて次の処理へ進むもので、描画待ちには適しています。
ところが、これには前提がありました。表示されていないタブでは、ブラウザは画面を描き直しません。 描き直されないので、次の処理へ進む合図がいつまでも来ません。
実際に、裏に回ったタブで待ち処理を3回動かして確かめてみました。
表示されているタブ 3回とも進む 裏に回ったタブ 1回も進まない(そこで止まったまま)
そのため、対象のページを前面に出しておかないと撮影が始まらない、という状態になっていました。
対処は、待ち方そのものを変えることでした。
変更前 画面が描き直されたら、次へ進む
→ 描き直されない裏タブでは、進む合図が来ない
変更後 0.1秒たったら、次へ進む
→ 表示されていなくても時間は進むので、止まらない
描画の完了を待てなくなるぶん、確実性は下がります。そこで待ち時間を少し長めに取り、撮影前にページを一度最後までスクロールさせて、読み込みを先に終わらせておく手順と組み合わせました。
⑤指定した幅でスクリーンショットが撮れていなかった
画面幅を1440pxに指定したのに、実際には1425pxで撮影されていました。差は15px。スクロールバーの幅でした。
画面幅を1440pxに指定すると、それはスクロールバーも含めた幅になります。中身が実際に使える幅は1440 − 15 = 1425pxで、撮影はそちらに合わせて行われていたわけです。撮影のあいだだけスクロールバーを隠すことで解消しました。
⑥配布したものと違うものをテストしていた
最も反省した点です。
③で書いた「一度でスクリーンショットを撮る方式」には、Chromeの少し強めの機能を使う必要がありました。こうした機能は、ツール側で「この機能を使います」と申告しておかないと利用できません。
このとき、申告の仕方を2通りから選べます。
必須として申告 ツールを入れた時点で使えるようになる 任意として申告 使う直前に、利用者へ確認を出して許可をもらう
利用者にいきなり強い権限を求めるのは避けたかったので、「任意」を選びました。 ところが、この機能は任意として申告できない決まりでした。Chromeはこう警告していました。
この権限は任意指定にできません。省略されます
「省略されます」というのは、申告そのものがなかったことにされるという意味です。 つまり機能が使えない状態のまま配布していました。撮影を実行しても、何も起きません。
気づけなかった理由は単純でした。テストのときだけ、申告を「必須」に書き換えたコピーを動かしていたのです。そのコピーでは当然うまく動くので、テストは問題なく通ってしまいます。
配布するものと、動作確認するものが別になっていたわけです。これ以降、検証は必ず配布するものそのままで行い、ブラウザが出す警告も毎回確認するようにしました。
まとめ:今回の検証で確認できたこと
デザインと実装のズレを画像だけで判定できるかを検証し、以下の結果が得られました。
- 色・余白・大きさは、画像から測れる: 色は指定した値と完全に一致し、余白や要素の大きさも誤差なく測れました。角丸も1px以内の誤差で測定できます。
- 文字サイズだけは、画像から測れない: 画像に写っているのは実際に描かれた文字の形だけで、そこから指定値を逆算することはできません。同じ24pxでも、文字によって描かれる大きさが変わるためです。
- 比べる2つのうち、片方は常に正確にできる: 実装側はブラウザ上で動いているので、値をそのまま取得できます。画像から測る必要があるのはデザイン側だけでした。
- 判定できる違いは7種類まで: 画像には「結果」しか写っておらず、「原因」は写っていません。同じ見た目を作る方法は何通りもあるため、これ以上は増やせませんでした。
- 見つけることより、絞ることのほうが難しい: 最初は三桁の指摘が出て、まったく使えませんでした。間違った指摘を取り除き、同じ問題をまとめて、二桁前半にしてようやく実用的になりました。
自動化できるのは「明らかに違うものを機械的に並べる」ところまででした。alt属性の文言が適切かどうかといった、意味の判断は人に残ります。
それでも、ページ全体を数十件まで整理できれば、目視で確認する負担はかなり変わります。ツールの役割は判断を代行することではなく、判断すべき対象を人の前に並べることだというのが、今回の結論です。
そしてもう1点。今回いちばん学びが大きかったのは、動かして実測するまで、ほとんど何も分からなかったという事実そのものでした。
ブラウザが接頭辞の指定を消してしまうことも、裏に回ったタブでは待ち処理が止まることも、スクロールバーが指定した幅を削っていたことも、公式ドキュメントには書かれていません。設計だけ進めて作っていたら、どの判断も間違えていたと思います。
本記事の執筆について
本記事は、Claudeとの対話を通じて執筆しました。掲載している数値は、いずれも実際に検証環境で計測した結果です。構成の整理と文章の下書きにClaudeを使い、内容の確認と最終的な判断は筆者が行っています。