Modal模态框开发避坑指南与性能优化实践
项目初期的技术选型
最近刚结束了一个后台管理系统,客户要求实现一个多功能的Modal模态框组件。说实话,当时觉得这需求挺常规的,不就是弹个框嘛。但实际做下来才发现,这里面门道不少。
考虑到项目使用的是Vue3技术栈,我决定基于Composition API来封装这个Modal组件。一方面是为了代码复用性高,另一方面也是想练练手。毕竟现在前端框架更新这么快,多掌握些新特性总是好的。
最大的坑:性能问题
开始写的时候还挺顺的,基本功能很快就实现了。但当我在页面中同时打开多个Modal时,发现页面明显卡顿了。通过Chrome DevTools分析,发现问题出在每次打开Modal都会重新渲染整个组件树。
折腾了半天才发现,原来是我把所有逻辑都写在setup函数里,导致每次打开都要重新执行一遍。后来调整了方案,把一些静态配置提取到外部,只保留必要的响应式数据在组件内部。
// 优化后的Modal组件核心代码
import { ref, watchEffect } from 'vue'
export default {
props: {
visible: Boolean,
content: String
},
setup(props) {
const isVisible = ref(props.visible)
watchEffect(() => {
isVisible.value = props.visible
})
const close = () => {
isVisible.value = false
}
return { isVisible, close }
}
}
又踩坑了,touchmove滚动失效
你以为这就完了?更头疼的问题还在后面。当Modal打开时,页面背景还是会随着用户的手势滑动。这在手机上体验特别差,客户也反馈说这问题必须解决。
最开始我想简单粗暴地用overflow:hidden来禁止背景滚动,但这会导致页面抖动,用户体验更差了。最后采用了比较取巧的方法,在body上加了个固定定位:
/* Modal样式处理 */
.modal-open {
position: fixed;
width: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
// 在组件中控制body样式
watch(isVisible, (newVal) => {
if (newVal) {
document.body.classList.add('modal-open')
} else {
document.body.classList.remove('modal-open')
}
})
数据交互的意外状况
项目中还遇到了一个很奇葩的问题。Modal里的表单需要调用接口获取初始化数据,但有时会因为网络延迟导致数据显示异常。开始没想到会有这种特殊情况,结果测试环境一切正常,上线后却频频报错。
后来加了个loading状态控制,并且对接口返回做了更严格的校验。虽然改完后还是有极小概率会出现数据不同步的情况,但至少不会影响主流程了。
const fetchData = async () => {
try {
loading.value = true
const res = await fetch('https://jztheme.com/api/data')
if (res.status === 200) {
data.value = await res.json()
}
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
最终的解决方案
经过几轮优化,现在的Modal组件已经能满足大部分场景了。支持动态内容注入、异步加载、多种尺寸切换等功能。不过还是有些小遗憾,比如动画效果还可以更流畅些,键盘导航的支持也不够完善。
这里分享下完整的核心代码,亲测有效:
<template>
<div v-if="isVisible" class="modal-overlay">
<div class="modal-content">
<button @click="close">关闭</button>
<slot></slot>
</div>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
props: {
visible: Boolean
},
emits: ['update:visible'],
setup(props, { emit }) {
const isVisible = ref(props.visible)
watchEffect(() => {
isVisible.value = props.visible
})
const close = () => {
isVisible.value = false
emit('update:visible', false)
}
return { isVisible, close }
}
}
</script>
<style>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: #fff;
padding: 20px;
border-radius: 8px;
max-width: 90%;
max-height: 90%;
overflow: auto;
}
</style>
回顾与反思
总的来说,这个Modal组件的开发过程让我学到了不少东西。从最初的简单封装,到最后考虑各种边界情况,确实花了不少时间。但也正因为这些踩坑经历,让我对组件设计有了更深的理解。
- 做得好的地方:组件的可复用性和性能都有不错的提升
- 还能改进的地方:动画效果和无障碍支持可以继续优化
- 踩坑提醒:一定要重视移动端的特殊场景,很多PC端没问题的功能在手机上可能会出幺蛾子
以上是我在项目中使用Modal模态框的一些实战经验,希望对你有帮助。有更好的实现方式欢迎评论区交流,或者你遇到过什么奇葩问题也可以一起吐槽下。

暂无评论