Skip to content
字数
1167 字
阅读时间
5 分钟

背景

在最开始使用 el-dialog 的时候发现,它会在 body 标签下插入一个 div 遮罩层,而不是像一般组件一样,在 vue 的组件内容 <div id="app"></div> 内进行层级嵌套内容,自己发现后也没有去细追这样设计的原因

然后有次面试就被问了......

现在回来清算旧账

dialog 为什么在 body 层

html 文档中,子div 的大小常常由 父div 限制,而遮罩层因为其特殊性,常常需要占满全屏空间,所以将 遮罩层的 div 插入到 vueApp 的 div 同级目录下,也就是 body 层下

子 div 突破父 div 大小限制

这里使用绝对布局来实现

html 代码:

html
<html>
<div class="div1">
 <div class="div2">
  <div class="div3">
  </div>
 </div>
 
</div>
</html>

css 代码:

css
<style>
 .div1{
  border-style:solid;
     border-width:1px;
  width: 400px;
  height: 300px;
  position:relative;
 }
 .div2{
  border-style:solid;
     border-width:1px;
  width: 200px;
  height: 100px;
  margin-left:50px;
  margin-top: 50px;
 }
 .div3{
  width:100%;
  height:100%;
  background-color:red;
  position:absolute;
  margin-left:-50px;
  margin-top: -50px;
  
 }
</style>

div3 受 div2 大小限制,用 position 突破,在 div2 前添加 div1,div1 使用 position:relative;
div3 使用 position:absolute;
由于 div2 没有使用 position,所以 div3 width:100%;height:100%; 时跟随 div1 大小,但是位置还是 div2 的起始位置,所以 div3 使用 margin-left:-50px;margin-top: -50px; 就改变到 div1 的相对位置

popover 和 tooltip 为什么在 body 下

相比于 dialog,element-ui 的 popover 和 tooltip 也是默认在 body 下创建的

为什么?

以 tooltip 为例,在.vue 文件中真正的弹层是放在组件节点下面的,使用 ref="popper" 来引用
在初始化的时候通过 this.$refs.popper 传递给 new Popper 创建,这样的话所有的弹层都在父节点下,会导致在 table 中弹出的层无法突破 z-index 的限制被 overflow 掉,展示不全

但在真实业务场景中,这两个组件在 body 下有很多界面不友好的问题,这个时候一般都将组件设置在特定的 父div

指定 tooltip 的生成 dom 位置

js
<template>
    <div>
        <el-tooltip
                placement="top"
                :append-to-body="false" <!-- 尤其重要参数 -->
                content="你好ToolTip"
                ref="mypop">  <!-- 为方便寻找,使用ref直接拿 -->
            <el-button>提示</el-button>
        </el-tooltip>
        <!-- 希望将tooltip的弹出部分内容的dom放在这里面 -->
        <div ref="here"></div>
    </div>
</template>
<script>
    export default {
        mounted() {
            // 在mounted的时候只需要使用这句话。
            this.$refs.here.appendChild(
                this.$refs.mypop.popperVM.$el
            )
        }
    }
</script>

指定 dropdown

也是利用 refs 的特性

js
<template>
    <div>
        <el-dropdown>
            <span class="el-dropdown-link">
                下拉菜单<i class="el-icon-arrow-down el-icon--right"></i>
            </span>
            <!--同样是将append-to-body参数设置为false-->
            <el-dropdown-menu slot="dropdown" :append-to-body="false" ref="mydropd">
                <el-dropdown-item>黄金糕</el-dropdown-item>
                <el-dropdown-item>狮子头</el-dropdown-item>
                <el-dropdown-item>螺蛳粉</el-dropdown-item>
                <el-dropdown-item disabled>双皮奶</el-dropdown-item>
                <el-dropdown-item divided>蚵仔煎</el-dropdown-item>
            </el-dropdown-menu>
        </el-dropdown>
        <div ref="here"></div>
    </div>
</template>
<script>
    export default {
        mounted() {
            this.$refs.here.appendChild(
                this.$refs.mydropd.popperElm
            )
        }
    }
</script>

实现原理

通过阅读这两个组件的源代码可以发现它们都是 minxin 了 vue-popper 这个公共类,使他们都具备这样的弹出式窗口的功能。部分关键源代码如下:

js
// dropdown-menu.vue 的部分源码
import Popper from 'element-ui/src/utils/vue-popper';
export default {
  name: 'ElDropdownMenu',
  componentName: 'ElDropdownMenu',
  mixins: [Popper],  // <=== 这里
  props: {
    visibleArrow: {
      type: Boolean,
      ...

而 vue-popper 组件提供了 appendToBody 参数,它默认值是 true,也就是默认就会把弹出的内容添加到 body 根节点。部分重要代码入下:

js
// vue-popper.js 的部分代码
methods: {
createPopper() {
  ...
  if (!popper || !reference) return;
  if (this.visibleArrow) this.appendArrow(popper);
  // 这里可以看到,根据参数来决定是否要把弹出的内容显示添加到body根节点。
  if (this.appendToBody) document.body.appendChild(this.popperElm);
  ...

一旦我们将 appendToBody 参数设置为了 false,那弹出的内容根本就不会出现在界面上了,所以我们需要自己在 mounted 生命周期函数里手动将对应的弹出内容添加到界面上,经过简单的代码阅读,不难发现:

对于 tooltip 组件,内部的 popperVM.$el 就是弹出内容。
对于 dropdown 组件,内部的 popperElm 就是弹出内容。
只需要将它们添加到我们自己想要的地方即可。

而 element-ui 里有很多组件都 minxin 了 vue-popper,应该都可以使用类似的方法去将内容放在指定位置。下面简单罗列了那些组件使用了 vue-popper:

submenu、autocomplete-suggestions、cascader、picker-dropdown、picker、select-dropdown、filter-panel、popover

参考

使用 position突破父元素大小限制_Incomplete

tooltip弹层为什么不放在body下面,或者是否可以全部放到body下面? · Issue #296 · ElemeFE/element · GitHub

【Microanswer】-elementui将tooltip和dropdown显示到指定dom而非body根节点

贡献者

The avatar of contributor named as jiechen jiechen

页面历史

撰写