PROMARI JOURNAL

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

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

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

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

PASS IT ON

ひとつの発見を、次の会話へ。

PHPとJavaScript、同じ定義を二度直しますか

TOMLは設定を表す形式です。配列や節を使ってサービスの並び、表示、設置場所、補助チャネル、計測の設定をまとめます。共有先ごとのデータファイルからロゴやURLの情報を読み込み、ブラウザ側の生成物へ渡すことで、PHPとTypeScriptの両方で、同じ定義を手作業で直す量を減らしています。片方だけ直して、もう片方を忘れる。そんな行き違いを減らしたいわけです。生成で共有するのはサービスの定義で、URLの符号化や文面の組み立てはブラウザ側が担います。PHPのrawurlencodeと同じ符号化規則も、その処理の中で扱います。組み立て処理を一か所に置くことで、PHPとTypeScriptへ同じ処理を二重に書くことを避けています。生成処理もプログラムなので、データの書き方が決まりから外れれば、その時点で止まります。生成できたことと、正しく生成されたことは別に検証します。

設定を直したら、次は書き出しです。–checkで正本と生成物が一致するかを確かめます。生成物だけを直すと、次の生成で元に戻ってしまいます。反対に正本だけを直すと、読者へ届くコードは古いまま。どちらも画面を眺めるだけでは見落としそうですよね。そこで、レビューでは両方の差分を見ます。自動生成に任せる部分と、私たちが確かめる部分を決めておくわけです。

設定をTOMLへ寄せたのは、PHPとJavaScriptで共有先の順番が食い違うのを避けたかったからです。実行環境ごとに必要な形式は違っても、変更の入口は一つにする。生成物も配布に必要ですが、手では直さない。生成スクリプトの変更と設定の変更を同じ差分で追えるようにしておくと、どこから差が生まれたかを確認できます。 設計判断:設定の正本を一つにする「設定は合っているのに画面が古い」というときも、正本・生成・配信のどこで止まったかを順に見ればよくなります。

設定生成はビルド時、生成物の利用は実行時です。ここを分けたぶん、読者のブラウザへデータファイルを解析する処理は持ち込みません。一方で、正本だけ更新して生成を忘れると、正常に動く古いコードが配られます。クラッシュしないぶん見落としやすいんですよね。だから生成スクリプトの成功だけではなく、再生成した結果とコミット済みの成果物の一致まで確認します。--checkは、その取り違えをレビュー前に見つけるための入口です。

図3.5-1 読者のブラウザへ、完成した部品を届ける
図3.5-1 読者のブラウザへ、完成した部品を届ける。現在のしくみ。読者のブラウザでデータファイルを解析するわけではありません。図3.5-1 読者のブラウザへ、完成した部品を届ける。現在のしくみ。読者のブラウザでデータファイルを解析するわけではありません。

設定にも型があります。trueと文字列の”true”、数値と文字列、配列と単一の値は同じではありません。知らないキーを黙って無視すると、書いた人は効いたつもりになりがちです。生成時に許可する項目と値の範囲を確かめることは、ブラウザで試す前に誤りを見つける入口になります。エラーメッセージも「設定が変」だけでなく、どの項目が期待と違うか分かると修正しやすいですね。

ちなみに、設定をいくつか重ねて使うなら、どちらを優先するかも決めておきたいところです。配列を丸ごと置き換えるのか、項目ごとに混ぜるのか。Deep Mergeという言葉を使っていても、その規則は実装ごとに違い得ます。とくに共有先の順番は意味を持つので、勝手に足し合わせてよいとは限りません。同じ入力から同じ出力ができるか、二回生成して差分が出ないかも確かめます。

この部品では、図3.5-2のように3段階で重ねます。①部品の既定値、②destinationsなどのHTML属性、③config属性に書いたJSONの順に読み、同じ項目は後のもので上書きします。入れ子の設定は項目ごとに混ぜますが、destinationsのような配列は混ぜずに丸ごと置き換えます。そのため図の例では、既定のX、属性のX・LINEを経て、最後のJSONのFacebookだけが残ります。configのJSONが壊れていて読めないときは、コンソールにエラーを出し、②までの値で表示を続けます。

図3.5-2 同じ項目は、後の設定で上書きされる
図3.5-2 同じ項目は、後の設定で上書きされる。現在の優先順位。この例の最終結果はFacebookだけ。XやLINEを足し合わせません。図3.5-2 同じ項目は、後の設定で上書きされる。現在の優先順位。この例の最終結果はFacebookだけ。XやLINEを足し合わせません。

実装の根拠:設定の優先順位と解析失敗時の処理

設定が壊れた朝、画面はどう振る舞うべきか

fail-closedを考えるときは、何を止めるかまで決めます。設定の検証が失敗したら生成を止める、APIで認可できなければ変更を拒否する。一方、共有機能に問題があっても記事本文は読めるようにする。ページに書いた共有先の名前を打ち間違えたときも、その名前だけを列から外してコンソールへ知らせ、正しく書いた共有先のボタンは表示を続けます。すべてを同じエラー処理へ寄せるより、守る条件ごとに、失敗の影響を閉じるほうが扱いやすいんです。

設定のコメントについても、触れておきます。半年後に article_top = false だけを見たら、「冒頭には出したくないのかな」と思いませんか。ところが今回の理由は逆で、冒頭にはテーマから出すので、自動挿入だけ止めたいのです。同じfalseでも、理由を知らないと受け取り方が変わってしまいますよね。私は、この判断をコメントに残しておきたいと思っています。使える設定と、このサイトで選んだ設定を分けておけば、他のサイトへ持っていくときにも考え直せます。

いいねの初期取得にも、失敗への備えが要ります。初期化中は操作を無効にしますが、取得に失敗した場合、接続コードは件数を0にせず未取得の「—」のまま残し、エラー案内を出して、ボタンから再試行できる状態に戻します。再操作では取得を先に試します。以前は0の仮表示をしていましたが、その0はサーバーで確定した0件と見分けが付きません。そこで「—」に変えました。部品のsetLikeState()へcount: nullを渡すと、件数の欄が「—」になります。

図3.5.1-1 「分からない」と「0件」は違う
図3.5.1-1 「分からない」と「0件」は違う。以前と現在の比較。サーバーから0件と返った場合だけが、「確定した0件」です。図3.5.1-1 「分からない」と「0件」は違う。以前と現在の比較。サーバーから0件と返った場合だけが、「確定した0件」です。

これは、失敗を全部無視することとも違います。画面では本文を読めるようにし、運用側では共有部品が読み込めなかったことを調べられるようにする。利用者向けの案内と、開発者が原因を探す情報では、必要な粒度が違います。エラーの詳細をそのまま画面へ大量に出すより、読者には次にできる行動を伝え、調査の情報は適した場所へ残します。

コメントの役割も、ここで効いてきます。false を「無効」と説明するだけなら、コードから読めます。でも「テーマが冒頭へ置くため、自動挿入は無効」と残せば、後の人は理由を保ったまま変更できます。残したいのは、コードの日本語訳より、その判断を変えてよい条件です。

少し先のことも想像してみましょう。半年後、この設定を見直すとしたら、何がきっかけになるでしょうか。テーマからの呼び出しをやめた、固定バーを導入した、記事の種類が増えた。そうした条件を考えておくと、設定が長く残っても「昔からこうだから」で維持せずに済みます。設定は一度決めて終わりではなく、運用の判断を記録する場所でもあります。

いいねを1回押したのに、数字が揃わない?

「押した」と「保存できた」を分けて考える

さて、次はハートのボタンです。押すと数字が一つ増える、見慣れた「いいね」ですね。ただ、画面で数字を増やすことと、その結果を保存できることは別なんです。まず、いいねを使うときは like 属性を付けます。ただし、属性を付けただけでは保存先は作られません。部品の初期状態は操作無効で、サイト側がsetLikeState()へ渡すbusyで操作可否を制御します。初期取得に失敗した後の再試行も、接続コード側の担当です。操作すると promari-sns-share-like イベントが発火し、detail.liked に希望する状態が入ります。保存後に setLikeState() へ確定値を返すのが連携のインターフェースです。件数が分からないあいだはcount: nullを渡すと、件数の欄は「—」になります。

JavaScriptいいねの連携API(初期状態と確定状態の反映例)
await customElements.whenDefined('promari-sns-share');
const element = document.querySelector('promari-sns-share[like]');

// サイトの保存APIから取得した値を渡す。以下の値は説明用。
element.setLikeState({ liked: false, count: 0, busy: false });

// 保存要求はこのイベントで受け取る。
element.addEventListener('promari-sns-share-like', (event) => {
  const requested = event.detail.liked;
  // サイト側で requested を保存し、成功・失敗の結果を setLikeState() に返す。
  // この抜粋はAPIのインターフェースを示すもので、保存処理そのものは含まない。
});

当サイトでは、共有部品の外側にあるWordPress側のAPIが保存を担当します。現在の実装は、ログイン中ならサーバーが確定した会員ID、ゲストなら署名付きCookieに基づく匿名識別子と記事IDを使います。クライアントから送られたユーザーIDで所有者を決めることはしません。冒頭と末尾は同じ確定値を表示し、再読み込みでも取り直します。会員とゲストで識別の範囲が違うため、件数をそのまま実人数とは読み替えないことも、表示と運用の約束に含めています。

さて、ここからは「いいね」の中を見ていきます。たとえば冒頭で押したら「1」になったのに、読み終えたところでは「0」のまま。そんな画面では、もう一度押したくなりますよね。これは今回の設計で避けたい場面の一つです。操作前の状態、読者が希望した状態、サーバーが保存した確定状態を分けて扱います。単に数字を1増やすのではなく、保存先へ希望するlikedの状態を渡し、返ってきた確定値を表示する。先に画面を変える場合も、失敗したら確定状態へ戻す約束が必要になります。

同じ記事に冒頭と末尾の2つの要素がある場合、それぞれが独立して件数を増減させると表示が食い違います。同じ確定値を両方へ渡し、保存中の操作も調整します。通信失敗を0件と解釈して上書きしないこと、遅れて返った古い応答で新しい状態を戻さないことも、非同期処理を設計するときに検討する条件です。連載では成功する一本道だけでなく、応答順序と再試行も分けて扱います。

図4.1-1 保存先の返事を、記事の上下へ同じように返す
図4.1-1 保存先の返事を、記事の上下へ同じように返す。現在のしくみ・数値は説明例。上と下が別々に「1を足す」のではなく、同じ確定値を表示します。図4.1-1 保存先の返事を、記事の上下へ同じように返す。現在のしくみ・数値は説明例。上と下が別々に「1を足す」のではなく、同じ確定値を表示します。

非同期処理で厄介なのは、要求順と応答順が一致しないことです。trueを保存した後にfalseを保存しても、最初の応答が遅れて届く可能性があります。ボタンを無効化するだけで、別タブや別端末の操作まで直列化できるわけではありません。今回のUIで調整する範囲と、保存側で守る範囲を分けて考えます。連載では、リクエストの世代管理で古い応答を捨てる方法と、サーバー上の更新順序をどう扱うかを別々に掘り下げます。

図4.1-2 最後に届いた返事が、新しいとは限らない
図4.1-2 最後に届いた返事が、新しいとは限らない。非同期処理の問題例。返事を採用する前に、「どのお願いへの返事か」を確かめる案が必要です。図4.1-2 最後に届いた返事が、新しいとは限らない。非同期処理の問題例。返事を採用する前に、「どのお願いへの返事か」を確かめる案が必要です。

状態をいくつかのbooleanで持つと、loading=trueなのに操作可能、といった組み合わせが生まれます。連載では、現状の実装を起点に状態遷移を整理します。未取得を確定件数の0と別表示にする(「—」として実装済み)、保存中なら同じ操作欄の再送を抑える、失敗なら直前の確定状態を維持する。判定をイベントハンドラーごとに書くより、許す遷移を明確にしたほうがテストしやすいんです。型で不正な組み合わせを減らす案も、ここで検討できます。

図4.1-3 待っている理由で、状態を分ける
図4.1-3 待っている理由で、状態を分ける。「—」は実装済み・状態の型は検討案。取得失敗では「未取得のまま」。保存失敗では「直前の確定値へ戻る」。図4.1-3 待っている理由で、状態を分ける。「—」は実装済み・状態の型は検討案。取得失敗では「未取得のまま」。保存失敗では「直前の確定値へ戻る」。

楽観的更新は、返事を待つ前に画面へ希望する状態を見せる方法です。反応が速く感じられる一方で、失敗したときに戻す処理や、古い応答をどう扱うかが必要になります。サーバーの確定後に表示する方法にも、待ち時間を伝える工夫が要ります。どちらが常に正しいというより、操作の性質と、失敗した場合の負担に合わせて選びます。

記事の上では3件なのに、下まで読んだら2件。これでは、どちらが正しいのか迷ってしまいますよね。今回のように冒頭と末尾へ置くなら、件数をそれぞれ別に決めないようにします。サーバーが返した同じ状態を両方へ配る。保存中の操作も記事単位で調整する。画面の下までスクロールしたとき、初めて食い違いに気付くようでは困りますよね。テストでは、片方を操作してもう片方を見るだけでなく、再読み込みした後にも同じ結果を取得できるか確かめます。

同じリクエストが二度届いても、1件にできるか

ここで少し、「同じ人のいいね」をどう見分けるのかにも触れておきましょう。その目印がいつまで残るかによって、件数の意味も変わってきます。会員IDなら同じアカウントとして別端末から扱えますが、ゲストCookieを消せば同じ人だとは判断できません。ログインしたからといって、過去の匿名履歴を全部その会員へ推測で結び付けるのも避けています。現在の連携では、明示的ないいね操作で同じブラウザの票を整理する場面と、過去の操作履歴を保持する場面を分けています。現在の票と、誰として操作した履歴かは、同じデータではないんです。

冪等性は、同じ要求を繰り返しても結果が余分に変化しない性質です。同じブラウザーと記事に対してliked=trueを再送しても、同じ1件として保存される必要があります。同時要求に対しては保存側の一意性や更新方法も関係し、フロントのボタンを無効にするだけでは保証できません。入力の型、許可する記事、トークン、保存、応答、キャッシュまでをAPIの一つのインターフェースとして考えます。

APIへ送るのは「反転して」ではなくliked=trueやliked=falseという希望状態です。応答を受け取れず再送しても、同じ状態へ落ち着くインターフェースにしたいからです。ただし、この形にしただけでは競合は消えません。存在確認と追加の間へ別の要求が入れば、実装次第で重複します。リクエストの意味と、保存処理の排他制御を揃えて初めて再送に耐えられる。ここはフロントだけ眺めていても分からないところです。

図4.1.1-1 「1増やす」より「付けた状態にする」
図4.1.1-1 「1増やす」より「付けた状態にする」。同じ要求を2回送る説明例。状態が変わらない再送では、票も追加の履歴も増やしません。図4.1.1-1 「1増やす」より「付けた状態にする」。同じ要求を2回送る説明例。状態が変わらない再送では、票も追加の履歴も増やしません。

対象:当サイト固有の実装(状態に基づく保存と履歴)。公開プラグインの担当範囲とは区別しています。

現在の保存処理では、記事と所有者から作るキーを基準にロックを取り、状態の変更と操作履歴の追加をトランザクションでまとめています。同じ希望状態の再送では、操作履歴も増やしません。ゲストの票を会員の票へ整理する場合も、単純に足して二票へしない。もちろん、ロックには待ち時間や競合時の失敗があるので、取れなかったときまで成功扱いにはしません。このロックが揃えるのは同じ保存キーへの処理で、ゲストと会員のようにキーが変わる要求まで一括で直列化するものではありません。トランザクションが効くストレージエンジンを使うことも前提です。連載では、この境界をまたぐ競合も検証対象にします。

図4.1.1-2 同じ対象への保存は、順番に・ひとまとめに
図4.1.1-2 同じ対象への保存は、順番に・ひとまとめに。現在の主な保存経路。ロックは順番のため。トランザクションは、票と履歴をまとめるためです。図4.1.1-2 同じ対象への保存は、順番に・ひとまとめに。現在の主な保存経路。ロックは順番のため。トランザクションは、票と履歴をまとめるためです。

対象:当サイト固有の実装(ロックとトランザクション)。公開プラグインの担当範囲とは区別しています。

Cookieの属性は、目的を分けて見ます。HttpOnlyはJavaScriptからの直接読み取りを制限し、SecureはHTTPSでの送信、SameSiteは別サイトからの要求にCookieを添える条件に関わります。これらを付けても、所有者の識別と認可が自動で完成するわけではありません。署名の検証、リクエスト元の確認、対象記事の公開条件、レート制限はそれぞれ別の担当です。Cookieを持っていることだけで任意の記事への操作を許さないようにします。

保存で一つにしたいのは、「票が変わった」という事実と、その操作履歴です。途中で失敗して票だけ増えたり、履歴だけ残ったりすると、後の集計が説明できません。同じ所有者と記事を更新する要求の順番をそろえ、関連する変更を一緒に確定し、失敗したら一緒に取り消す。その境界までが保存の設計です。

WordPressのnonceは使い捨ての認証証明ではなく、一定期間内のリクエスト検証に使う値です。再送防止や認可の代わりにはなりません。現在の共通APIでは、署名Cookieの読者トークンをnonceのactionへ含め、ログイン状態はWordPress側で確認します。Origin、対象記事の公開状態、入力の型、停止中アカウントかどうかも別々に検査します。トークンの検証が通ることと、その操作を許してよいことを混同しないようにします。

図4.1.1-3 「誰のいいねか」は、サーバーが確かめる
図4.1.1-3 「誰のいいねか」は、サーバーが確かめる。現在の所有者の決め方。端末から好きなIDを送っても、それだけで他人の票は変更できません。図4.1.1-3 「誰のいいねか」は、サーバーが確かめる。現在の所有者の決め方。端末から好きなIDを送っても、それだけで他人の票は変更できません。
考えてみる:タイムアウトしたら、もう一度「+1」でよい?

タイムアウトだけでは、保存に失敗したかどうか分かりません。サーバーでは保存できていて、応答だけが届かなかった可能性もあります。そのまま「+1」を再送すれば2件になるかもしれません。同じブラウザー・同じ記事の「liked=true」を何度受けても同じ結果にする設計が、ここで効きます。これが冪等性を必要とする具体的な理由です。 設計ポイント:再送しても増えすぎない保存

読み取りと変更は、APIの入口でも分けています。GETで状態を取得しただけでは、過去の匿名票を会員へ付け替えません。変更はPOSTで明示してもらう。返すデータはlikedと件数などの確定状態で、DOMの操作方法は含めません。これなら保存先を変えるときも、接続コードが同じインターフェースへ変換すれば表示部品を保てます。HTTPメソッドを分けるだけでなく、読み取りで何を変更しないかまで決めておくのがポイントです。

HTTPステータスは、その返事をどう受け取るかの手掛かりです。存在しない記事、許可できない要求、サーバー側の障害は、同じ「0件」ではありません。fetchは、HTTPのエラー応答でもPromise自体が解決する場合があるため、通信が返ったことと正常な状態コードだったことを分けて確認します。さらにJSONとして読めるか、項目がインターフェースの定めどおりかを確かめます。

キャッシュにも気を配ります。全員に同じ記事本文を配るキャッシュと、ブラウザーごとにlikedが違う応答では条件が異なります。ある方の状態を他の方へ返してしまえば、件数が合っていても誤った表示です。Cache-Controlなどの応答ヘッダーを見て、途中の仕組みが何を保存してよいかを決めます。速くする工夫は、誰にとって同じデータなのかを確認してから加えたいですね。

追跡用の記録では、現在のいいね件数と操作回数を分けています。付ける・取り消すを繰り返せば、現在の票が0でも操作履歴は残ります。コメントも記事IDと会員ID、または匿名識別子に結び付きます。管理者の集計ではその違いを確認できるようにし、公開画面へIPなどを出すことはしません。履歴を持てば何でも分析できるというわけではなく、過去に取っていなかった所有者を後から復元することもできません。この限界は残したまま扱います。

再送のテストでは、同じ所有者と記事にtrueを二回送り、票も履歴も一回分で止まるかを確認します。次にfalseを二回送り、取消が一回だけ残るかを見る。会員の別セッション、ゲストからの切り替え、他人のユーザーIDを送った場合も並べます。数字だけ一致していても、所有者や履歴がずれていたら集計は信用できません。テストデータはローカルに閉じ、本番の読者の反応へ混ぜない形で確かめています。

WordPressのnonceが保証する範囲は、公式のnonce解説を、fetchのHTTP応答の扱いはMDNのFetchガイドを確認できます。

その「成功」は、どこまで成功したのか

コピーできた? 共有できた? ブラウザの返事を待つ

「コピーしました」と出たのに、貼り付けると前のURL。コピーできたと思っているだけに、これは困りますよね。小さな案内文ですが、いつ成功と判断するかが大事になってきます。それでは、ブラウザAPIからどんな返事が来たら、この案内を出せるのかを見ていきましょう。

「コピー」は記事URLをクリップボードへ渡します。「共有」は対応端末でWeb Share APIを使い、OSの共有メニューを開きます。Web Share APIがない端末では、丸型表示の「共有」から追加の共有先を開けます。「その他」はキーボードで開閉でき、Escapeで閉じられます。スマートフォンでは配置とメニューの位置を調整し、画面からはみ出さないようにしています。

コピーのボタンも、押せば必ず成功するとは限りません。Clipboard APIは非同期の操作で、Promiseを通して成功や失敗が返ります。呼び出せたことと、コピーできたことは別なんです。安全なコンテキスト、権限、ユーザー操作との関係など、ブラウザ側の条件によって拒否されることもあります。まだ結果が分からないうちに「コピーしました」と伝えると、貼り付け先で何も出ずに困りますよね。操作が終わってから、その結果を表示するように考えます。

スマートフォンで共有先が一覧になる、あの画面も使えます。Web Share APIは、URLなどを端末の共有画面へ渡す機能です。ただ、SNSへの投稿が完了したことまでは分かりません。使える機能かどうかを確かめ、ユーザーの操作から呼び出し、キャンセルと障害も必要に応じて分けます。どの端末にも同じ共有先が入っているわけではないので、通常の共有リンクやコピーも残しておきます。手元の端末で、どの選択肢が出るか見てみるのも面白いですよ。

返るPromiseの意味はMDNのNavigator.share()にもまとまっています。端末によって解決のタイミングが違う点まで含め、投稿完了の通知には使わないようにします。

Promiseが返ったこと、解決したこと、SNSへの投稿が完了したことは別です。「URLをコピー」の操作では、成功案内をawaitの後に出し、拒否された場合はURLを表示して手動コピーへ退避します。Composeではコピーの完了を待たずに投稿画面を開く処理を呼び、コピーの結果が分かってから成功・失敗を案内します。失敗しても手動コピーの入力窓は出しません。読者がすでに別のタブへ移っている可能性があるためです。一方、共有操作の計測イベントは失敗やキャンセルを区別せず通知します。現在のイベントを「共有成功」と読んではいけないわけです。成功・拒否・キャンセルを別の結果として公開する案は、互換性も含めて連載で検討します。

図5.1-1 「呼べた」「終わった」「投稿した」は違う時点
図5.1-1 「呼べた」「終わった」「投稿した」は違う時点。現在の実装。案内はawaitで結果を受け取った後に出します。端末の共有の解決は、投稿完了の知らせではありません。図5.1-1 「呼べた」「終わった」「投稿した」は違う時点。現在の実装。案内はawaitで結果を受け取った後に出します。端末の共有の解決は、投稿完了の知らせではありません。

Web Share APIの拒否は、catchで受け止めています。キャンセルと障害は分類していませんし、共有先での投稿結果も取得していません。分類を加えるなら、AbortErrorを必ず利用者のキャンセルだと断定しないことも必要です。共有先が存在しない場合などにも同じ例外が返ることがあります。エラー名をそのまま業務上の結果にせず、APIが保証している範囲で画面と計測を設計します。

図5.1-2 「押した」「コピーできた」「投稿した」は別
図5.1-2 「押した」「コピーできた」「投稿した」は別。現在の実装で分かること。現在の計測イベントを「SNSへの投稿成功」と数えないようにします。図5.1-2 「押した」「コピーできた」「投稿した」は別。現在の実装で分かること。現在の計測イベントを「SNSへの投稿成功」と数えないようにします。

実装の根拠:成功案内・拒否処理・操作通知

ユーザー操作との近さも重要です。ブラウザは、ページを開いただけで勝手に共有画面を出したり、無関係なタイミングでクリップボードを書き換えたりされないよう、機能ごとに条件を設けています。ボタンのクリックから長い別処理を挟むと、操作に基づく呼び出しとして認められなくなる場合もあります。「メソッドが存在するか」だけでなく、「いま呼べるか」があるわけです。

Progressive Enhancementは、利用できる機能に応じて体験を加えていく考え方です。共有APIが使える環境では便利な入口を出し、使えない環境にも通常のリンクやコピーなど別の道を残す。何もかも全端末で同じに見せることより、目的へたどり着けることを優先します。スマートフォンとPCで選択肢が違っても、それだけで不具合とは限りません。

共有のクリックは、SNSへの投稿完了ではない

さて、ボタンが動くようになると、どれくらい使われているのかも気になってきますよね。ただ、数字を見る前に「何を1回と数えているのか」を確かめておきましょう。ここを取り違えると、同じ数字でもまったく違う意味に読めてしまいます。

まず、数字が集まるまでの流れを追ってみます。共有ボタンが押されると、部品は「共有ボタンが押されました」という知らせを、promari-sns-shareという名前のCustomEventで部品の外、つまり記事のページへ出します。当サイトでは、その知らせを受け取った接続コードが、pm-analyticsという受付へ渡します。この受付を持っているのが、当サイトの計測用プラグインpromari-observabilityです。このプラグインが、読者がアクセス解析を許可しているかを確かめてから、Googleのアクセス解析サービスであるGA4へ送ります。まだ選んでいない間は送らずに待ち、断られたら捨てます。

ここで数えているのは「共有ボタンが押された回数」です。コピーがうまくいったか、SNSで実際に投稿されたかは数えていません。知らせには「成功した・失敗した・途中でやめた」を区別する項目がなく、端末の共有画面(Web Share)で読者が途中でやめた場合も、同じ知らせが届きます。いいねの件数とも別の数字です。なお、共有部品そのものはGA4へ直接送りません。送るかどうかを決めるのは、サイト側の計測の仕組みです。数字を読むときは、「押された回数を数えている」という範囲に立ち返るようにしています。

では、なぜ部品が自分から知らせを出すのでしょうか。共有ボタンはShadow DOMの中にあります。Shadow DOMは部品の中身を外から隠す仕組みなので、ページ側から「a[data-share]のリンクを探して、クリックを見張る」という方法が使えません。そこで部品は、ボタンが押されたら意味の分かる知らせとして外へ伝え、サイト側はその知らせを受け取ります。知らせが部品の外まで届くかは、どの要素から出したか(dispatch)と、bubbles・composedという設定で決まります。名前が同じというだけで届くとは限らないので、知らせの名前・中身(detail)・出した場所・意味をまとめて確かめます。

図5.2-1 内部のクリックを、外向けの連絡に言い換える
図5.2-1 内部のクリックを、外向けの連絡に言い換える。現在の通知経路。中のHTMLを変えても、通知の名前と意味が同じなら連携を保ちやすくなります。図5.2-1 内部のクリックを、外向けの連絡に言い換える。現在の通知経路。中のHTMLを変えても、通知の名前と意味が同じなら連携を保ちやすくなります。

実装の根拠:CustomEventShareActivityPublisherの送出元

アクセス解析を許可していない方にも、共有ボタンは使っていただきたいですよね。そこで、許可されているかどうかは計測側(promari-observability)だけが確かめ、送るかどうかを決めます。共有部品は、同意の状態を知らなくても動きます。こう分けておくと、表示する部品にGA4専用の処理を詰め込まずに済みます。集まった数字は「どこで、どのボタンが押されたか」を考える材料です。SNSへの投稿数や、記事を読んだ人数とは読み替えないようにしています。

知らせ(CustomEvent)は、部品の中のHTMLを外へ見せずに、「何が起きたか」だけを伝える手段です。名前だけでなく、detailにどんな値が入るか、いつ出るかも、サイト側との約束(インターフェース)になります。この約束を守っていれば、部品の中のボタンを作り替えても、計測側を直す必要はありません。ただし、1回の操作で知らせが2回出れば、計測も2回分になります。画面を描き直したあとに、受け取る処理を重ねて登録していないかなど、1回の操作で1回だけ知らせが出ることも確かめます。

bubblesは、知らせが親の要素へ順に伝わっていくかどうか、composedは、Shadow DOMの外まで出られるかどうかを決める設定です。ただ、どの要素から知らせを出したかでも、届く範囲は変わります。部品そのもの(ホスト要素)から出すのか、中のボタンから出すのかで違うからです。知らせの名前を見つけたら、出す場所と受け取る場所を線でつないでみると、流れがつかみやすくなります。

今回の送出元を具体的に追うと、共有の知らせは、部品そのもの(<promari-sns-share>要素)から出しています。出しているのはCustomEventShareActivityPublisherというクラスです。いいねの知らせも、同じく部品そのものから出します。中のボタンのクリックを、そのまま外へ見せているわけではありません。だから中のHTMLを作り替えても、知らせの名前と中身の意味さえ変えなければ、サイト側はこれまでどおり受け取れます。

経路の詳細は、DOM仕様のイベント送出とcomposedの解説、共有結果の限界はWeb Share仕様で確認できます。

計測の名前にも、みんなで同じ意味に読むための約束が要ります。たとえば「share_success(共有成功)」という名前を見たとき、営業の方は「SNSに投稿された回数」だと思い、作った人は「共有画面を開けた回数」のつもりでいるかもしれません。同じ数字を見ながら、別の話をしてしまうわけです。計測は、数を集める前に「何を一回と数えるか」を決める仕事です。成功・開始・途中でやめた、を分けて数えるなら、その決め方を、数字を使う人にも伝えます。

もう一つ、共有されたリンクから記事へ来た人の数は、ボタンが押された回数とは別の方法で数えます。共有するリンクの末尾にUTMという目印を付けておき、「どこから来た訪問か」を見分けるのです。この二つの数は、一致しなくて当たり前です。共有しても誰も開かないこともあれば、一つのリンクを何人もが開くこともあります。数字が合わないからおかしい、と決める前に、それぞれ何を数えているのかへ戻ってみる。地味ですが、大事な確認ですね。

図5.2-3 1回の共有操作から、訪問が3回来てもよい
図5.2-3 1回の共有操作から、訪問が3回来てもよい。数字は説明用・実測ではない。共有操作数と訪問数を、一致させることは検証条件にしません。図5.2-3 1回の共有操作から、訪問が3回来てもよい。数字は説明用・実測ではない。共有操作数と訪問数を、一致させることは検証条件にしません。
COMMENTS
コメント…

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

PASS IT ON

この気づきを、誰かにも。

この記事を書いた人

Takaomi Murasaki

Promari SNS Shareの開発を通して、Webの仕組みや設計の選び方を紹介しています。動く実装と、その判断に至るまでを記事に残しています。

公開記事 11 件

プロフィールを見る
THANK YOU FOR READING.すべての記事へ ↗

FROM INSIGHT TO IMPACT

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

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

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

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

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

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

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

ご相談後の流れ

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