TikTokのストーリーのように、投稿から24時間で消えるコンテンツがあります。私は公開アカウントのストーリーをログインなしで表示する小さなWebツール TikStories を個人で作っていて、各ストーリーに「消えるまであと何時間か」を表示しています。
表示自体は「24時間 − 経過時間」で済みそうに見えますが、実際に考え始めると決めることがいくつもありました。この記事はその整理です。
なお、以下のコードは考え方を説明するための最小サンプルで、TikStoriesの実装そのものではありません。
1. 基準は「投稿時刻」、計算はUNIX時間で
残り時間は「期限 − 現在時刻」です。期限は投稿時刻 + 24時間。ここでタイムゾーン付きの日付文字列同士を比べ始めると混乱するので、比較はすべてミリ秒のUNIX時間で行います。
const DAY_MS = 24 * 60 * 60 * 1000;
function msLeft(createdAtSec, nowMs = Date.now()) {
const expiresAtMs = createdAtSec * 1000 + DAY_MS;
return expiresAtMs - nowMs;
}
APIが秒で返すのかミリ秒で返すのかは、最初に必ず確認します。ここを取り違えると「残り約2万日」のような表示になります。
2. 表示の丸めは「切り捨て」が無難
残り 1時間59分 を「2時間」と出すか「1時間」と出すか。ユーザーが知りたいのは「まだ間に合うか」なので、実際より長く見せる切り上げは避けて切り捨てる方が無難です。サンプルでは、1時間を切ったら分単位に切り替えています。
function formatLeft(ms) {
if (ms <= 0) return 'expired';
const h = Math.floor(ms / 3_600_000);
if (h >= 1) return `${h}h left`;
const m = Math.max(1, Math.floor(ms / 60_000));
return `${m}m left`;
}
サンプルでは「0分」と出さないために、1分未満を「1m」に寄せています。0分表示は「もう消えたのか、まだあるのか」が分からない、いちばん困る表示です。
3. サーバーとブラウザの時計はずれている
期限の計算をサーバーで済ませて「残り◯時間」の文字列だけ返す方法もありますが、ページを開いたまま放置されると表示が古くなります。逆にブラウザで毎回計算すると、利用者のPCの時計がずれている場合に誤差が出ます。
折衷案として、サーバーはレスポンスに自分の現在時刻を含め、ブラウザ側でその差分(オフセット)を補正してから計算する、という形が扱いやすいです。
// serverNowMs: レスポンスに含めたサーバー時刻
const offset = serverNowMs - Date.now();
const left = msLeft(createdAtSec, Date.now() + offset);
数分程度のずれなら時間単位の表示にはほぼ影響しませんが、残り1時間を切った分単位の表示では効いてきます。
4. 開きっぱなしのタブを想定する
ストーリー一覧を開いたまま別の作業をして、1時間後に戻ってくる、というのは普通にあることです。そのとき「残り1h」のままの表示は誤りになります。
- 1分ごと程度に表示だけ再計算する(再取得までは不要)
- 期限を過ぎた項目は「expired」に切り替え、再生ボタンは無効にする
再生しようとして初めて失敗するより、見た目の段階で「もう無い」と分かる方が親切です。
5. 「無い」が普通の状態
24時間で消えるので、多くのアカウントは大半の時間、アクティブなストーリーを持っていません。これはエラーではなく通常状態です。TikStoriesではストーリーが無いとき、空のページを出す代わりにそのアカウントの最近の動画を表示するようにしました。残り時間の設計とは別の話ですが、「期限付きコンテンツ」を扱うなら、空の状態を最初から主役として設計しておくのが大事だと感じています。
まとめ
- 期限はUNIX時間で計算し、単位(秒/ミリ秒)を最初に確認する
- 表示は切り捨て。0分表示は出さない
- サーバー時刻とのオフセットを補正する
- 開きっぱなしのタブのために表示だけ定期的に再計算する
- 「何も無い」を通常状態として設計する
期限切れのコンテンツは取り戻せないので、「あとどれくらい見られるか」を正直に出すことが、この種のツールでいちばん役に立つ情報だと思っています。