1

需求背景

在最近的项目中,使用了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指向。


一画先生
83 声望12 粉丝

我司长期招聘前端开发工程师,有意的小伙伴+vx: Mr_yihua