Threads Scroll Restoration Fix (Firefox)

Fix back button not restoring scroll position on threads.com in Firefox

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Threads Scroll Restoration Fix (Firefox)
// @namespace    Tsuyumi25
// @version      0.2
// @description  Fix back button not restoring scroll position on threads.com in Firefox
// @match        https://www.threads.com/*
// @match        https://threads.com/*
// @run-at       document-start
// @inject-into  page
// @license      MIT
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const SCROLL_KEY = '__threads_scroll_pos';
  let restoring = false;

  history.scrollRestoration = 'manual';

  function saveScroll() {
    if (!restoring) {
      sessionStorage.setItem(SCROLL_KEY + ':' + location.href, String(document.documentElement.scrollTop));
    }
  }

  function tryRestore(url) {
    const saved = sessionStorage.getItem(SCROLL_KEY + ':' + url);
    if (saved === null) return;

    const target = parseInt(saved, 10);
    if (target === 0) return;

    restoring = true;
    let attempts = 0;
    const maxAttempts = 50;

    function tick() {
      attempts++;
      if (document.documentElement.scrollHeight >= target + window.innerHeight || attempts >= maxAttempts) {
        window.scrollTo(0, target);
        // 短暫守住位置,防止 Threads 的 JS 之後 scrollTo(0,0) 蓋掉
        setTimeout(() => { restoring = false; }, 500);
        return;
      }
      requestAnimationFrame(tick);
    }

    requestAnimationFrame(tick);
  }

  // 攔截 pushState — 存 scroll,導航後嘗試恢復目標 URL 的位置
  const _pushState = history.pushState.bind(history);
  history.pushState = function (state, title, url) {
    saveScroll();
    _pushState(state, title, url);
    // 如果目標 URL 有存過 scroll(例如 in-app 返回按鈕走 pushState 回時間線)
    const dest = new URL(url, location.href).href;
    tryRestore(dest);
  };

  // 攔截 replaceState
  const _replaceState = history.replaceState.bind(history);
  history.replaceState = function (state, title, url) {
    if (url) saveScroll();
    return _replaceState(state, title, url);
  };

  // popstate(瀏覽器 back/forward 按鈕、滑鼠側鍵)
  window.addEventListener('popstate', function () {
    tryRestore(location.href);
  });

  // 攔截 scrollTo,在恢復期間擋掉 scrollTo(0,0)
  const _scrollTo = window.scrollTo.bind(window);
  window.scrollTo = function (x, y) {
    if (restoring) {
      // 只擋 scrollTo(0, 0),其他放行
      if ((x === 0 && y === 0) || (typeof x === 'object' && x.top === 0)) return;
    }
    return _scrollTo(x, y);
  };

  // 鎖死 scrollRestoration
  Object.defineProperty(history, 'scrollRestoration', {
    get: () => 'manual',
    set: () => {},
    configurable: false,
  });
})();