在做jointjs的vue组件的时候发现一个问题,使用element ui的tabs组件加载多个paper的时候,除了当前页面正常之外,其他被隐藏的tab的比例scale都不正常,网上查了半天才知道jointjs在display: none的状态下不能正常渲染。
原因是:The problem is that the methods for getting the bounding box of an HTML/SVG element only work when the element is in the DOM and is visible.
也就是说jointjs确定元素边界的时候需要依赖dom,display: none设置后dom就不再渲染,导致比例不正常。
详情请参照下面
Incorrect scaling when trying to draw node in hidden div. · Issue #262 · clientIO/joint · GitHub
这篇文章中提到的 visibility: hidden方法有占位,隐藏后会出现空白位置,这不是我们想要的。正确的做法是position: absolute; left: -99999em,使用绝对定位不占位,移到可见区域之外,这样dom会继续渲染,就不影响jointjs了,echarts估计也是一样的。
知道了问题所在,下面就是如何实现,有文章提到使用iview的tabs组件,这个我试过,确实可以,但是有2个副作用:
1.必须使用animated进行动画切换,如果关掉animated就会回到display: none方式上,而且各个pane的高度互相影响,都显示成最大高度,这个应该是没有使用绝对定位造成的高度占位,用户体验较差。
2.我的环境是element ui,虽然可以单独import这个组件,但css要全部引入,这样就导致css冲突,要解决这个问题也很麻烦。
最终回到修改element ui的路上,所幸el-tab-pane代码少,并且没有依赖,非常方便修改
源代码可以在这里找到
node_modules/element-ui/packages/tabs/src/tab-pane.vue
<template>
<div
class="el-tab-pane"
v-if="(!lazy || loaded) || active"
v-show="active"
role="tabpanel"
:aria-hidden="!active"
:id="`pane-${paneName}`"
:aria-labelledby="`tab-${paneName}`"
>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'ElTabPane',
componentName: 'ElTabPane',
props: {
label: String,
labelContent: Function,
name: String,
closable: Boolean,
disabled: Boolean,
lazy: Boolean
},
data() {
return {
index: null,
loaded: false
};
},
computed: {
isClosable() {
return this.closable || this.$parent.closable;
},
active() {
const active = this.$parent.currentName === (this.name || this.index);
if (active) {
this.loaded = true;
}
return active;
},
paneName() {
return this.name || this.index;
}
},
updated() {
this.$parent.$emit('tab-nav-update');
}
};
</script>
其中控制display: none的是下面这句
v-show="active"
替换成下面这样即可
:style="active?'':'position: absolute; left: -99999em;'"
修改后代码
<template>
<div
v-if="(!lazy || loaded) || active"
:id="`pane-${paneName}`"
:style="active?'':'position: absolute; left: -99999em;'"
class="el-tab-pane"
role="tabpanel"
:aria-hidden="!active"
:aria-labelledby="`tab-${paneName}`"
>
<slot />
</div>
</template>
<script>
export default {
name: 'ElTabPane',
componentName: 'ElTabPane',
props: {
label: String,
labelContent: Function,
name: String,
closable: Boolean,
disabled: Boolean,
lazy: Boolean
},
data() {
return {
index: null,
loaded: false
}
},
computed: {
isClosable() {
return this.closable || this.$parent.closable
},
active() {
const active = this.$parent.currentName === (this.name || this.index)
if (active) {
// eslint-disable-next-line
this.loaded = true
}
return active
},
paneName() {
return this.name || this.index
}
},
updated() {
this.$parent.$emit('tab-nav-update')
}
}
</script>
然后在自己代码里引入修改后的组件,就可以替换element UI原来的tabs组件,用法和原来一样非常方便。
import ElTabPane from './tabs/src/tab-pane.vue'
...
export default {
components: {
ElTabPane,
...
后记:
修改就一句非常简单,但是当时脑袋短路,没有意识到v-show就是控制display: none,还以为是在其他地方加上的,反复读tabs的其他代码都没有发现哪里有修改display: none,最后才发现是v-show在作怪,修改后完美渲染jointjs。