PROMARI JOURNAL

iframe から、丸型の共有ボタンと「いいね」へ。Promari SNS Share を公開しました。

作成: / 公開: / 内容更新:

執筆:Takaomi Murasaki / 掲載・運営:プロマリ

丸い共有ボタンの設計を、用語のポップアップと身近なたとえでたどります。表示・保存・計測・配信の内容を省略せず、四層の責任と設計判断を詳しく解説。全5ページ、連載予定は16章。

PASS IT ON

ひとつの発見を、次の会話へ。
自然光が差す机で、共有ボタンといいねを表示したノートPCとスマートフォンを並べた写真風のイメージ

こんにちは、プロマリの紫です。記事の上に並んでいる、丸い共有ボタン。今回は、この小さなボタンの裏側をご紹介したいと思います。「ボタンを丸くしたい」から始まったのに、気付けば型の契約、非同期処理、保存の一意性、CDNの配信まで。え? ボタンの形を変えるだけで、そこまで考えるのかって。それが、作ってみるとなかなか奥が深いのでございます。小さなボタンにも、気を付けたいことがいろいろあります。なぜWeb Componentsにしたのか、なぜUIに保存処理を持たせなかったのか。動いているコードを見ながら、どんな作りを選び、どこまで自分たちで管理することにしたのかを紹介していきます。

点線の付いた用語は、その言葉を押すと詳しい説明が開きます。意味、身近なたとえ、この実装での使い方を順に読めます。キーボードではTabで用語へ移動し、EnterまたはSpaceで開き、Escapeで本文へ戻れます。各ページで最初に出る用語から参照できるようにしました。

TECH BRIEF 技術の概要

  • 公開リポジトリ:promari-toolkit。プラグインやツールを集め、各コンポーネントを独立してバージョン管理します。
  • 今回のプラグイン:promari-sns-share 1.1.0。タグは promari-sns-share-v1.1.0 です。
  • 表示:X・LINE・Facebook・コピー・共有・その他の丸型ボタンと、件数付きのいいね。
  • 配信:バージョンを固定したjsDelivrのCDN。SRIで配信ファイルの内容も照合します。
  • 保存と計測:いいねの保存はサイト側、アクセス解析は同意管理を持つ計測レイヤが担当します。

丸いボタンを作るだけ、のはずだった

読者が押したくなる場所はどこか

それでは、まず記事の上にある共有欄をのぞいてみましょう。X・LINE・Facebookに加えて、コピーと端末の共有メニュー。利用頻度の低い共有先は「その他」にまとめました。見た目は丸いボタンですが、内部では「外部URLを開く」「ブラウザAPIを呼ぶ」「サーバーへ状態を保存する」という別々の操作が並んでいます。同じ丸いボタンでも、裏側の仕事はそれぞれ違うんですね。全部を一つのクリック処理にまとめてしまうと、何をもって成功とするのか、失敗したらどうするのかまで混ざってしまいます。操作欄としては一つ、実装の責任は別々。まず、この線を引くところから始めました。

Promari SNS Share 1.1.0 / 表示の説明図
いいねと、X・LINE・Facebook・コピー・共有・その他の丸型ボタン。操作名を大きく示した説明図いいねと、X・LINE・Facebook・コピー・共有・その他の丸型ボタン。操作名を大きく示した説明図
実際のアイコンを使い、操作名を読みやすく配置した説明図です。共有ボタンといいねは同じプラグインが描画し、記事の冒頭と末尾でいいねの状態が同期します。

ところが、設置してみると共有欄が二重に出ました。テーマに元からあった共有欄と、プラグインの自動挿入が、どちらも仕事をしていたんです。ボタンを整えるはずが、増やしてしまいました(笑)。CSSで片方を隠すこともできますが、それではDOMもイベントも残ります。そこで、描画はプラグイン、冒頭と末尾の配置はテーマへ分担を整理。自動挿入を止め、テンプレートから明示的に呼ぶ形にしました。「どこに出すか」の決定者を一つにすると、表示の確認箇所も絞れます。

ちなみに、共有先は追加しようと思えばいくらでも増やせます。とはいえ、一つ増やすたびに表示条件やイベント処理をあちこち直すのは、なかなか大変ですよね。私が欲しかったのは、普段使う操作を手前に置き、補助の共有先は設定で選べる部品です。冒頭と末尾で同じ設定を使い、アイコンやURLの定義も使い回す。見た目の整理を進めていたら、そのまま「何を一か所で管理するか」という設計の話になりました。見た目を整える話が、中の作り方にもつながってくる。自分で作ってみると、こういう発見があるんですね。

さて、きれいに丸く並んだところで完成! と言いたいところですが、もう少しお付き合いください。保存中に末尾のボタンも押されたら? 応答だけ失われて再送されたら? 見た目だけでは分からない、処理の順番で結果が変わる場面もあるんです。まずは、そうした場面を一つずつ考えてみます。

キーボードだけでも使えるか。保存中にもう一度押されたらどうするか。通信に失敗しても記事は読めるか。アクセス解析を許可しなくても共有できるか。こうした条件も、私たちが作る機能の一部です。何ができるかという機能要件と、どんな状況でも守りたいかという非機能要件を一緒に考えると、「それは後で」と置いていた処理が、最初から必要だったことに気付きます。

冒頭と末尾へ置くのは、読み始めと読み終わりの両方に操作の入口を残すためです。ただし、入口を二つにしたからといって、状態まで二つ持つ必要はありません。共有URLは同じ記事の正規URL、いいねは同じ記事の確定状態を参照します。配置ごとの差は計測用の位置情報に寄せる。表示位置は違っても、記事への反応は一つと決めておくと、保存キーへページ上の座標や配置名が紛れ込まずに済みます。

ここで私が大切にしたいのは、ボタンの数ではなく、読者が迷わず次の行動へ進めることです。「共有機能を付けました」だけなら、リンクを並べれば済みます。でも、記事の途中で邪魔にならないか、見つけたときに何が起こるか想像できるか、操作した後に結果が分かるか。この前後を含めて、一つの機能として眺めてみます。

共有といいねは隣同士ですが、ユースケースとしては別です。共有は記事のURLを外へ渡す操作。いいねは記事と読者の関係を保存する操作。前者には外部サービスの共有URL、後者には保存の一意性や認証状態が関わります。この違いを無視して「共通のボタン処理」に寄せすぎると、コピーにも保存APIの都合が漏れてきます。共通化するのは配置や操作の通知まで。失敗の意味が違う処理は、無理に同じ成功判定へ押し込まないようにしています。

操作の契約は、成功系だけで書くときれいに見えるんですよね。そこで「未取得・操作可能・保存中・失敗」を並べ、各状態で次の操作を受け付けるかを決めます。コピーの失敗といいね保存の失敗では、戻す状態も案内も違います。共通の例外ハンドラーへ丸投げする前に、何を維持し、何を再試行できるのかを決める。この作業をすると、ボタンのdisabledが単なる装飾ではなく、状態遷移の制約として読めるようになります。

アクセシビリティも、仕上げの飾りではありません。マウスで丸いボタンを押せる方だけが読むとは限らないからです。Tabキーで順に移動したとき、いまどこにいるか見えるでしょうか。EnterやSpaceで操作できるでしょうか。メニューを閉じた後、元のボタンへ戻れるでしょうか。小さな共有欄でも、この一連の流れを通して確かめます。

フォーカスまわりは、CSSだけで完結しません。メニューを閉じた後にトリガーへ戻すのか、選んだ操作へ移すのか。DOMを描き直したとき、押していた要素が消えて現在地を失わないか。とくに共有欄は複数配置するので、冒頭の操作で末尾へフォーカスが飛んだら困ります。テストでもボタンを見つけてクリックするだけでなく、document.activeElementShadow DOM内のフォーカスを確認する視点が要ります。見た目の小さな修正でも、操作の文脈は保ちたいですね。

aria-pressedは、切り替え式のボタンが押された状態かどうかを支援技術へ伝える属性です。aria-liveは、画面の一部で変わった情報を読み上げへ伝えるために使います。ただし、付ければ付けるほど親切になるわけではありません。件数の変化を何度も重ねて読み上げたら、記事を読む邪魔になります。何を、いつ、一度だけ伝えるかまで考えます。

「その他」のようなメニューでは、開けることと同じくらい閉じられることが大事です。Escapeで閉じられるか、外側へ移ったときに意図せず残らないか、選んだ共有先が分かるか。スマートフォンでは、右端のボタンから開いても画面外へ飛び出さないかを見ます。自分の大きなディスプレイで一度動かしただけでは、なかなか気付きにくい部分ですね。

属性の意味は、WAI-ARIAのaria-pressedaria-liveで確認できます。

ここまでの条件は、装飾が終わってから追加するより、要素とイベントの設計に織り込むほうが楽です。リンクは遷移、ボタンは操作、開閉領域は状態を持つ。ブラウザ標準の振る舞いを使える部分は使い、独自の制御が必要な部分だけ実装します。全部をクリック可能なdivで組み立てると、後からキーボード操作まで自分で面倒を見ることになるんですよね。自作する範囲は、ここでも絞っておきます。

もう一つ、動きについても触れておきます。ボタンが少し浮く演出は、操作できる場所だと伝える助けになることがあります。ただ、動きが苦手な方や、動きを減らす設定で使う方もいます。派手なアニメーションで注意を集める前に、その動きが何を伝えているのか、なくても使えるかを考えます。共有欄の主役は、あくまで読者の行動です。

動作確認では、同じ部品を二つ置いて片方だけ操作してみます。フォーカス、読み上げ用の通知、開いているメニュー、保存中の状態。この四つが、必要以上にもう片方へ伝わらないかを見るんです。件数は同期したいけれど、メニューまで同時に開いてほしいわけではありません。共有する状態と、各要素に閉じる状態を分けると、複数配置のテストで何を確認するかもはっきりします。

小さな共有欄から、どこまで学べるか

ここからは、iframeを外した理由、Web Componentの境界、型と設定生成、いいねの保存、計測、配信の順に見ていきます。気になるところから読んでも大丈夫です。コードそのものに加えて、「この変更が入ったらどこを直すか」「通信に失敗したら何が残るか」まで追っていきましょう。小さな部品なので全体を見渡せますし、それでいて設計上の論点はしっかりあります。身近なボタンを入り口に、中の仕組みまで見ていける。今回ご紹介するには、なかなか面白い題材だと思った次第です。

用語の補足は点線付きの言葉から開けます。本文では定義を繰り返すより、その技術を選ぶと何が簡単になり、代わりに何を自分で引き受けるのかを中心に書きます。設計判断のラベルからは理由を確認して、その場でコメントも残せます。「この条件なら別案のほうがよさそう」という話も、ぜひ聞かせてください。判断の前提が違うと、同じ技術でも選び方が変わりますからね。

読むときは、自分のプロダクトへ置き換えてみると面白いと思います。保存先がWordPressではなく別のAPIだったら? 同じ部品をSPAに載せたら? 共有先の定義を別チームが管理したら? そうした変更で、どこまで既存コードを触らずに済むか。抽象化の価値は、クラスの数よりこの差に出ます。逆に、変更の予定も検証上の利点もない境界なら、分けないほうが読みやすいこともあります。

この後は、1ページ目で共有欄とブラウザの境界、2ページ目で設計と型、3ページ目で設定・状態・計測、4ページ目で導入と検証を扱います。5ページ目は連載の目次です。少し長くなりますので、ひと息つきながらどうぞ。途中で休んでも、ページと見出しから戻ってこられるようにしています。読みながら開発者ツールを一度開いてみると、文章の中の言葉が、実際に動く仕組みへ少し近づくと思います。

なぜ、貼るだけで動くiframeを手放したのか

見えているのに触れない――同一オリジンの境界

以前の共有欄はiframeでした。「用意されたコードを貼れば動く」。難しい設定をしなくても使えるのは、やっぱり助かりますよね。別のページを小さな枠に入れて表示する仕組みで、貼るだけで使えるのは便利ですよね。ただ、自分のページに見えていても、枠の中は別の文書です。別のドメインで描かれるボタンのフォントや余白は、こちらのCSSで自由には揃えられません。見た目だけではなく、クリックにも同じように気を付けたいところがあります。枠の中で押されたクリックは、親ページで普段どおり監視するだけでは直接取得できないんです。

以前の公式ウィジェット風の表示(再現図)
以前のFacebook・X・LINEのブランド色を使った3つの共有ボタン(説明用の再現図)
以前の青・黒・緑のボタンから、現在はサイト全体の色に馴染む丸型の共有欄へ変更しました。

オリジンは、スキーム・ホスト・ポートの組み合わせです。異なるオリジンのiframeを親ページから自由に読み書きできないのは、埋め込み先の個人情報などを守るためのブラウザの制約です。通常のクリックイベントも、別文書の中から親ページへそのまま伝わるわけではありません。したがって、外側でクリックを監視するだけでは、内部の共有操作を正確に捉えられません。

ここで見分けたいのは、クリックの伝播と、別文書へのアクセス権限です。同じ文書の中で親へ知らせる仕組みがあっても、iframeの中は別の文書です。外側へ伝えたい情報があるなら、その文書との連絡方法を別に用意します。管理できない埋め込みの内部を、親ページの設定だけで自由に変えられるとは考えません。

ちなみに、iframeと一切やり取りできないわけではありません。双方が対応していれば、postMessageで情報を交換できます。その場合は送信先のオリジンを指定し、受信側で送信元とデータの形を確認します。今回は、各社の埋め込みの中へこちらの処理を追加できるわけではないので、自分たちで管理できる共有リンクとイベントへ置き換えました。欲しいのは何か、どこまで自分たちで持つのか。このあたりが、最初の設計判断になりました。 設計判断:自分たちで管理する境界を決める

iframeで困ったのは、見た目より操作を観測する境界が文書ごとに分かれていることでした。親ページのイベント委譲でクリックを拾える、という前提が通りません。CSSの調整と計測を同じ管理範囲に置きたかったので、共有URLを自分たちで組み立てる方へ寄せました。ただし、外部SNSの投稿処理まで自作するわけではありません。こちらが持つのは共有画面へ渡す入口まで。その先の完了状態を観測できない点は、後の計測にも効いてきます。

オリジンの比較では、パスを見ません。たとえば https://promari.jp/https://promari.jp/blog/ は同じオリジンです。一方、https://developer.mozilla.org/ はホストが違うので別オリジン。スキームや実効ポートの違いも判定に関わります。リダイレクトを挟む構成なら、HTMLへ書いたURLだけでなく、最終的にどのオリジンの文書を読み込んだかまで確認します。

ここでCORSを足せば解決するかというと、話は別です。CORSで扱うのは、主に別オリジンから取得した応答をスクリプトへ公開する条件。iframeのDOMへアクセスする権限や、別文書のクリックイベントを親へ伝える仕組みにはなりません。通信の応答を読めることと、文書の内部を操作できることは別の契約です。この二つが混ざると、ヘッダーを変えても直らない問題を延々と追うことになります。

postMessageを使う場合も、「メッセージが届いたから信用する」では足りません。知らない相手から「保存に成功しました」と言われても、すぐ画面を書き換えたくはないですよね。送信元のoriginを確認し、必要なら送信元のwindowも照合し、データの項目と型を検証します。今回の共有欄では、その連携を各社のウィジェットへ求めるより、自分たちで説明できる小さな操作へ寄せることを選びました。

境界を検証するなら、同一オリジンと別オリジンのiframeを用意し、DOM参照・クリックの伝播・postMessageを別々に試します。CORSヘッダーを変更したケースも並べると、どの許可が何に効くかを切り分けられます。ウィジェットの挙動を推測するより、小さく再現したほうが早いことも多いんですよね。今回はその連携自体を減らせる構成を選びましたが、埋め込みを残す案件なら、この検証結果が設計の出発点になります。

iframeとCORSの違いは、MDNの同一オリジンポリシーでも確認できます。

ボタン一つの裏で、何を読み込んでいる?

ボタンの見た目を眺めたところで、次はブラウザの裏側へ。ページを開いたとき、何を読み込んでいるのでしょうか。今回の実装では、ボタンをプラグイン自身が描き、利用者が選んだときに各SNSの共有用URLを開きます。ボタンの表示にSNS各社のSDKを読み込む必要はありません。ただし、プラグイン本体の取得にはCDNへの通信があり、共有操作を選べば共有先への通信が発生します。

SDKを使うかどうかは、転送サイズだけでは決められません。解析・実行のコストに加えて、どのイベントを公開してくれるか、同意前の通信を制御できるか、障害時にホスト側へ何が伝わるかも見ます。今回欲しいのは記事URLを共有画面へ渡す入口だったので、ウィジェット一式を読み込む理由は薄くなりました。公式SDKを外せば何でも軽くなる、という話ではありません。必要な契約に対して、引き受ける依存が見合うかで判断しています。

共有リンクなら、記事URLやタイトルをクエリへ入れ、読者の操作で共有先を開けます。ロゴをインラインSVGとして持てば、ロゴごとに画像を取りに行く必要も減らせます。一方でSVGもHTMLへ埋め込むコードです。公開ソースで管理した定義を使うことと、不特定の入力をそのままSVGとして挿入することは区別する必要があります。外部SDKを減らしたから入力検証が不要になる、ということではありません。

開発者ツールのNetworkを開くと、この話を自分の目で確かめられます。まずページを読み込んだ直後の通信を見て、次に共有ボタンを押す。二つを混ぜずに観察するのがコツです。最初から取得するJavaScriptと、操作した後に開く共有先では、読者が負担するタイミングも目的も違います。

さらに、一つのファイルの転送量だけで判断しないようにします。小さなJavaScriptでも、実行すると別のファイルをたくさん要求する場合がありますし、キャッシュが効いていれば、二度目の転送は軽くなる場合もあります。逆に転送が軽くても、解析や実行に時間がかかれば画面の反応は鈍くなります。「何KB減ったか」と「読者の操作が楽になったか」は、別々に確かめたいことです。

SDKという名前だけで、重い・危ないと決め付ける必要はありません。決済やログインのように、公式の仕組みに任せる価値が大きい機能もあります。今回の入口は「記事のURLを共有画面へ渡したい」でした。やりたいことを短く言えるようにしてから、それに見合う道具を選ぶ。道具選びの順番を入れ替えるだけで、作るものも、保守するものも見通しやすくなります。

インラインSVGについても同じです。画像の輪郭をコードで持つので、拡大しても形が滑らかで、色も揃えやすい。しかし、便利だから外から届いたSVGをそのまま挿入してよいわけではありません。管理したアイコンを使うことと、利用者の入力を描画することは違う仕事です。「通信を減らす工夫」と「入力を信用する判断」を一つにしないようにします。

考えてみる:SDKを外したら、外部通信はゼロになる?

ゼロにはなりません。今回も部品の取得でCDNへ接続し、読者が共有先を選べばそのサービスへ通信します。減らしたのは、ボタンを表示するために各社のSDKへ依存する範囲です。ネットワーク欄を見るなら、ページを開いた直後と、共有ボタンを押した後を分けると、どの操作が通信を起こしたのかを追いやすくなります。

自作すると、自由と一緒に何を引き受けるのか

CSPは、スクリプト・画像・接続先などをブラウザへ許可する仕組みです。許可先が減れば構成を把握しやすくなりますが、CDNから部品を取得する以上、その配信先を許可する必要は残ります。いいねを保存するAPIへの通信も別に扱います。実際に必要な経路を確認し、動かすためだけに無制限の許可へ広げないことが大切です。

もちろん、自作したら何でも解決、というお話ではございません。共有先のURLが変わったときの対応も、端末ごとの違いも、アクセシビリティも、自分たちで見ていく必要があります。公式ウィジェットには、その保守を任せられる良さがあります。今回は必要な共有機能に範囲を絞り、契約を小さくしてテストできる形を選びました。自分のサイトで採用するときも、得られる自由と、引き受ける手間を一緒に考えてみてください。

CSPを読むと、script-srcconnect-src といった名前が出てきます。前者はスクリプトの取得・実行に関わる許可、後者はfetchなどから接続する先の許可です。同じ外部サービスでも、どの種類の通信として使っているかで、確認する場所が変わります。動かなかったからとりあえず全部許可する、では、せっかく決めた境界が消えてしまいます。

CSPは、許可先の数より実際の依存とポリシーが対応しているかが大事です。JavaScriptの取得が通っても、いいねAPIがconnect-srcで止まれば、表示だけ成功することもあります。まず違反したディレクティブと通信先を確認し、必要な範囲だけ修正する。動かすためにワイルドカードを広げてしまうと、後で何を許可したのか追えなくなります。エスケープやAPIの認可は別の層で必要なので、CSPへ全部任せる構成にもしません。

自作する判断にも、同じく条件があります。共有先が少なく、求める見た目や計測の意味が明確なら、小さな部品として管理する良さがあります。逆に、サービス独自の複雑な機能まで再現したくなったら、保守の範囲が急に広がります。最初の目的を書いておくと、「ここから先は今回の仕事ではない」と立ち止まれます。できることを増やすより、引き受ける範囲を決めるほうが難しいこともありますね。

iframeを外すと自由にはなりますが、そのぶん互換性や操作性は自分たちの担当になります。自由にできることが増えるのは嬉しいのですが、そのぶん面倒を見るところも増えるんですね。そこまで引き受けられるかを、最初に考えました。私の場合は、共有の入口だけを小さく持ち、外部SNSの仕事には踏み込まない範囲なら管理できると考えました。それでは次に、この分担をどうコードへ落としたのかをご紹介します。どんなフォルダーに入っているかだけでなく、どの処理が何を頼りに動くのかにも注目してみてください。

COMMENTS
コメント

このページ(1ページ目)の感想・質問・設計へのコメント

PASS IT ON

この気づきを、誰かにも。
THANK YOU FOR READING.すべての記事へ ↗

FROM INSIGHT TO IMPACT

「できたらいいな」を、
動く仕組みに。

記事で見つけたヒントを、あなたの事業へ。
新しいサービスも、手間のかかる業務も。
いまの課題から、つくるべきものを一緒に考えます。

開発・AI・研修の実績を見る

まだ、仕様書はいりません。

「何から始める?」から、ご一緒に。

課題がまとまっていなくても大丈夫。テーマを選ぶと相談文をご用意します。
連絡先の必須入力は、お名前とメールアドレスだけ。

まずは課題の整理から相談する

ご相談後の流れ

  1. 01 内容を確認
  2. 02 メールでご連絡
  3. 03 課題・進め方をご相談
送信だけで契約やお申込みが確定することはありません。