【问题标题】:Check if the browser tab is in focus in ReactJS检查浏览器选项卡是否在 ReactJS 中处于焦点位置
【发布时间】:2022-04-17 19:49:28
【问题描述】:

我在 ReactJS 中有一个网站。每当我的标签成为焦点或隐藏时,我都想得到回调。为此,我遇到了 Page Visibility API,但我无法弄清楚如何在 ReactJS 中使用它。

我应该在哪个生命周期方法中为此注册回调?

【问题讨论】:

    标签: reactjs


    【解决方案1】:

    从 React 16.8 开始使用钩子构建它

    import React, { useEffect } from "react";
    
    // User has switched back to the tab
    const onFocus = () => {
        console.log("Tab is in focus");
    };
    
    // User has switched away from the tab (AKA tab is hidden)
    const onBlur = () => {
        console.log("Tab is blurred");
    };
    
    const WindowFocusHandler = () => {
        useEffect(() => {
            window.addEventListener("focus", onFocus);
            window.addEventListener("blur", onBlur);
            // Calls onFocus when the window first loads
            onFocus();
            // Specify how to clean up after this effect:
            return () => {
                window.removeEventListener("focus", onFocus);
                window.removeEventListener("blur", onBlur);
            };
      }, []);
    
        return <></>;
    };
    
    export default WindowFocusHandler;
    
    

    【讨论】:

    • 它在我切换选项卡时工作,但如果我重新加载页面并且窗口已经成为焦点,这将不起作用。我使用了完全相同的代码。发生这种情况有什么原因吗?
    • 监听器在初始加载时无法工作。在这种情况下,请在安装组件时调用一次onFocus
    【解决方案2】:

    这应该可行:

    componentDidMount() {
        window.addEventListener("focus", this.onFocus)
    }
    
    componentWillUnmount() {
        window.removeEventListener("focus", this.onFocus)
    }
    
    onFocus = () => {
        //
    }
    

    编辑:“模糊”也是如此,它应该适用于选项卡被隐藏时。

    检查@Assaf 的答案以了解钩子的使用情况。

    【讨论】:

    • 你不能在绑定时调用函数,只要像window.addEventListener("focus", this.onfocus)那样绑定它。解除绑定也是如此
    • 我正在使用 window.addEventListener('focus', method) 但是当我单击链接时不会弹出警报,但是当我转到另一个选项卡只是为了模拟'离开'然后去再回来然后警报工作不好。我在 useEffect 挂钩中使用它。有人有这个想法吗?
    【解决方案3】:

    没有可靠的方法来检查它,所以你需要将几种方法组合在一起。这是 react-hooks 的上下文

    import React, { useState, useEffect } from 'react'
    
    export const WindowContext = React.createContext(null)
    
    export const WindowContextProvider = props => {
      const [windowIsActive, setWindowIsActive] = useState(true)
    
    
      function handleActivity(forcedFlag) {
        if (typeof forcedFlag === 'boolean') {
          return forcedFlag ? setWindowIsActive(true) : setWindowIsActive(false)
        }
    
        return document.hidden ? setWindowIsActive(false) : setWindowIsActive(true)
      }
    
      useEffect(() => {
        const handleActivityFalse = () => handleActivity(false)
        const handleActivityTrue = () => handleActivity(true)
    
        document.addEventListener('visibilitychange', handleActivity)
        document.addEventListener('blur', handleActivityFalse)
        window.addEventListener('blur', handleActivityFalse)
        window.addEventListener('focus', handleActivityTrue )
        document.addEventListener('focus', handleActivityTrue)
    
        return () => {
          window.removeEventListener('blur', handleActivity)
          document.removeEventListener('blur', handleActivityFalse)
          window.removeEventListener('focus', handleActivityFalse)
          document.removeEventListener('focus', handleActivityTrue )
          document.removeEventListener('visibilitychange', handleActivityTrue )
        }
      }, [])
    
      return <WindowContext.Provider value={{ windowIsActive }}>{props.children}</WindowContext.Provider>
    }
    

    【讨论】:

    • 此答案不会删除 removeEventListeners 中与内联添加的相同功能。
    • @fresidue 现在可以了吗?
    • 我还必须添加document.addEventListener('visibilitychange', handleActivity)。否则,活动不会更新,例如,如果您打开控制台导致窗口模糊,则缩小选项卡,然后再次最大化。
    • @AlbertSchilling 我可以在我的示例中看到同一行...
    • @ZiiMakc 抱歉,该评论旨在确认和解释为什么这行也是必要的。
    【解决方案4】:

    我找到了This library。这可能会有所帮助。

    这是我将如何使用它来解决您的问题

    import React from 'react';
    import PageVisibility from 'react-page-visibility';
    
    class YourComponent extends React.Component {
        state = {
          isWindowInFocus: true,
        }
        componentDidMount() {
          const { isWindowInFocus } = this.props;
          if (!isWindowInFocus) {
            // do something
          }
        }
    
        listentoWindow = isVisible => {
          this.setState({
            isWindowInFocus: isVisible,
          });
        }
        render() {
          return (
            <PageVisibility onChange={this.listentoWindow}>
              <div>
               Your component JSX
              </div>
            </PageVisibility>
          );
        }
    }
    

    【讨论】:

      【解决方案5】:

      这些都不能很好地满足我的需要,这是一种检测用户是否在选项卡之间切换或通过双击任务栏中的图标最小化浏览器的方法。

      他们要么启动了多次,但确实注册了正确的状态,从任务栏图标最小化时没有工作,或者只是没能一个接一个地跟上多个操作。

      看到每次焦点改变时我都需要发出服务器请求,上述情况有点“不”。

      这就是我所做的:

      const DetectChatFocus = () => {
          const [chatFocus, setChatFocus] = useState(true);
      
          useEffect(() => {
              const handleActivityFalse = () => {
                  setChatFocus(false);
                  serverRequest(false);
              };
      
              const handleActivityTrue = () => {
                  setChatFocus(true);
                  serverRequest(true);
              };
      
              window.addEventListener('focus', handleActivityTrue);
              window.addEventListener('blur', handleActivityFalse);
      
              return () => {
                  window.removeEventListener('focus', handleActivityTrue);
                  window.removeEventListener('blur', handleActivityFalse);
              };
          }, [chatFocus]);
      };
      
      export default DetectChatFocus;
      

      目前这似乎工作得很好,在 Chrome 和 Firefox 上都经过测试,你需要做的就是在一个主要组件中或任何你需要的地方初始化它,它会跟踪所有这些场景的窗口焦点,它会每个操作只发出一个服务器请求。

      【讨论】:

        【解决方案6】:

        更完整和优化的钩子:

        import React, { useState, useEffect } from 'react'
        import _ from 'lodash'
        
        export default function useIsWindowFocused(): boolean {
            const [windowIsActive, setWindowIsActive] = useState(true)
        
            const handleActivity = React.useCallback(
                _.debounce(
                    (e: { type: string }) => {
                        if (e?.type == 'focus') {
                            return setWindowIsActive(true)
                        }
                        if (e?.type == 'blur') {
                            return setWindowIsActive(false)
                        }
                        if (e?.type == 'visibilitychange') {
                            if (document.hidden) {
                                return setWindowIsActive(false)
                            } else {
                                return setWindowIsActive(true)
                            }
                        }
                    },
                    100,
                    { leading: false },
                ),
                [],
            )
        
            useEffect(() => {
                document.addEventListener('visibilitychange', handleActivity)
                document.addEventListener('blur', handleActivity)
                window.addEventListener('blur', handleActivity)
                window.addEventListener('focus', handleActivity)
                document.addEventListener('focus', handleActivity)
        
                return () => {
                    window.removeEventListener('blur', handleActivity)
                    document.removeEventListener('blur', handleActivity)
                    window.removeEventListener('focus', handleActivity)
                    document.removeEventListener('focus', handleActivity)
                    document.removeEventListener('visibilitychange', handleActivity)
                }
            }, [])
        
            return windowIsActive
        }

        【讨论】:

        • 看起来很有趣,你能解释一下 debounce 的作用以及你为什么使用它吗?
        • Debounce 基本上可以防止函数运行过于频繁。 debounce 不是限制(每 X ms 定期执行一个函数),而是保留任意数量的执行调用,直到时间限制,然后运行该函数一次。见geeksforgeeks.org/lodash-_-debounce-method。我使用的是确保每 100 毫秒不会触发一次以上的 handleActivity。这不是必须的,但知道它在控制之下,我感到很自在。
        【解决方案7】:

        我觉得看document.visibilityState的直接状态更简单,因为它返回'visible''hidden'

        const [visibilityState, setVisibilityState] = useState('visible')
        
        useEffect(() => {
          setVisibilityState(document.visibilityState)
        }, [document.visibilityState])
        

        【讨论】:

          【解决方案8】:

          现代钩子:

          import { useCallback, useEffect, useState } from "react";
          
          const useTabActive = () => {
            const [visibilityState, setVisibilityState] = useState(true);
          
            const handleVisibilityChange = useCallback(() => {
              setVisibilityState(document.visibilityState === 'visible');
            }, []);
          
            useEffect(() => {
              document.addEventListener("visibilitychange", handleVisibilityChange)
              return () => {
                document.removeEventListener("visibilitychange", handleVisibilityChange)
              }
            }, []);
          
            return visibilityState;
          }
          
          export default useTabActive;
          

          使用Document.visibilityState

          【讨论】:

            猜你喜欢
            • 2011-11-15
            • 1970-01-01
            • 2017-10-01
            • 1970-01-01
            • 2020-11-23
            • 2023-02-26
            • 1970-01-01
            • 2015-06-27
            相关资源
            最近更新 更多