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

引言

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

相关推荐
Olrookie2 分钟前
若依前后端分离版学习笔记(二十)——实现滑块验证码(vue3)
java·前端·笔记·后端·学习·vue·ruoyi
533_43 分钟前
[vue] dayjs 显示实时时间
前端·javascript·vue.js
故事与他6451 小时前
XSS_and_Mysql_file靶场攻略
前端·学习方法·xss
莫的感情2 小时前
下载按钮点击一次却下载两个文件问题
前端
一个很帅的帅哥2 小时前
JavaScript事件循环
开发语言·前端·javascript
小宁爱Python2 小时前
Django Web 开发系列(二):视图进阶、快捷函数与请求响应处理
前端·django·sqlite
fox_2 小时前
深入理解React中的不可变性:原理、价值与实践
前端·react.js
Github项目推荐2 小时前
你的错误处理一团糟-是时候修复它了-🛠️
前端·后端
Code小翊2 小时前
C语言bsearch的使用
java·c语言·前端
云枫晖2 小时前
Webapck系列-初识Webpack
前端·javascript