如何优雅地用原生实现一个模态框

引言

徒手实现一个模态框看似简单,实则不然,一是需要处理滚动穿透问题,二是需要处理tabindex的问题,这两个问题都不好处理。

一般UI框架都会自带模态框组件,针对滚动穿透问题,它的做法一般是将外层容器的滚动条屏蔽掉。布局方面,一般是用固定定位+绝对定位。这里边有一个无法解决的问题,就是你永远无法确定模态框是否处于顶层,即便你zindex给的再大,都无法保证这一点。因为始终存在会比这个值更大的可能。

那有没有一种方式可以确保模态框始终处于顶层呢,其实是有的,即利用原生dialog的能力。

代码示例

html结构

html 复制代码
<button onclick="dialog.show()">按钮</button>

<dialog class="dialog" id="dialog">
  <div class="form">
    <div class="form-title">登录</div>
    <div class="form-control">
      <label class="form-control-label">账号: </label>
      <input class="form-control-value" />
    </div>
    <div class="form-control">
      <label class="form-control-label">密码: </label>
      <input type="password" class="form-control-value" />
    </div>
    <button class="btn-submit" @click="dialog.close()">登录</button>
</div>

</dialog>

效果如下:

简单给模态框里面的内容一些样式

css 复制代码
.form{
  width: 420px;
  padding: 20px 30px;
}
.form-title{
  display: flex;
  justify-content: center;
  padding: 20px;
  font-size: 20px;
}
.form-control{
  display: flex;
  align-items: center;
  padding: 12px 0;
  width: 100%;
}
.form-control-label{
  margin-right: 6px;
}
.form-control-value{
  flex: 1;
  height: 32px;
  padding-left: 6px;
  border: 1px solid #ccc;
}
.form-control-value:focus{
  outline: none;
}
.btn-submit{
  display: block;
  margin: 20px auto;
  background-color: rgb(93, 167, 93);
  color: #fff;
  width: 200px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
}

效果如下:

添加蒙层

html结构

html 复制代码
<button onclick="dialog.showModal()">按钮</button>
// ...
</dialog>

只需将dialog.show改为dialog.showModal即可。

再给dialog和蒙层一些样式

css 复制代码
.dialog{
  border-width: 0;
  border-radius: 6px;
}


dialog::backdrop{
  background-color: #32586823; // 给蒙层一个灰色背景
  backdrop-filter: blur(1px); // 添加毛玻璃效果
}

效果如下:

看起来好多了,但略显生硬。考虑是否可以给dialog添加一个过渡效果。

很遗憾,只通过样式是做不到的。

因为动画的本质是数字的变化,而dialog这里的显示和节点的创建和销毁,并非数字的变化。

细心的小伙伴已经发现了,这个dialog从层级上来看是处于当前文档节点之外的一个游离节点。

既然动画的本质是数字的变化,那我们是否可以通过改变这个dialog的透明度(opacity)来巧妙地做到过渡动画呢?

代码实现

ts 复制代码
<script setup lang="ts">

function handleOpen() {
  const dialog = document.getElementById('dialog')! as any
  
  // 打开对话框时
  dialog.showModal();
  requestAnimationFrame(() => {
    dialog.classList.add('open');
  });
}

function handleSubmit() {
  const dialog = document.getElementById('dialog')! as any

  // 关闭对话框时
    
  dialog.classList.remove('open');
  setTimeout(() => {
    dialog.close()

  }, 200);
}
</script>
html 复制代码
<template>
  <button @click="handleOpen">按钮</button>

  <dialog class="dialog" id="dialog">
    <div class="form">
      <div class="form-title">登录</div>
      <div class="form-control">
        <label class="form-control-label">账号: </label>
        <input class="form-control-value" />
      </div>
      <div class="form-control">
        <label class="form-control-label">密码: </label>
        <input type="password" class="form-control-value" />
      </div>
      <button class="btn-submit" @click="handleSubmit">登录</button>
    </div>

  </dialog>
</template>

样式稍作调整

css 复制代码
.dialog{
  border-width: 0;
  border-radius: 6px;
  transition-duration: .5s;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.3s ease, transform 0.3s ease;
}
dialog.open {
  opacity: 1;
  transform: scale(1);
}

dialog.open::backdrop{
  opacity: 1;
  transform: scale(1);
  background-color: #32586823;
  backdrop-filter: blur(1px);
}
.dialog::backdrop{
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

效果如下:

这样看起来是不是好多了。

总结

通过原生实现模态框,一方面可以解决zindex层级覆盖的问题,二来也能规避掉滚动穿透和tabindex跑到蒙层下面的问题。由于是浏览器原生支持的,所以性能也会更好。

相关推荐
爱嘿嘿的小黑9 分钟前
docker 常用命令
前端
dangfulin11 分钟前
CSS——变换、过度与动画
前端·css
南屿欣风22 分钟前
解决 Gin Web 应用中 Air 热部署无效的问题
前端·gin
猿大师办公助手25 分钟前
Web网页内嵌福昕OFD版式办公套件实现在线预览编辑PDF、OFD文档
前端·pdf·word
幼儿园技术家1 小时前
什么是RESTful 或 GraphQL?
前端
echola_mendes2 小时前
LangChain 结构化输出:用 Pydantic + PydanticOutputParser 驯服 LLM 的“自由发挥”
服务器·前端·数据库·ai·langchain
拉不动的猪2 小时前
刷刷题46(常见的三种js继承类型及其优缺点)
前端·javascript·面试
关注我:程序猿之塞伯坦2 小时前
JavaScript 性能优化实战:突破瓶颈,打造极致 Web 体验
开发语言·前端·javascript
兰德里的折磨5502 小时前
对于后端已经实现逻辑了,而前端还没有设置显示的改造
前端·vue.js·elementui
hikktn2 小时前
【开源宝藏】30天学会CSS - DAY9 第九课 牛顿摆动量守恒动画
前端·css·开源