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

引言

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

相关推荐
秋名山大前端6 小时前
Chrome GPU 加速优化配置(前端 3D 可视化 / 数字孪生专用)
前端·chrome·3d
今天不要写bug6 小时前
antv x6实现封装拖拽流程图配置(适用于工单流程、审批流程应用场景)
前端·typescript·vue·流程图
luquinn6 小时前
实现统一门户登录跳转免登录
开发语言·前端·javascript
用户21411832636027 小时前
dify案例分享-5分钟搭建智能思维导图系统!Dify + MCP工具实战教程
前端
augenstern4167 小时前
HTML(面试)
前端
excel7 小时前
前端常见布局误区:1fr 为什么撑爆了我的容器?
前端
烛阴7 小时前
TypeScript 类型魔法:像遍历对象一样改造你的类型
前端·javascript·typescript
vayy7 小时前
uniapp中 ios端 scroll-view 组件内部子元素z-index失效问题
前端·ios·微信小程序·uni-app
专注API从业者7 小时前
基于 Node.js 的淘宝 API 接口开发:快速构建异步数据采集服务
大数据·前端·数据库·数据挖掘·node.js
前端无冕之王7 小时前
一份兼容多端的HTML邮件模板实践与详解
前端·css·数据库·html