iframe 弹窗取消按钮触发父页面新增弹窗接口
在开发中,我们可能会因为一些需要需要调用弹窗,进而需要进行父子组件间的通信,但iframe弹窗的新建和销毁又必然会伴随着很多其他的问题,而我最近也遇到了一个关于弹窗销毁带来的一些问题
bug复现
我们的业务需求是在父页面新增一个方法打开一个子页面弹窗,在子页面窗口进行一系列操作然后关闭弹窗,其中我们在关闭子页面弹窗的时候,后台服务器会接收到前端父页面发来的新增弹窗的接口请求,现在我们需要解决这一无端请求的发送问题
父页面打开弹窗的方法
javascript
//回调方法 子页面可以直接对父页面进行操作的回调函数
window.parent.callback = function (mes) {
$('#bootstrap-tree-table').bootstrapTable('refresh');
var layer = layui.layer;
var index = layer.load(2, {time: 10 * 1000}); //又换了种风格,并且设定最长等待10秒
setTimeout(function () {
layer.msg(mes, {icon: 1});
epoPage.loadData();
workNum();
}, 1000)
};
$(".staffAdd").click(function () {
window.parent.layer.open({
type: 2,
title: false,
shade:0.2, /*增加遮罩,阻断点击穿透*/
area: ['55%', '70%'],
content: '/page/staffAdd'
});
})
子页面弹窗以及关闭弹窗
HTML层
html
<form class="layui-form" action="" autocomplete="off" id="form">
<div class="staff-add-action">
<button class="layui-btn layui-btn-lg save" onclick="sub()" type="button">保存</button>
<button class="layui-btn layui-btn-primary layui-btn-lg cancel">取消</button>
</div>
</form>
JavaScript层
javascript
<script th:inline="javascript">
sub = function () {
$.ajax({
url: "/staff/add",
type: "post",
dataType: "json",
data: data,
success: function (res) {
layer.close(loading);
if (res.success) {
var index = parent.layer.index;
parent.callback(res.msg); //调用父页面的回调函数
var temp = window.parent.layer.close(index);
} else {
layer.msg(res.msg);
}
}
})
}
$(".save").attr("readonly", false);
}
$(".cancel").click(function () {
var index = parent.layer.index;
parent.layer.close(index);
})
</script>
在这个代码运行的页面当我们点击取消按钮,则父页面会向后端传递 /page/staffAdd 这个打开页面弹窗的请求
解决方案
bug类别
大分类上属于前端 DOM 事件类 Bug
细分属于事件逃逸(Event leakage /event escape) + HTML 表单默认行为陷阱 + iframe 上下文销毁副作用
行业口语叫法:
iframe 销毁事件逃逸 bug(layer iframe 高频坑)
form 内 button 缺少type="button"默认 submit 踩坑
同步销毁 DOM / iframe 导致未完成 DOM 事件异常冒泡逃逸
css
注意:这个不是 JS 代码逻辑错误,是HTML 原生默认行为 + 浏览器 DOM 生命周期机制共同叠加出来的复合坑,不是框架 bug,属于开发者对浏览器默认行为理解不足引入的缺陷。
原因拆解
less
1.HTML 原生缺陷条件:<form>内的<button>省略 type 属性,浏览器默认 type="submit"。点击按钮会原生触发表单 submit 事件,这是 HTML 标准规定,很多开发者不知道这个默认值。
注:很多人误以为 button 默认是普通按钮,实际 form 标签内部 button 默认是提交按钮。
2.时序缺陷条件:在 click 事件回调里面同步直接执行parent.layer.close(index)立刻销毁 iframe;此时浏览器 DOM 事件冒泡、事件处理流程还没有执行完毕,iframe DOM、JS 上下文直接被浏览器回收。
3.浏览器机制副作用条件:iframe 被销毁后,还在事件队列中未处理完的原生 DOM 事件失去宿主上下文,事件对象 target 错乱,事件向上逃逸到顶层父页面 window;父页面用$(document).on()全局事件委托,匹配到选择器,错误执行父页面回调
总结:三个条件叠加,才出现:点子页面取消,结果执行父页面打开弹窗的逻辑。仅仅缺 type 只会发生子页面表单提交;如果不立刻同步销毁 iframe,事件不会逃逸到父页面。
解决方案
HTML规范
form 表单内部,所有非提交按钮强制写 type 属性
bash
提交按钮:<button type="submit">保存</button>
普通按钮 / 取消 / 重置:<button type="button">取消</button>
禁止 form 内部出现不带 type 的裸 button。
html
<!-- form表单规范示例 -->
<form class="layui-form" action="" autocomplete="off" id="form">
<div class="staff-add-action">
<button class="layui-btn layui-btn-lg save" onclick="sub()" type="button">保存</button>
<button class="layui-btn layui-btn-primary layui-btn-lg cancel" type="button">取消</button>
</div>
</form>
JS 时序规范
bash
不要在 DOM 事件回调中同步销毁当前事件所在的 DOM/iframe。
把销毁 / 移除操作丢到下一轮事件循环,使用setTimeout(fn, 0~30),让浏览器把当前 click/submit 事件完整走完,再销毁 DOM。
javascript
// 错误:同步销毁,事件还没跑完
parent.layer.close(index);
// 正确:等事件循环完成后再销毁
setTimeout(()=> parent.layer.close(index), 20);
javascript
// 取消按钮事件,延时关闭iframe
$(".cancel-btn").on("click", function (e) {
e.preventDefault();
let index = parent.layer.getFrameIndex(window.name);
// 延时执行关闭,避免事件逃逸
setTimeout(() => {
parent.layer.close(index);
}, 20);
});