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

引言

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

相关推荐
0思必得01 小时前
[Web自动化] Selenium处理动态网页
前端·爬虫·python·selenium·自动化
东东5161 小时前
智能社区管理系统的设计与实现ssm+vue
前端·javascript·vue.js·毕业设计·毕设
catino1 小时前
图片、文件的预览
前端·javascript
layman05283 小时前
webpack5 css-loader:从基础到原理
前端·css·webpack
半桔3 小时前
【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典
前端·css·html
AI老李3 小时前
PostCSS完全指南:功能/配置/插件/SourceMap/AST/插件开发/自定义语法
前端·javascript·postcss
_OP_CHEN3 小时前
【前端开发之CSS】(一)初识 CSS:网页化妆术的终极指南,新手也能轻松拿捏页面美化!
前端·css·html·网页开发·样式表·界面美化
啊哈一半醒3 小时前
CSS 主流布局
前端·css·css布局·标准流 浮动 定位·flex grid 响应式布局
PHP武器库3 小时前
ULUI:不止于按钮和菜单,一个专注于“业务组件”的纯 CSS 框架
前端·css
电商API_180079052474 小时前
第三方淘宝商品详情 API 全维度调用指南:从技术对接到生产落地
java·大数据·前端·数据库·人工智能·网络爬虫