推荐阅读
我们知道, 对于 app 端的弹窗, 物理返回键可以关闭弹窗, 但是在 web 端, 按物理返回键, 直接就返回上一页了。
返回键关闭弹窗, 无疑交互体验更好, 那么怎么实现呢?
首先向大家推荐一个好用的弹窗管理库: NiceModal, 下列代码都是基于该库以及 mui (大多是伪代码, 当然可以不用库或者使用其他库)
因为返回键会改变 history, 所以我们的实现思路肯定是通过监听 popstate
来关闭弹窗:
history.pushState()
popstate
事件发生时关闭弹窗history.back()
恢复 history
栈history.pushState()
和 history.back()
, 会破坏用户的浏览记录
history.pushState()
的话, 用户就无法通过 back / forward
回到 B 页面了暂且将我们的方法命名为 useInjectHistory
// 声明
const TestModal = NiceModal.create(() => {
const modal = useModal();
useInjectHistory(modal);
return <Dialog>test modal</Dialog>;
});
// 调用
NiceModal.show(TestModal);
但是调用方需要有阻止弹窗关闭的能力, 也就是说 "弹窗关闭" 需要放在外部, 即:
// 声明
const TestModal = NiceModal.create(() => {
const modal = useModal();
useInjectHistory(modal, () => {
modal.hide();
});
return <Dialog>test modal</Dialog>;
});
// 调用
NiceModal.show(TestModal);
export function useInjectHistory(
modal: NiceModalHandler<Record<string, unknown>>,
/**
* 如果需要在用户物理返回时关闭弹窗, 就在该方法中手动调用 modal.hide();
* 如果拒绝关闭弹窗, 就别 hide() 并 throw Error;
*/
onPopState: (e: PopStateEvent) => Promise<void>
) {
// mui 的 useEventCallback
const finalOnPopState = useEventCallback((e: PopStateEvent) => {
isTriggeredByPopStateRef.current = true;
// 当 popstate 发生时, 如果 onPopState 抛错 (即调用方拒绝关闭弹窗),
// 那么此处重新 history.pushState, 以恢复 history 栈
onPopState(e).catch(() => {
window.history.pushState(null, "", "#dialog");
});
});
// react-use 的 useEvent (也可自行使用其他方法, 反正就是监听 popstate, 然后执行 onPopState)
useEvent("popstate", finalOnPopState);
// useListen 见下文所述
useListen(modal.visible, () => {
if (modal.visible) {
// 弹窗打开时, push history
window.history.pushState(null, "", "#dialog");
} else if (!isTriggeredByPopStateRef.current)
// 其他地方 (非 popstate 事件) 触发弹窗关闭时, 恢复 history 栈
window.history.back();
});
}
useListen 见本站文章 一个监听变量变化的语法糖
上面的实现有一个问题: 如果我页面上多个弹窗并存时,
popstate
事件会在所有弹窗中触发, 导致一个popstate
关闭了所有弹窗, 如何解决这个问题呢? 且听下回分解
如非特别声明,本站作品均为原创,遵循【自由转载-保持署名-非商用-非衍生 创意共享 3.0 许可证】。
对于转载作品,如需二次转载,请遵循原作许可。