Skip to content

記事末にいいね(拍手)を置く (#1949) - #3278

Merged
ma91n merged 25 commits into
mainfrom
likes-worker-1949
Sep 12, 2026
Merged

ma91n merged 25 commits into
mainfrom
likes-worker-1949

Conversation

@ma91n

@ma91n ma91n commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

#1949。記事末に「いいね」を置き、押された数を Cloudflare Worker + KV に記録します。

サイト初の JS ではありません#2565(記事末のリンクのコピー)が PR #2730 で公開済みで、social-button.ejs は既に navigator.clipboard を呼んでいます。これはその2つ目です。

段階 内容 状態
1 Worker + KV 済み
2 ブログ側の見せ方 済み
3 毎日のダンプ取り込みと本番デプロイ Cloudflare アカウント作成待ち

このままマージしても画面は変わりません。 themes/future/_config.ymllikes_endpoint が空で、lihidden のままです。段階3で Worker の URL を入れたときに出ます。

方針の変更(issue から)

issue の構成案は GET /count?path=... を読者のブラウザから叩く形でしたが、数の表示はビルド時に焼き込む形に変えました。記事ページの表示ごとに問い合わせると全記事ページに外部への通信が1本増えます。はてブ数も PV も update-cache.yml が毎日取って JSON にコミットしているので、いいねもそこへ乗せれば読み取りはゼロです。JS が走るのは叩いたときだけ。

代償は「他人のいいねは翌日反映」ですが、隣に並ぶはてブ数が同じ遅延なので数の新しさが揃います。

Worker(workers/likes/

  • POST /like/<記事ID>?n=<個数>1人3つまで。上限は Worker が持つ

  • POST /unlike/<記事ID> — その読者のぶんだけ引いて記録も消す

  • GET /dump — Bearer トークンで保護。バッチが毎日叩く

  • KV のキーは URL ではなく記事IDlike:20260804a)。alias で URL を付け替えても数が残る

  • 数は値と metadata の両方に持つ。 ダンプは list() の metadata から読むので記事数ぶんの get を撃たない(Workers 無料枠は1リクエスト50サブリクエストまで)

  • 生の IP は保存しない。 塩を足した SHA-256 を TTL 24時間で置く

  • 連打はまとめて1回送る(叩き終わってから 800ms)。1つずつ送ると KV の書き込みを3倍消費する(無料枠は1日1,000)

  • 連打と総当たりは Rate Limiting binding で止める。 KV を触らないので書き込み枠を消費しない

  • Durable Objects は使わない。 getput は競合しうるが、いいね数は1件の差が読者の判断を変える数字ではない

見せ方

共有ボタンと同じ行の左端に、56px の錠剤(丸のちょうど2倍)。器・ボタン・数字は共有ボタンと同じ部品(.share-item / .share-btn / .share-count)で、CSS の差は幅と押した後の色だけです。

  • マークは拍手。 サムズアップは Facebook の「いいね」と同じ記号で隣に本物が並び、ハートは一覧の ♡(総シェア数)と重なる。拍手はどちらとも当たらない
    • Tabler に拍手が無いので、線画は Phosphor Icons(MIT)、塗りは Font Awesome Free(CC BY 4.0)Phosphor の fill は後ろの手しか塗らず線画とほぼ同じ絵になるため使えない(描いて確認)
    • 辞書が { viewBox, fillRule, paths } のオブジェクトも取れるようにした
  • Medium の拍手と同じで何度でも叩ける。 数が増えるのは3つまでで、上限の後も叩けるが増えるのは見た目だけ
  • 取り消しは長押し(1秒)。押し始めて 250ms で「長押しでキャンセルします」(グレーの吹き出し)を出してから実行する。キーボードは長押しできないので、上限の後にもう一度押すと取り消し(clickdetail で見分ける)
  • 入れた後は絵・数字・枠をクリムゾンにそろえる。 暗い側は絵と枠を crimson-on-dark(3.46)に替え、数字は文字なので 4.5 に届かず赤くしない
  • :focus ではなく :focus-visible.share-btn:focus で書かれていて、マウスで押した後もフォーカスが残る間ずっと反転したままだった。共有ボタンごと直した

叩いたときに起きること

  1. 絵が跳ねるlike-pop
  2. やわらかい光と、上と横へ散る小さな点like-bloom / like-sparks)。硬い輪は衝撃波に見えたので輪郭を持たせない。下へ飛ぶ点は持たない(ボタンの下の数字に重なるため)
  3. 小さな風船が浮かぶ.like-float)。1つずつ別の要素なので前のが浮いていても消えない。数は段が上がるほど増える
  4. 3つ目に届くと +3 の札(クリムゾンのチップ・白文字)。1つだけ、決まった場所に。 散らばると、そのぶん送られたように読めてしまう。出ている間にもう一度叩かれたら小さく揺らすだけ
  5. 叩き終わってから「ありがとうございました!」(クリムゾンの吹き出し)を2秒。一度きり

1つ目・2つ目・3つ目で祝いが一段ずつ強くなります。 強さは liis-level-N が持ち、CSS 変数(--pop-scale / --burst-alpha / --burst-bloom / --burst-spread)で跳ねと光と点にまとめて効かせます。

prefers-reduced-motion: reduce では動きを全部止め、結果だけを出します。

動作確認

Worker(wrangler dev、アカウント無しの Miniflare)

叩いたもの 結果
/like を1→2→3→4回 mine が 1→2→3→3(上限で止まる)
?n=3 でまとめて 一度で likes:3, mine:3
/unlike likes:0, mine:0。二度目も 0 のまま
Origin 無し / 別オリジン 403
不正な記事ID 400
GET /dump トークン無し / 有り 401 / 200
同一IPから30連打 17回目まで 200、以降 429
1,212件を入れてダンプ list() 2ページ・18,070 bytes・32ms

ブラウザ側(生成物の inline script を DOM スタブで Node 実行)

場面 浮かぶ風船
1拍手 1 1個
2拍手 2 3個
3拍手 3 +3 6個
4拍手 3(増えない) +3(揺れ) 9個
叩き終わり 3 出る
長押し 0 出さない
再訪(記録あり) 焼き込み値 出さない(跳ねも走らない
通信が届かない / 429 戻る 出さない

未処理の reject は残りません。一覧ページ(共有ボタン25組)にいいねボタンは0件で、記事ページだけに出ます。make lint-css / make lint-partials / textlint とも通過。

段階3でやること

  • themes/future/_config.ymllikes_endpoint に Worker の URL を入れる
  • update-cache.ymlGET /dump を足して like_count_cache.json をコミットする。取り込む側で実在する記事IDだけを拾う
  • Rate Limiting binding は wrangler では unsafe(実験的)扱い。ローカルでは効いたが本番で確かめる

🤖 Generated with Claude Code

ma91n and others added 3 commits September 11, 2026 15:21
記事の「役に立った」を受け取る Worker と KV を workers/likes/ に置く。
数の表示はビルド時に焼き込む方針にしたので、Worker が持つのは
POST /like/<記事ID> とバッチ用の GET /dump の2本だけ。

ブログ側の JS と見せ方、毎日のダンプ取り込みは後続のコミットで足す。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#2565(記事末のリンクのコピー)が PR #2730 で公開済みで、
social-button.ejs が既に navigator.clipboard を呼んでいる。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ラベル付きの横長ボタンを共有ボタンと同じ行の左端に置く。色はネイビー、
マークはサムズアップ、高さは丸と同じ 28px。数字はボタンの外。

ハートを使わないのは、一覧と関連記事の ♡ が総シェア数を表していて、
記事末の帯にそのまま並ぶため。

数は like_count_cache.json から焼き込む(段階3でバッチが作る)。
送り先(themes/future/_config.yml の likes_endpoint)が空の間は
li が hidden のままなのでボタンは出ない。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ma91n ma91n changed the title いいね機能: Cloudflare Worker と KV (#1949) いいね機能: Worker と、共有ボタンの隣のボタン (#1949) Sep 11, 2026
ma91n and others added 22 commits September 11, 2026 16:48
はてなブックマークなどの共有ボタンと同じ位置にそろえる。器が
.share-item と同じ形になるので、li に share-item を、数字に
share-count を付けて1つのルールから引く。

あわせてダークモードの置き換えを足した。ネイビーは暗い地で 1.05
しか出ないので、dark-parts() の .share-btn の枝に .like-button を並べる。

数字を下に回したぶん行の幅が 355px から 334px に縮み、幅375pxで
共有の丸が5つの記事でも折り返さなくなった。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
「役に立った」の文字を消し、28px の丸を横に伸ばした 44px の錠剤にする
(14px の3文字ぶん)。役の違いはラベルではなく形が示す。

ボタンが .share-btn そのものになったので、CSS の差は横幅と押した後の
状態だけになった。色・高さ・アイコンの大きさ・hover の反転・ダークの
置き換えは共有側の1つのルールから来る。

名前は title と sr-only が持つ(共有ボタンと同じ形)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
サムズアップは Facebook の「いいね」と同じ記号で、すぐ隣に Facebook の
共有ボタンが並ぶ。幅を 56px(14px の4文字ぶん)に広げ、地を面の段で
一段落として(surface-tint)取り違えを避ける。

枠は共有ボタンと同じ rule-base のまま。形と地で分かれているので
3つ目の手は持たない。押した後もマークの塗りだけで示す。

地は button.share-btn の transparent を打ち消す必要があるので
button.like-button と同じ詳細度で書く。カスケードの勝者を照合して、
幅と地だけが like-button 由来になっていることを確認した。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
サムズアップは Facebook の「いいね」と同じ記号で、すぐ隣に Facebook の
共有ボタンが並ぶ。ハートに替えると一覧の ♡(総シェア数)と記号が重なるが、
あちらは 12px の素の文字で押せず、こちらは 72px の錠剤の中にあって
地も色も違うので、取り違えの重さが釣り合わない。

押すとマークが跳ね、枠と同じ形の輪が広がって消える。マークは
クリムゾンの塗りになる。差し色の「画面内1箇所」は守ったうえで、
出るのは読者が押した後だけなので、その1箇所が読者自身の行為を指す。

引き金は aria-pressed ではなく script が足す is-liking。属性で回すと
前に押した記事を開き直すたびに動いてしまう。

速さは like-pop-speed(0.3s)で、時間の段の2つ目の例外として置く。
prefers-reduced-motion では動きを止めて結果だけを出す。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
枠と同じ形の線が外へ広がるので衝撃波に見えた。跳ねるのはマークだけにする。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
指している間は「いいね」、押した直後は「ありがとうございます!」を2秒。
形はコピーの「コピーしました」と1つのルールを共有する。title は使わない
(OSの素の吹き出しは出るまで間があり、見た目もサイトの語彙の外)。

吹き出しの左端はボタンにそろえる。礼の文は 12px でも 150px 近くあり、
72px のボタンの中央に置くと本文列の左端からはみ出す。

押した後は下の数字もクリムゾンにそろえる。暗い側は数字を赤くしない——
ハートは crimson-on-dark で 3.46(非テキストの 3:1)を満たすが、
文字に要る 4.5 には 3.81 で届かない。

あわせて .share-btn:focus を :focus-visible に直した。マウスで押した後も
フォーカスが残る間ずっと反転したままになり、他を押すまで戻らなかった。
CLAUDE.md が「:focus ではなく :focus-visible」と決めている箇所。

呼び名は「いいね」にそろえた(issue の題は「役に立った」)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
押す前の名前は title(OSのツールチップ)に戻す。指したときに出すだけなので
専用の吹き出しは要らない。

礼は押した少し後に一度だけ出し、以後は指しても出さない。跳ねが終わってから
出したいので、レスポンスが返った時刻ではなく押した時刻から 400ms で測る。
往復の速さでタイミングがぶれない。

地はネイビーからクリムゾンへ。赤くなったハートと同じできごととして読ませる。
押したら title を外す(すでに押したボタンに「いいね」と出ても意味が無く、
礼の吹き出しと二重にも見える)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
マークがハートになって共有ボタンのロゴと記号で分かれたので、色の手は
持たない。地も枠も共有ボタンと同じになり、CSS の差は幅だけになった。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
マークを拍手に。サムズアップは Facebook の「いいね」と同じ記号で隣に
本物が並び、ハートは一覧の ♡(総シェア数)と重なる。拍手はどちらとも
当たらない。Tabler に拍手が無いので Phosphor Icons(MIT)から借りる。
線画も塗りで描く流儀で viewBox も 256 なので、svg-icon の辞書が
{ viewBox, fillRule, paths } のオブジェクトも取れるようにした。

Medium の拍手と同じで何度でも叩ける。跳ねは毎回返すが、数が増えるのも
記録を送るのも最初の1回だけ。+1 が出るのも1回だけにした——すぐ下に
数字があるので、動かない数の上で毎回 +1 を出すと嘘になる。

押した瞬間に +1 が上へ抜け、それが終わってから礼が出る(押してから 700ms)。
+1 の速さは新しい段を置かず like-pop-speed * 2 から導く。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1人が入れられるのは3つまで(Medium の拍手と同じ形)。上限は Worker が
持ち、POST /like に ?n= で個数を受ける。上限まで入れた後は
POST /unlike でその読者のぶんだけ引く。

連打はまとめて1回送る(叩き終わってから 800ms)。1つずつ送ると KV の
書き込みを3倍消費する。読者には叩いた瞬間に数が動くので待ち時間は見えない。

風船は差分ではなく「いくつ入れたか」(+1 → +2 → +3)。上限の後も叩けて
+3 が出続ける。差分にすると、動かない数の上で毎回 +1 が出て嘘になる。

取り消しは長押し(600ms)。キーボードは長押しできないので、上限の後に
もう一度押すと取り消しにする(click の detail で見分ける)。

塗りのアイコンを Font Awesome Free に替えた。Phosphor の fill は後ろの手
しか塗らず、線画とほとんど同じ絵になる。押した後は絵・数字・枠をそろえて
クリムゾンにする。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
風船は叩くたびに撃ち直さず、1つが浮いたまま数字だけ差し替わって
叩き終わってから消えるようにした。撃ち直すと連打で +1 +2 +3 が
次々に流れて落ち着かない。礼は固定の待ち時間ではなく、風船が
引っ込むのを引き金にする(同じ場所に出るので重ねられない)。

叩いたときの祝いを足した。硬い輪は衝撃波に見えたので、やわらかい光
(::before)と外へ散る小さな点(::after を1つ置いて box-shadow で6つ)
にする。1つ目・2つ目・3つ目で一段ずつ強くなる。強さは li の
is-level-N が持ち、CSS 変数で跳ねと光と点にまとめて効かせる。

長押しは押し始めて 250ms で「長押しでキャンセルします」を出し、
そこからもう一拍おいて(合計1秒)実行する。取り消しは戻せないので
何が起きるかを先に見せる。1つでも入れていれば効く。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
礼のクリムゾンとも、ブランドのネイビーとも役が違う、ただの機械の案内
なので色を持たせない。ink-strong を面として使う(白い文字で 10.05)。
テーマで明暗が入れ替わるので、暗い側は文字の色だけ戻す。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
同じ場所に出るので、案内を出すときに礼を引っ込め、案内が出ている間は
礼を出さないようにした。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
風船が出るのは3つ目に届いたときだけにした。1つ目・2つ目は跳ねと祝いで返す。

使い回しをやめて1つずつ別の要素にする。使い回すと、前のが浮いている間に
次を出したときに前のが消える。左右のずれ(--dx)を順に変えて重ならない
ようにした。動きを止める設定ではアニメーションが走らないので、
要素は時間で片づける。

礼の引き金は風船ではなく「叩き終わり」に移した。風船が出ない
1つ・2つの場合にも礼が出る。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
札が散らばって重なると、そのぶん送られたように読めてしまう。札は
1つだけ、決まった場所に出す(3つ目に届いたときだけ。上限の後も同じ位置)。

代わりに、叩くたびに小さな風船を飛ばす。1つずつ別の要素なので前のが
浮いていても消えず、左右のずれ・大きさ・出だしの遅れを順に変えて
同じ軌跡が重ならないようにした。数は段が上がるほど増える。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
札は素の文字だと風船と同じ色の点が並んで見分けが付かないので、
クリムゾンの塗りに白い文字(5.35)のチップにした。z-index で風船より
前に出す(札が隠れると何個入ったかが読めない)。

風船は札を避けて左右へ抜けるようにした。まっすぐ上げると札の真下で
渋滞して、前の風船が消えたように見える。--dx から 0 の近くを外し、
振れ幅を 14〜20px から 18〜38px に広げた。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
浮いた位置を基準に置いて、引っ込むときは元いた場所でフェードするだけに
した。上がるのは出てくるときだけなので、transition ではなく入りの
アニメーションで持つ。

出ている間にもう一度叩かれたら、出し直さずに小さく揺らす。数は変わらない
ので、受け取ったことだけを返す。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
横位置は translate(-50%) で中央に寄せているので、そこを -52% にしても
幅34pxのチップでは 0.7px しか動かず見えなかった。振れ幅を px で書き直し、
縦 ±3px・回転 9度・拡大 1.14 まで上げて、時間も 0.24s から 0.36s にした。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
横 ±3px・回転 9度・拡大 1.14 は強すぎたので、横 ±1.5px・縦 ±2px・
回転 4度・拡大 1.06 に。時間も 0.36s から 0.3s(like-pop-speed)へ戻した。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
横 ±1px・縦 ±1.5px・回転 2度・拡大 1.03。見える下限に寄せる。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ボタンの下には数字があり、そこに点が重なると読みにくい。散る点を
上と横だけの5つにして、浮かぶ風船の出だしも下へ下げないようにした。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ma91n ma91n changed the title いいね機能: Worker と、共有ボタンの隣のボタン (#1949) 記事末にいいね(拍手)を置く (#1949) Sep 12, 2026
@ma91n
ma91n marked this pull request as ready for review September 12, 2026 05:36
@ma91n
ma91n merged commit 5614d8e into main Sep 12, 2026
4 checks passed
@ma91n
ma91n deleted the likes-worker-1949 branch September 12, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant