乐趣区

页面可见与不可见的事件visibilitychange

需求背景

在最近的项目中,使用了 transition 和定时器实现了随机走动物体的功能,走动的物体还会有 animation 的动画。我发现在手机中,按 home 键或者切换应用,使页面不在屏幕中,也就是页面不可见,过一段时间切回来,会出现物体移动但是没有播放 animaiton 的动画的情况。

我就想到了 visibilitychange。

结合 react 使用,添加类似 onShow/onHide 生命周期

额外生命周期

浏览器 document 有个 visibilitychange 的事件,由于存在兼容性问题,所以代码里也做了兼容处理。该事件会在 document.visibilityState 发生变化时触发,visibilityState 有两个状态值——visible 和 hidden,表示页面是否在屏幕当中。

let changeState;
let visibilityChange;

if (typeof document.hidden !== 'undefined') {
    visibilityChange = 'visibilitychange';
    changeState = 'visibilityState';
} else if (typeof document.mozHidden !== 'undefined') {
    visibilityChange = 'mozvisibilitychange';
    changeState = 'mozVisibilityState';
} else if (typeof document.msHidden !== 'undefined') {
    visibilityChange = 'msvisibilitychange';
    changeState = 'msVisibilityState';
} else if (typeof document.webkitHidden !== 'undefined') {
    visibilityChange = 'webkitvisibilitychange';
    changeState = 'webkitVisibilityState';
}

知道了当前浏览器的状态属性和事件名称后,就可以添加时间监听了。

const visibleCallbackList = [];
const hiddenCallbackList = [];

document.addEventListener(
    visibilityChange,
    () => {if (document[changeState] === 'visible') {for (let i = 0; i < visibleCallbackList.length; i++) {if (typeof visibleCallbackList[i] === 'function') {visibleCallbackList[i]();}
            }
        } else if (document[changeState] === 'hidden') {for (let i = 0; i < hiddenCallbackList.length; i++) {if (typeof hiddenCallbackList[i] === 'function') {hiddenCallbackList[i]();}
            }
        }
    },
    false
);

上述代码中,维护了两个数组,分别代表页面进入可见状态时需要执行的回调列表和进入不可见状态时需要执行的回调列表。这两个列表在下面会讲到。

因为我们是使用 react 开发,所以想在组件级别做到该组件是否能使用该功能,所以想到让组件具有类似小程序的 onShow 和 onHide 的生命周期,在这个生命周期中执行组件内部的逻辑。

export const h5OnShow = callback => {visibleCallbackList.push(callback);
};

export const h5OnHide = callback => {hiddenCallbackList.push(callback);
};

/**
 *
 * @param {Object}
 *   {Function} h5OnShowCallback h5 需要注销的显示回调
 *   {Function} h5OnHideCallback h5 需要注销的隐藏回调
 */
export const h5ExtraLifecycleWillUnmount = ({h5OnShowCallback, h5OnHideCallback}) => {if (h5OnShowCallback) {visibleCallbackList.splice(visibleCallbackList.indexOf(h5OnShowCallback), 1);
    }
    if (h5OnHideCallback) {hiddenCallbackList.splice(hiddenCallbackList.indexOf(h5OnHideCallback), 1);
    }

};

如上述代码中,h5OnShow 方法中将传入的 callback push 至 visibleCallbackList 数组,h5OnHide 方法将 callback push 到 hiddenCallbackList。
h5ExtraLifecycleWillUnmount 是在组件即将要卸载的时候调用,将回调列表里的方法删除。

额外生命周期的使用

    componentDidMount() {h5OnShow(this.pageShow);
        h5OnHide(this.pageHide);
    }
    componentWillUnmount() {
        h5ExtraLifecycleWillUnmount({
            h5OnShowCallback: this.pageShow,
            h5OnHideCallback: this.pageHide
        });
    }
    pageShow = () => {// 开启随机走动定时器}
    pageHide = () => {// 关闭随机走动定时器}

在组件里,注册 onSHow 和 onHide,在页面显示时开启定时器,在页面隐藏时关闭定时器并把 transition 设置为 none,这样在页面不可见时不会做无用的逻辑处理,这也是符合用户的预期,因为页面隐藏时并不关心在这期间做了生命动画变更。

可优化点:
1.visibleCallbackList 和 hiddenCallbackList 使用 WeakSet 更好,保证了不会出现内存泄漏。
2. 如果组件实例化多次,pageShow 和 pageHide 使用箭头函数并不友好,可使用修饰器模式改变原型上的方法的 this 指向。

退出移动版