📘 useAwayTracker Hook 使用文档

useAwayTracker 是一个 React Hook,用于检测用户是否离开页面,并在回到页面时精确计算离开时长。 可应用于 自动刷新数据安全登出用户行为分析 等场景。


✨ 功能特性

  • 核心功能

    • 监听页面是否可见(visibilitychange
    • 可选监听 blur/focus(更细粒度检测)
    • 离开时记录时间、回到时计算离开秒数
  • API

    • isAway: boolean — 当前是否离开
    • awaySeconds: number — 已离开秒数(实时更新)
    • reset(): void — 手动清零
    • getCurrentAwaySeconds(): number — 即时获取已离开秒数
    • onReturn(seconds, reason) — 回到页面时的回调
  • 健壮性

    • 不依赖后台的 setInterval,避免限速误差
    • 回到页面时通过时间差校准,保证数据准确
    • 自动清理事件与定时器,避免内存泄漏

📦 Hook 源码

import { useState, useRef, useEffect, useCallback } from "react";
 
/**
 * 页面离开/返回检测 Hook
 * @param {(seconds: number, reason: string) => void} onReturn 回到页面时的回调
 */
export function useAwayTracker(onReturn) {
  const [isAway, setIsAway] = useState(false);
  const [awaySeconds, setAwaySeconds] = useState(0);
 
  const awayStartRef = useRef(null);
  const timerRef = useRef(null);
 
  const getCurrentAwaySeconds = useCallback(() => {
    if (!awayStartRef.current) return 0;
    return Math.floor((Date.now() - awayStartRef.current) / 1000);
  }, []);
 
  const reset = useCallback(() => {
    awayStartRef.current = null;
    setAwaySeconds(0);
    setIsAway(false);
  }, []);
 
  const markAway = useCallback((reason) => {
    if (awayStartRef.current) return; // 已经在离开状态
    awayStartRef.current = Date.now();
    setIsAway(true);
 
    // 启动一个计时器(仅用于前台展示,后台可能限速,但回来会修正)
    timerRef.current = setInterval(() => {
      setAwaySeconds(getCurrentAwaySeconds());
    }, 1000);
 
    console.debug("[useAwayTracker] markAway:", reason, new Date().toISOString());
  }, [getCurrentAwaySeconds]);
 
  const markReturn = useCallback((reason) => {
    if (!awayStartRef.current) return;
 
    const seconds = Math.floor((Date.now() - awayStartRef.current) / 1000);
    clearInterval(timerRef.current);
    timerRef.current = null;
    awayStartRef.current = null;
 
    setAwaySeconds(seconds);
    setIsAway(false);
 
    console.debug("[useAwayTracker] markReturn:", reason, "awaySeconds=", seconds);
    if (onReturn) onReturn(seconds, reason || "return");
 
    // 回来后 2 秒重置 awaySeconds 为 0(让 UI 回归)
    setTimeout(() => setAwaySeconds(0), 2000);
  }, [onReturn]);
 
  useEffect(() => {
    const handleVisibility = () => {
      if (document.hidden) {
        markAway("visibilitychange:hidden");
      } else {
        markReturn("visibilitychange:visible");
      }
    };
 
    document.addEventListener("visibilitychange", handleVisibility, true);
    window.addEventListener("blur", () => markAway("window:blur"), true);
    window.addEventListener("focus", () => markReturn("window:focus"), true);
 
    return () => {
      document.removeEventListener("visibilitychange", handleVisibility, true);
      window.removeEventListener("blur", () => markAway("window:blur"), true);
      window.removeEventListener("focus", () => markReturn("window:focus"), true);
      clearInterval(timerRef.current);
    };
  }, [markAway, markReturn]);
 
  return {
    isAway,
    awaySeconds,
    getCurrentAwaySeconds,
    reset,
  };
}

🚀 使用示例

import React from "react";
import { useAwayTracker } from "./useAwayTracker";
 
export default function Demo() {
  const { isAway, awaySeconds } = useAwayTracker((seconds, reason) => {
    console.log(`用户离开 ${seconds} 秒后回来(${reason})`);
    if (seconds >= 10) {
      alert("检测到你离开超过 10 秒,自动刷新数据!");
    }
  });
 
  return (
    <div>
      <h3>H5 离开页面检测 Demo</h3>
      {isAway
        ? `当前离开中... 已离开 ${awaySeconds} 秒`
        : "当前页面活跃中 ✅"}
    </div>
  );
}

⚡ 行为说明

  • 离开页面

    • 页面 hiddenwindow.blurisAway = true
    • awaySeconds 开始计数
    • 页面标题可选修改为 【离开中】xxx
  • 回到页面

    • 页面 visiblewindow.focusisAway = false
    • 自动计算离开时长 → 回调 onReturn(seconds, reason)
    • UI 2 秒后重置计时为 0

🛠 应用场景

  • 数据刷新 回到页面后自动刷新数据,保证实时性。

  • 安全策略 长时间离开后要求重新登录。

  • 用户体验 离开时暂停动画 / 视频,回到时恢复播放。