预览地址 这篇博客意在总结记录Bug的解决和完成组件的过程。
Tabs标签页
<x-tabs :selected.sync="selected">
<x-tabs-item item="经济" name="1">
财经报道
</x-tabs-item>
</x-tabs>
点击变换
按照上面的结构,内容应该放在tabs-item
,那么选择既
就由tabs
控制。
tabs
大致HTML结构
<div class="tabs">
<div class="tabs-header"........>
<divclass="tabs-header-item".........>
// 标签页,这里简称 header-item
//.........
</div>
<div ref="line" class="line" v-if="!cards"></div>
//高亮线条
</div>
<div class="tabs-content">
<slot></slot>
//展示内容
</div>
</div>
由于只有两个组件,所以组件通信就非常简单了。这里不做多的说明
内容切换的动画效果
如同预览所见的(目前所有position只有一种动画效果),有一种轮播切换的赶脚。
遇到的问题
一般这种动画容易造成一些bug,例如
- 新内容进来和旧内容出去在同一时间,会存在挤兑。官网这里由明确的一些解决方案。像是
动态组件
,key
或者mode='xxx'
什么的。但是slot
这种相当麻烦,在不知道插入元素的情况下,很难做到动画的顺畅和没有额外的bug。 - 使用
钩子函数
都设置为绝对定位,结束改回来。但动画期间父元素失去高度。会有抖动的不美观效果。
在这里的解决方法。
首先我去Ant Design看了看。 因为这个动画效果本来就是模仿他的。
大概猜了一下,Ant Design的做法应该是把三组并排,通过控制父级的负margin
控制显示。
只需在移出的时候绝对定位一下就行了
position: absolute;
left: 0;
top: 0;
然后无非就是一个从x(左/右)移入,一个反向移出就行了。
line的位移
line
的位移应该是这部分相对比较麻烦的。之所以选择用div
单独一个元素来做高亮线条,而不是header-item
的border
。是因为
- 更容易变换位置,适合后面的
position
变换 - 可以调整高度和宽度
大概的css
首先父元素肯定要相对定位
.tabs-header{
display: flex;
//....
position: relative;
height: $tab-height;
//....
}
子元素绝对定位
>.line{
position: absolute;
bottom: 0;
left: 0;
//....
}
根据不同的position
,line
完成不同方向和位置的偏移
这样子会写相当多的重复代码,非常不利于阅读和维护,例如。
代码重构:表驱动编程
先做命名上的修改
const {line} = this.$refs
let [left,top] = [item.offsetLeft,item.offsetTop]
let {width,height} = item.getBoundingClientRect()
let positionName
这里面看到其实每个位置的设置都只需要三个属性
,而且top
和bottom
,left
和right
都是一样的设置。
所以
//linemove函数,这名字真的不行,后面改改。
let position = {
topOrBottom:{
width:`${width}px`,
height:0,
transform: `translate(${left}px,0)`
},
leftOrRight:{
width: 0,
height:`${height}px`,
transform: `translateY(${top}px)`
}
}
positionName = this.position === 'left' || this.position === 'right' ? 'leftOrRight' : 'topOrBottom';
line.style.height = position[positionName].height
line.style.width = position[positionName].width
line.style.transform = position[positionName].transform
这样子的修改就使代码可读性更强,一目了然。
line
位置不正确的问题
其实本来是对的,展示组件的时候,position
绑定动态数据,在button
切换的时候,发现line
位置不正确。
watch:{
position(){
this.lineMove()
}
},
这里简单讲一下我对$nextTick
的理解:
return function queueNextTick (cb?: Function, ctx?: Object){
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
//.......
}
} else if (_resolve) {
//... 这里是传入的回调为空且支持【promise】的时候默认选择的【promise】,这在官网里面有讲过
}
})
}
先对传入的回调函数
做一个收集,放进一个callbacks
数组里面
事实上$nextTick
也希望异步任务可以尽快一点执行。所以在api
的选择上是
- 第一步先选择属于
‘微任务’
的promise
,看看浏览器是否原生支持promise
- 不支持则降级为
macroTimerFunc
- 同样在
macroTimerFunc
里根据浏览器的支持程度对这些异步api
做一个选择(setImmediate
,messagechannel
....),反正setTimeout
是最低的。
然后会在下一个tick
执行(flushCallbacks
)对callbacks
做个从头开始的遍历执行(就像是队列一样),这里具体的比较细。反正最终就是依次执行传入的nextTick
就对了。
这样子看来,在我测试这里的情况里应该是选择的promise
,这当然是不行的。
因为动画过渡的影响,在执行的时候,动画也在执行,这期间元素的css属性并不是我想要的。
现在的选择就是 移除展示的动画过渡效果,其实就是展示组件,位置改变的动画没了,其他正常使用的过渡效果依然存在。
最后的代码
mounted(){
this.$nextTick(()=>{
this.lineMove()
//......
})
//.....
}
后面会继续仿着element和Antd的组件效果,实现关于标签页更多功能。 0
博客里面如果有不正确或者错误的地方,希望大佬们可以批评指正。 如果你觉得将就还行,给我的轮子项目一个star就是最大的鼓励了。