很多教程的手風(fēng)琴組件都是一個(gè)v-for數(shù)組來實(shí)現(xiàn)手風(fēng)琴組件,v-for封裝起來很簡(jiǎn)單毡们,但是我認(rèn)為并不好婉支。
理由如下:
- 這種方式很不優(yōu)雅
- 其實(shí)我沒用過這種實(shí)現(xiàn)方式的手風(fēng)琴稽穆,不知道到底怎么實(shí)現(xiàn)配合router來實(shí)現(xiàn)展開和激活?感覺v-for這種實(shí)現(xiàn)手風(fēng)琴的方式實(shí)現(xiàn)這個(gè)功能不太容易米丘。
- 無法靈活嵌套
所以自己用自定義指令實(shí)現(xiàn)了一個(gè)手風(fēng)琴組件鳞疲。
代碼很長(zhǎng),不想學(xué)習(xí)的可以直接github下載代碼run serve直接使用蠕蚜。
-
先看效果:
- 支持初始化撐開多個(gè)折疊版,點(diǎn)擊任意一個(gè)之后會(huì)將其他撐開的都關(guān)閉尚洽。
- 初始撐開為props 參數(shù)visible=true
- 后續(xù)會(huì)更新支持多個(gè)不同手風(fēng)琴,支持開啟手風(fēng)琴模式
結(jié)構(gòu)
-
邊側(cè)導(dǎo)航欄結(jié)構(gòu)
-
<!-- 由于本身是封裝的一個(gè)邊側(cè)導(dǎo)航欄,所有組件中有
NLY-accordionNav
NLY-accordionNavItem
NLY-accordionNavTree
-->
<NLY-accordionNav>
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
Nejinn
</NLY-accordionNavItem>
<NLY-accordionNavTree icon="nlyblog nly-blog-book" v-nly-accordion.sss>
Nejinn
</NLY-accordionNavTree>
<NLY-accordionNavCollapse id="sss" visible>
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
lerity
</NLY-accordionNavItem>
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
blog
</NLY-accordionNavItem>
</NLY-accordionNavCollapse>
</NLY-accordionNav>
-
只有手風(fēng)琴折疊版的結(jié)構(gòu)
-
<任意元素 v-nly-accordion.collapseId>
</任意元素元素>
<NLY-accordionNavCollapse id='collapseId'>
...嵌套元素,隨意插入
</NLY-accordionNavCollapse>
demo:
<div v-nly-accordion.collapse1>點(diǎn)擊我收起或展開 collapse1</div>
<NLY-accordionNavCollapse id='collapse1'>
<a>我是折疊版中的元素</a>
</NLY-accordionNavCollapse>
<div v-nly-accordion.collapse2>點(diǎn)擊我收起或展開collapse2</div>
<NLY-accordionNavCollapse id='collapse2'>
<a>我是折疊版中的元素</a>
</NLY-accordionNavCollapse>
使用這種結(jié)構(gòu)的時(shí)候,請(qǐng)注意自己寫css靶累∠俸粒可以在accordion.vue中修改就行。
組件目錄結(jié)構(gòu)
自定義指令 v-nly-accordion
- nlyaccordion.js
// nlyaccordion.js
import Vue from "vue";
/**
* 差集函數(shù)
*/
function getDifference(allCollapseId, idKeys) {
let mixArray = [];
allCollapseId.forEach(item => {
if (idKeys.indexOf(item) == -1) {
mixArray.push(item);
}
});
return mixArray;
}
Vue.directive("nly-accordion", function(el, binding, vnode) {
/**
* 初始化指令時(shí)監(jiān)控collapseStatus事件,collapseStatus事件由NLY-accordionNavCollapse組件發(fā)出,有2個(gè)參數(shù),
* 一個(gè)是NLY-accordionNavCollapse事件props參數(shù)id,
* 一個(gè)是NLY-accordionNavCollapse折疊狀態(tài)show
* function(a,b)中a是show,b是id
*/
vnode.context.$root.$on("collapseStatus", function(a, b) {
// 將所有提交collapseStatus事件的NLY-accordionNavCollapse組件的id放入allCollapseId
if (allCollapseId.indexOf(b) == -1) {
allCollapseId.push(b);
}
/**
* 獲取當(dāng)前指令的modifiers,如果當(dāng)前指令的modifiers中包含提交collapseStatus事件的NLY-accordionNavCollapse組件的id
* 則初始化掛載指令的組件或者element的class挣柬,且修改當(dāng)前指令modifiers為對(duì)應(yīng)的show值
* 對(duì)應(yīng)的初始化show為true,則在當(dāng)前掛載指令的element的class中添加open
* 對(duì)應(yīng)的初始化show為false,則在當(dāng)前掛載指令的element的class中移除open
*/
let idKeys = Object.keys(binding.modifiers);
if (idKeys.indexOf(b) != -1) {
binding.modifiers[String(b)] = a;
if (a) {
el.classList.add("open");
} else {
el.classList.remove("open");
}
}
});
/**
* 新建一個(gè)array儲(chǔ)存組件NLY-accordionNavCollapse的id
* 注意會(huì)先執(zhí)行這里的代碼再執(zhí)行上面的代碼潮酒。
*/
let allCollapseId = [];
/**
* 點(diǎn)擊事件
*/
el.onclick = function() {
// 獲取指令的modifiers
let idKeys = Object.keys(binding.modifiers);
// 求出modifiers和儲(chǔ)存所有id的數(shù)組的差集
let mixArray = getDifference(allCollapseId, idKeys);
/**
* 循環(huán)當(dāng)前指令的modifiers,并循環(huán)當(dāng)前掛載指令實(shí)例的父組件的所有子組件
* 以組件的id找出指令對(duì)應(yīng)的組件,執(zhí)行對(duì)應(yīng)的展開折疊動(dòng)作
*/
idKeys.forEach(idKeysItem => {
vnode.componentInstance.$parent.$children.forEach(childrenItem => {
if (childrenItem.id == idKeysItem) {
if (binding.modifiers[idKeysItem]) {
childrenItem.show = false;
el.classList.remove("open");
} else {
childrenItem.show = true;
el.classList.add("open");
}
}
});
});
/**
* 判斷當(dāng)前指令的modifiers是否有對(duì)應(yīng)的NLY-accordionNavCollapse組件
* 如果有就執(zhí)行關(guān)閉其他NLY-accordionNavCollapse組件的動(dòng)作,如果沒有,就不進(jìn)行操作
*/
idKeys.forEach(idKeysItem => {
if (allCollapseId.indexOf(idKeysItem) != -1) {
mixArray.forEach(mixArrayItem => {
vnode.componentInstance.$parent.$children.forEach(childrenItem => {
if (childrenItem.id == mixArrayItem) {
childrenItem.show = false;
el.classList.remove("open");
}
});
});
}
});
};
});
動(dòng)畫過渡組件
- 動(dòng)畫過渡組件是借鑒element-ui的,但是說實(shí)話邪蛔,我不是很喜歡這個(gè)急黎。
- collapse動(dòng)畫在NLY-accordionNavCollapse組件中引入
<script>
import collapse from "./collapse.js";
export default {
name: "AccordionNavCollapse",
components: {
collapse: collapse
},
- collapse.js
// collapse.js
const elTransition =
"0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out";
const Transition = {
"before-enter"(el) {
el.style.transition = elTransition;
if (!el.dataset) el.dataset = {};
el.dataset.oldPaddingTop = el.style.paddingTop;
el.dataset.oldPaddingBottom = el.style.paddingBottom;
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
},
enter(el) {
el.dataset.oldOverflow = el.style.overflow;
if (el.scrollHeight !== 0) {
el.style.height = el.scrollHeight + "px";
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
} else {
el.style.height = "";
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
}
el.style.overflow = "hidden";
},
"after-enter"(el) {
el.style.transition = "";
el.style.height = "";
el.style.overflow = el.dataset.oldOverflow;
},
"before-leave"(el) {
if (!el.dataset) el.dataset = {};
el.dataset.oldPaddingTop = el.style.paddingTop;
el.dataset.oldPaddingBottom = el.style.paddingBottom;
el.dataset.oldOverflow = el.style.overflow;
el.style.height = el.scrollHeight + "px";
el.style.overflow = "hidden";
},
leave(el) {
if (el.scrollHeight !== 0) {
el.style.transition = elTransition;
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
}
},
"after-leave"(el) {
el.style.transition = "";
el.style.height = "";
el.style.overflow = el.dataset.oldOverflow;
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
}
};
export default {
name: "collapseTransition",
functional: true,
render(h, { children }) {
const data = {
on: Transition
};
return h("transition", data, children);
}
};
組件
NLY-accordionNav
// AccordionNav.vue
<template>
<nav class="nly-blog-sider-nav flex-column">
<ul class="nly-blog-sider-menu flex-column">
<slot />
</ul>
</nav>
</template>
<script>
export default {
name: "AccordionNav"
};
</script>
NLY-accordionNavItem
// AccordionNavItem.vue
<template>
<li class="nly-blog-sider-menu-item">
<a class="nly-blog-sider-menu-title">
<i :class="iconClass" v-if="icon"> </i>
<p>
<slot />
</p>
</a>
</li>
</template>
<script>
export default {
name: "AccordionNavItem",
props: {
icon: {
type: String
}
},
computed: {
iconClass: function() {
return ["nly-blog-sider-menu-icon", this.icon];
}
}
};
</script>
NLY-accordionNavTree
// AccordionNavTree.vue
<template>
<li class="nly-blog-sider-menu-item">
<a class="nly-blog-sider-menu-title">
<i :class="iconClass" v-if="icon"> </i>
<p>
<slot />
</p>
<i class="nly-blog-sider-menu-arrow"> </i>
</a>
</li>
</template>
<script>
export default {
name: "AccordionNavTree",
props: {
icon: {
type: String
}
},
computed: {
iconClass: function() {
return ["nly-blog-sider-menu-icon", this.icon];
}
}
};
</script>
NLY-accordionNavCollapse
// AccordionNavCollapse.vue
<template>
<collapse>
<ul class="nly-blog-sider-menu menu-tree" v-show="show">
<slot />
</ul>
</collapse>
</template>
<script>
import collapse from "./collapse.js";
export default {
name: "AccordionNavCollapse",
components: {
collapse: collapse
},
data() {
return {
show: this.visible
};
},
model: {
prop: "visible",
event: "input"
},
props: {
visible: {
type: Boolean,
default: false
},
id: {
type: [String, Number]
}
},
created() {
this.show = this.visible;
this.$nextTick(function() {
this.emitState();
});
},
computed: {},
methods: {
emitState: function emitState() {
// 告訴指令當(dāng)前id和show
this.$root.$emit("collapseStatus", this.show, this.id);
}
},
mounted() {},
watch: {
visible: function(newval, oldval) {
if (newval != oldval) {
this.show = newval;
}
},
show: function show(newVal, oldVal) {
if (newVal !== oldVal) {
this.emitState();
}
}
}
};
</script>
注冊(cè)組件
- index.js
import AccordionNav from "./AccordionNav.vue";
import AccordionNavItem from "./AccordionNavItem.vue";
import AccordionNavTree from "./AccordionNavTree.vue";
import AccordionNavCollapse from "./AccordionNavCollapse.vue";
export default {
install: Vue => {
Vue.component("NLY-accordionNav", AccordionNav);
Vue.component("NLY-accordionNavItem", AccordionNavItem);
Vue.component("NLY-accordionNavTree", AccordionNavTree);
Vue.component("NLY-accordionNavCollapse", AccordionNavCollapse);
}
};
全局注冊(cè)指令和組件
- main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
Vue.config.productionTip = false;
// 自定義圖標(biāo),阿里巴巴矢量圖標(biāo)庫(kù)
import "./assets/nlyblogfont/iconfont.css";
// 全局注冊(cè)組件
import NLYblog from "./nlyaccordion";
Vue.use(NLYblog);
// 全局注冊(cè)指令
import "./nlyaccordion/nlyaccordion.js";
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
Less
- 請(qǐng)把less放到app.vue或者手風(fēng)琴的父組件中
- 也可以編譯成css,然后再引入
<style lang="less">
.flex-column {
flex-direction: column !important;
}
.nly-blog-sider-nav {
padding: 0.5rem 1.5rem 0.5rem 1rem;
display: flex;
flex-wrap: wrap;
margin-bottom: 0;
list-style: none;
.nly-blog-sider-menu {
margin-top: 1rem;
list-style: none;
&.menu-tree {
margin-top: 0;
margin-left: 1rem;
}
.nly-blog-sider-menu-item {
padding: 0 1rem 0 1rem;
&.open {
> .nly-blog-sider-menu-title {
i.nly-blog-sider-menu-arrow {
transform: translateY(-2px);
}
i.nly-blog-sider-menu-arrow::after {
transform: rotate(-45deg) translateX(-2px);
}
i.nly-blog-sider-menu-arrow::before {
transform: rotate(45deg) translateX(2px);
}
}
}
}
.nly-blog-sider-menu-title:hover {
color: #0fbcf9;
transition: color 0.3s ease-in;
.nly-blog-sider-menu-arrow::after {
background-color: #0fbcf9;
}
.nly-blog-sider-menu-arrow::before {
background-color: #0fbcf9;
}
}
.nly-blog-sider-menu-title {
// color: inherit;
white-space: nowrap;
cursor: pointer;
display: block;
color: #f97f51;
position: relative;
padding: 0.2rem 0.5rem 0.2rem 0.5rem;
transition: color 0.3s ease-in;
.nly-blog-sider-menu-icon {
// font-size: 1.8rem;
margin-right: 0.5rem;
color: inherit;
// vertical-align: -0.3rem;
}
p {
display: inline-block;
// font-size: 1.3rem;
color: inherit;
}
.nly-blog-sider-menu-arrow {
position: absolute;
top: 50%;
right: 16px;
width: 10px;
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.nly-blog-sider-menu-arrow::before {
transform: rotate(-45deg) translateX(2px);
position: absolute;
width: 6px;
height: 1.5px;
background-color: #f97f51;
border-radius: 2px;
transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
top 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
content: "";
}
.nly-blog-sider-menu-arrow::after {
transform: rotate(45deg) translateX(-2px);
position: absolute;
width: 6px;
height: 1.5px;
background-color: #f97f51;
border-radius: 2px;
transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
top 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
content: "";
}
}
}
}
</style>
Demo
<template>
<NLY-accordionNav>
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
Nejinn
</NLY-accordionNavItem>
<NLY-accordionNavTree icon="nlyblog nly-blog-book" v-nly-accordion.sss>
Nejinn
</NLY-accordionNavTree>
<NLY-accordionNavCollapse id="sss" visible>
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
lerity
</NLY-accordionNavItem>
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
blog
</NLY-accordionNavItem>
</NLY-accordionNavCollapse>
<NLY-accordionNavTree icon="nlyblog nly-blog-book" v-nly-accordion.zzz>
一顆
</NLY-accordionNavTree>
<NLY-accordionNavCollapse id="zzz">
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
數(shù)據(jù)
</NLY-accordionNavItem>
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
小白菜
</NLY-accordionNavItem>
</NLY-accordionNavCollapse>
<NLY-accordionNavTree icon="nlyblog nly-blog-book" v-nly-accordion.ccc>
測(cè)試
</NLY-accordionNavTree>
<NLY-accordionNavCollapse id="ccc">
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
黃色
</NLY-accordionNavItem>
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
藍(lán)色
</NLY-accordionNavItem>
</NLY-accordionNavCollapse>
<NLY-accordionNavTree icon="nlyblog nly-blog-book" v-nly-accordion.ddd>
大巴
</NLY-accordionNavTree>
<NLY-accordionNavCollapse id="ddd">
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
上車
</NLY-accordionNavItem>
<NLY-accordionNavItem icon="nlyblog nly-blog-home">
不開車就下車
</NLY-accordionNavItem>
</NLY-accordionNavCollapse>
</NLY-accordionNav>
</template>
<script>
export default {
name: "accordion"
};
</script>
<style lang="less">
.flex-column {
flex-direction: column !important;
}
.nly-blog-sider-nav {
padding: 0.5rem 1.5rem 0.5rem 1rem;
display: flex;
flex-wrap: wrap;
margin-bottom: 0;
list-style: none;
.nly-blog-sider-menu {
margin-top: 1rem;
list-style: none;
&.menu-tree {
margin-top: 0;
margin-left: 1rem;
}
.nly-blog-sider-menu-item {
padding: 0 1rem 0 1rem;
&.open {
> .nly-blog-sider-menu-title {
i.nly-blog-sider-menu-arrow {
transform: translateY(-2px);
}
i.nly-blog-sider-menu-arrow::after {
transform: rotate(-45deg) translateX(-2px);
}
i.nly-blog-sider-menu-arrow::before {
transform: rotate(45deg) translateX(2px);
}
}
}
}
.nly-blog-sider-menu-title:hover {
color: #0fbcf9;
transition: color 0.3s ease-in;
.nly-blog-sider-menu-arrow::after {
background-color: #0fbcf9;
}
.nly-blog-sider-menu-arrow::before {
background-color: #0fbcf9;
}
}
.nly-blog-sider-menu-title {
// color: inherit;
white-space: nowrap;
cursor: pointer;
display: block;
color: #f97f51;
position: relative;
padding: 0.2rem 0.5rem 0.2rem 0.5rem;
transition: color 0.3s ease-in;
.nly-blog-sider-menu-icon {
// font-size: 1.8rem;
margin-right: 0.5rem;
color: inherit;
// vertical-align: -0.3rem;
}
p {
display: inline-block;
// font-size: 1.3rem;
color: inherit;
}
.nly-blog-sider-menu-arrow {
position: absolute;
top: 50%;
right: 16px;
width: 10px;
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.nly-blog-sider-menu-arrow::before {
transform: rotate(-45deg) translateX(2px);
position: absolute;
width: 6px;
height: 1.5px;
background-color: #f97f51;
border-radius: 2px;
transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
top 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
content: "";
}
.nly-blog-sider-menu-arrow::after {
transform: rotate(45deg) translateX(-2px);
position: absolute;
width: 6px;
height: 1.5px;
background-color: #f97f51;
border-radius: 2px;
transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
top 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
content: "";
}
}
}
}
</style>
這時(shí)候運(yùn)行就可以看到效果圖的大手風(fēng)琴折疊板。