Rails: カスタムTurbo Streamでブラウザタブのファビコンをリアルタイム更新する(翻訳)
前回の記事では、RailsでTurbo Streamのカスタムアクションを用いてブラウザタブのカウンタを更新する方法について説明しました。この機能は、タブを見ている限りは問題なく動いていそうですが、同様のタブが多数ある場合や、タブが1個だけピン留めされていてファビコンしか表示されていない場合はどうすればいいのでしょうか?
本記事では、前回のソリューションを拡張して、ファビコン自身にバッジを追加します。方法もAPIがクリーンな点も前回と同様で、ターゲットが違うだけです。
コードはいつものようにGitHubリポジトリでご覧いただけます。
実装後の動作は以下のようになります。
🔗 ファビコン更新用アクションを作成する
ファビコン画像の更新方法は、ブラウザタブのタイトルのカウンタを更新するときと同じパターンです。ビューに、メッセージのカウントに応じた初期のファビコン画像を設定します。
<% content_for :title, @count.positive? ? "#{@count} - Messages" : "Messages" %>
+<% content_for :favicon, @count.positive? ? "./icon-unread.svg" : "./icon.svg" %>
<%= turbo_stream_from :messages %>
同じものを、レイアウトのファビコン設定用リンクにも設定します。
<link rel="icon" href="/icon.png" type="image/png">
-<link rel="icon" href="/icon.svg" type="image/svg+xml">
+<link rel="icon" href="<%= yield(:favicon) || "/icon.svg" %>" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png">
Turbo Streamのレスポンスでは、タイトルのカウンタ表示の隣りで新しいカスタムアクションを呼び出します。
<%= turbo_stream.prepend "messages", partial: @message %>
<%= turbo_stream.set_title_counter @count %>
+
+<%= turbo_stream.update_favicon @count %>
削除の場合も同様です。
<%= turbo_stream.remove @message %>
<%= turbo_stream.set_title_counter @count %>
+
+<%= turbo_stream.update_favicon @count %>
update_faviconの呼び出し方が、前回記事のset_title_counterアクションの呼び出し方と似ていることにご注目ください。こちらもクリーンなAPIです。
続いて、カスタムのTurbo Streamタグで使うヘルパーメソッドも作成しましょう。
module TurboStreamActionsHelper
def set_title_counter(count, divider: nil)
turbo_stream_action_tag :set_title_counter, count: count, divider: divider
end
+
+ def update_favicon(count)
+ turbo_stream_action_tag :update_favicon, count: count
+ end
end
Turbo::Streams::TagBuilder.prepend(TurboStreamActionsHelper)
turbo_stream_action_tagメソッドは、アクション名と属性を含んだHTMLタグを生成します。ここでは以下のようなタグが生成されます。
<turbo-stream action="update_favicon" count="5">
<template></template>
</turbo-stream>
Turboはこのタグを見つけると、Turbo.StreamActionsのupdate_favicon関数を探索して実行します。このupdate_favicon関数は以下のようにシンプルです。
// app/javascript/turbo_stream_actions/update_favicon.js
export default function() {
const count = this.getAttribute("count") || 0
const faviconLink = document.querySelector("link[rel*='icon']")
const iconPath = count > 0 ? "./icon-unread.svg" : "./icon.svg"
faviconLink.href = iconPath
}
この関数は、Turbo Streamタグからcount属性を読み取り、ドキュメント内のファビコンリンク要素を探索して、リンクのhrefを未読アイコン、またはカウントに応じたデフォルトのアイコンに更新します。
ロジックはいたってシンプルで、メッセージが未読の場合は未読のバッジアイコンを表示し、それ以外の場合はデフォルトのアイコンを表示するだけです。後はブラウザがよしなにやってくれます。
それでは、この関数をTurboで登録しましょう。
import { Turbo } from "@hotwired/turbo-rails"
import set_title_counter from "turbo_stream_actions/set_title_counter"
+import update_favicon from "turbo_stream_actions/update_favicon"
Turbo.StreamActions.set_title_counter = set_title_counter
+Turbo.StreamActions.update_favicon = update_favicon
これで新しいカスタムアクションが接続されて動くようになりました。メッセージを作成または削除すると、ファビコン画像が自動更新されます。これは、個別のユーザーによる直接操作の場合にも、他のユーザーからのブロードキャストの場合にも期待通り動きます。
ファビコン自体は単なるRailsのファビコンやアイコンですが、未読を表す青いバッジを追加しています。見た目はぱっとしませんが、目的は達成できます。
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg">
<circle cx="256" cy="256" r="256" fill="red"/>
<circle cx="382" cy="130" r="130" fill="blue"/>
</svg>
もちろん、このあたりは自分たちのファビコンやアイコンに合わせて適宜変更してください。普通はバッジにもっと明るい色を使うのが最適です。
まとめ: このファビコン更新機能は、前回の記事で扱ったブラウザタブのタイトルカウンタを更新する機能を補完するものです。通知音やデスクトップ通知など、必要なブラウザAPIを好きなだけ追加できます。どれも同じパターンでやれますよ❤️。
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。