Vue子组件懒加载,页内导航,平滑过渡
需求与实现思路
想实现类似 elementUI tabs 那种效果, 但是是在单个容器内部,且支持懒加载,点击右侧(点击时才加载对应组件)的nav item 可以跳转到对应的组件区域。(并且在加载后自动跳转到对应区域)
懒加载实现方式
本例使用条件渲染控制方式,缺点:
-
需要额外配置
v-if
的绑定 -
似乎不是真正意义上的懒加载(似乎)(只是心里过不去而已,毕竟多了个
v-if
)
平滑过渡实现方式
mukashi
mukashi
在很久很久以前,瞟过一眼张鑫旭大佬的文章,今天碰到类似的需求,突然想到这个效果,就去找了下,还好它还在:
CSS scroll-vehavior:smooth; 实现平滑过渡 👈 你可以狠狠的点击这里进行查看
懒的看这里:[要点]
- 父容器
overflow:hidden
- 父容器
scroll-vehavior:smooth;
- 使用
label for input
ora herf to target
都行。(不过 a 链接的写hash还是算了吧) - 子组件中的
input
一定要height:100%
不然一个视口内会出现其它元素。。。 - clip 属性剪裁绝对定位元素。
该属性缺点:
- 有本事给我支持 贝塞尔曲线啊!
- 不想提到的兼容性
兼容性
Go to Can I use 除了 safari 不太给面子,其它的还行
本次用于B端需求,浏览器谁还会用IE?大不了没有这个效果的时候僵硬的跳转一下。
index.vue
<template>
<template>
<div class="smart-cashier-wrapper">
<div class="smart-container">
<payment-side-manage >
<input id="payment-side-manage" slot="holder">
</payment-side-manage>
<mode-of-payment v-if="isShow.modeOfPayment" ref="modeOfPayment">
<input id="mode-of-payment" slot="holder">
</mode-of-payment>
<cashier-config v-if="isShow.cashierConfig" ref="cashierConfig">
<input id="cashier-config" slot="holder">
</cashier-config>
</div>
<div class="smart-nav">
<label class="smart-to" for="payment-side-manage">支付端管理</label>
<label class="smart-to" for="mode-of-payment" @click.once="handleChildComponentCreated('modeOfPayment')">支付方式管理</label>
<label class="smart-to" for="cashier-config" @click.once="handleChildComponentCreated('cashierConfig')">收银台配置</label>
</div>
</div>
</template>
- 使用了具名插槽,是因为想将
label
与input
的关系更加亲密 once
修饰符确保只加载一次- 第一个子元素默认加载。所以不给
v-if
<script>
<script>
import CashierConfig from "./child/cashierConfig";
import ModeOfPayment from "./child/modeOfPayment";
import PaymentSideManage from "./child/paymentSideManage";
export default {
name: "smartCashier",
components: {PaymentSideManage, ModeOfPayment, CashierConfig},
data(){
return {
isShow:{
"cashierConfig":false,
"modeOfPayment":false
}
}
},
methods:{
handleChildComponentCreated(refName){
this.isShow[refName] = true
this.$nextTick(()=>{
/* 不管用
this.$refs[refName].$el.click()
*/
/* 不管用
let newEvent = document.createEvent('HTMLEvents');
newEvent.initEvent('click', false,false);
this.$refs[refName].$el.dispatchEvent(newEvent)
*/
/*scrollIntoView 牛逼!!*/
this.$refs[refName].$el.scrollIntoView()
})
}
}
}
</script>
- 拿
ref
而不是直接拿DOM
$nextTick
微任务搞起,是因为直接执行你就会败北
<style>
<style scoped lang="scss">
.smart-cashier-wrapper{
width: calc(100% - 200px);
height: 100vh;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
position: relative;
*border:1px solid red;
.smart-container{
flex-grow: 1;
height: 100%;
max-height: 100vh;
border:1px solid red;
overflow: hidden;
scroll-behavior: smooth;
}
.smart-nav{
width: 200px;
height: 100vh;
border:1px solid red;
position: fixed;
right: 0;
.smart-to{
display: block;
width: 100%;
height: 50px;
color: black;
text-align: center;
line-height: 50px;
font-weight: bold;
}
}
}
</style>
中间有很多这种
*border:1px solid red;
是方便我调试的时候能看到元素到底在哪里,也可以像下面这样玩。不用的时候就加上*
.componentName-wraper{
*{
border:1px solid red;
}
}
child.vue
<template>
<div class="modeOfPayment-wrapper">
<slot name="holder"></slot>
</div>
</template>
<script>
export default {
name: "modeOfPayment",
created() {
console.log("支付方式 was created")
}
}
</script>
<style scoped lang="scss">
.modeOfPayment-wrapper{
width: 100%;
height: 100%;
border:1px solid red;
overflow: hidden;
position: relative;
> input{
position: absolute;
top: 0;
height: 100%;
width: 1px;
border:0;
padding: 0;
margin: 0;
clip: rect(0 0 0 0);
}
}
</style>
在生命周期 hook 中加入调试输出,可以看到加载效果。
踩坑总结
-
slot v-slot 具名插槽的使用 受到版本号影响
-
受到事件是否可信的 标识
isTrusted
的影响(发现脚本触发的点击事件无效后)Element.click()
救不了我dispatchEvent()
救不了我
-
scrollIntoView
牛逼 !!! -
https://developer.mozilla.org/zh-CN/docs/Web/API/Event/isTrusted 去认识一下