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

引言

徒手实现一个模态框看似简单,实则不然,一是需要处理滚动穿透问题,二是需要处理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跑到蒙层下面的问题。由于是浏览器原生支持的,所以性能也会更好。

相关推荐
counterxing3 小时前
我整理了一个免费开发资源目录,还做成了 CLI 和 MCP
前端·agent·ai编程
子兮曰9 小时前
Bun v1.3.14 深度解析:Image API、HTTP/3、全局虚拟存储与五十项变革
前端·后端·bun
kyriewen10 小时前
今天,百年巨头一次砍了9200人,而一个离职科学家的实话让全网睡不着觉
前端·openai·ai编程
问心无愧051311 小时前
ctf show web 入门42
android·前端·android studio
kyriewen11 小时前
老板逼我上AI,我偷偷在浏览器里跑LLaMA,省下20万API费
前端·react.js·llm
Beginner x_u11 小时前
前端八股整理(手写 02)|数组转树、数组扁平化、随机打乱一个数组
前端·数组·数组转树·数组扁平化
KaMeidebaby11 小时前
卡梅德生物技术快报|禽类成纤维细胞 FISH 实验:鸟类性别染色体基因定位技术实现与数据验证
前端·数据库·其他·百度·新浪微博
天若有情67312 小时前
前端高阶性能优化:跳出传统懒加载与预加载,基于用户行为做轻量预判加载
前端·性能优化
小小小小宇12 小时前
前端转后端:SQL 是什么
前端
张元清13 小时前
React Observer Hooks:7 种监听 DOM 而不写样板代码的方式
前端·javascript·面试