我给大家分享的是用前端框架vuejs来实现tabbar组件的封装 —— 第一次写文章,写得不好请见谅。
是因为很多大部分的移动程序都是采用了tabber这种模式的组件,导致了tabber的使用频率特别高,为了方便起见,封装tabber组件,大大解决了时间,每次写项目需要用到tabbar时,直接导入就可以,不需要再花费时间去重复相同的代码,节约成本。
1.因为封装TabBar组件,我主要采用的是插槽slot,所有在开始之前,我先简单的介绍插槽
2.在学习插槽前,先理解一个概念: 编译作用域
官方的准则是:父组件模版的所有东西都会在父级作用域内编译;子组件的所有东西都会在子级作用域内编译。
换句话就是说: 父组件替代插槽的标签,但是内容却是由子组件来提供。
3.组件的插槽
组件的插槽是为了让我们封装的组件更加具有扩展性。
让使用者可以决定组件内部的一些内容到底展示什么。
一旦有了这个组件,我们就可以在多个页面中复用了。
4.slot基本使用
5.具名插槽slot
当子组件的功能复杂时,子组件的插槽可能并非是一个。比如我们封装导航栏的子组件,就需要三个插槽,分别代表左边、中间、右边。这个时候我们就需要给插槽起一个名字:
<slot name='myslot'></slot>
如果,我们每一个单独去封装一个组件,显示是不合适的,但是如果我们封装成一个,好像也不合理
比如每个页面都返回,这部分内容我们就需要重复去封装。
比如有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字等等。
那么如何封装合适呢?抽取共性,保留不同
最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。 是搜索框,还是文字,还是菜单。可以由调用者来决定
应该如何封装
TabBar中显示的内容是由外界决定
自定义TabBarItem,可以传入 图片和文字
在开始前需要安装好vue的脚手架,可以参照vue官方,cn.vuejs.org/ 这里我使用的是vuecli2,
新建一个项目
vue init webpack tabbar
填写项目描述,作者,安装vue-router
将所有用的资源,拷贝到项目中,这里我把图片素材,拷入到了src/assets/img/tabbar目录里。在src下,新建了一个pages目录,存放每一个页面的组件,可以看一下我的目录
如何创建组件tabbar,也就是移动端底部的工具栏,这是将要实现的效果图。
<template> <div class="tab-bar-item"> <!-- 插槽会被直接替换,所有最好在插槽外面包装一个div --> <div> <slot name="item-icon"></slot></div> <div> <slot name="item-icon-active"></slot> </div> <div> <slot name="item-text" ></slot> </div> </div> </template> <style > .tab-bar-item{ flex: 1; text-align: center; height: 49px; font-size: 14px; } .tab-bar-item img{ width: 24px; height: 24px; margin-top: 3px; /* 去除图片自身带的空隙 */ vertical-align: middle; } .active { color: rgb(255, 0, 149); } </style> 复制代码
<template> <div id="tab-bar"> <slot></slot> </div> </template> <script> export default { } </script> <style scoped> #tab-bar{ display: flex; background-color: #f6f6f6; position: fixed; left: 0; right: 0; bottom: 0; box-shadow:0px -1px 1px rgba(100,100,100,.1); } </style> 复制代码
<template> <tab-bar> <tab-bar-item > <img slot="item-icon" src="@/assets/img/tabbar/shouye.png" alt=""> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item > <img slot="item-icon" src="@/assets/img/tabbar/icon_category.png" alt=""> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item> <img slot="item-icon" src="@/assets/img/tabbar/gouwuche.png" alt=""> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item > <img slot="item-icon" src="@/assets/img/tabbar/wode.png" alt=""> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> </template> <script> import TabBar from '@/components/tabbar/TabBar'; import TabBarItem from '@/components/tabbar/TabBarItem'; export default { components: { TabBar, TabBarItem } } </script> <style> </style> 复制代码
要实现子组件的点击切换改变状态,也就在两张图片中进行切换,用户直接同时把两张图片传入,底层封装会进行自动的切换。
<template> <tab-bar> <tab-bar-item > <img slot="item-icon" src="@/assets/img/tabbar/shouye.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/shouye-2.png" alt=""> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item > <img slot="item-icon" src="@/assets/img/tabbar/icon_category.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/icon_category-2.png" alt=""> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item > <img slot="item-icon" src="@/assets/img/tabbar/gouwuche.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/gouwuche-2.png" alt=""> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item> <img slot="item-icon" src="@/assets/img/tabbar/wode.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/wode-2.png" alt=""> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> </template> <script> import TabBar from '@/components/tabbar/TabBar'; import TabBarItem from '@/components/tabbar/TabBarItem'; export default { components: { TabBar, TabBarItem } } </script> 复制代码
<template> <div class="tab-bar-item"> <!-- 插槽会被直接替换,所有最好在插槽外面包装一个div --> <div v-if="!isActive"> <slot name="item-icon"></slot></div> <div v-else> <slot name="item-icon-active"></slot> </div> <div :class="{active: isActive }"> <slot name="item-text" ></slot> </div> </div> </template> <script> export default { name: "TabBarItem", data() { return { isActive: false, } } } </script> <style > .tab-bar-item{ flex: 1; text-align: center; height: 49px; font-size: 14px; } .tab-bar-item img{ width: 24px; height: 24px; margin-top: 3px; /* 去除图片自身带的空隙 */ vertical-align: middle; } .active { color: rgb(255, 0, 149); } </style> 复制代码
import Vue from 'vue' import Router from 'vue-router'; const Home = () => import('../pages/home/Home') const Cart = () => import('../pages/cart/Cart') const Profile = () => import('../pages/profile/Profile') const Category = () => import('../pages/category/Category') Vue.use(Router) export default new Router({ routes: [ { path: '/', redirect: '/home' }, { path:'/home', component: Home }, { path:'/cart', component: Cart }, { path:'/profile', component: Profile }, { path:'/category', component: Category }, ], mode: 'history' }) 复制代码
<tab-bar> <tab-bar-item path="/home" > <img slot="item-icon" src="@/assets/img/tabbar/shouye.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/shouye-2.png" alt=""> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item path="/category" "> <img slot="item-icon" src="@/assets/img/tabbar/icon_category.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/icon_category-2.png" alt=""> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item path="/cart"> <img slot="item-icon" src="@/assets/img/tabbar/gouwuche.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/gouwuche-2.png" alt=""> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item path="/profile""> <img slot="item-icon" src="@/assets/img/tabbar/wode.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/wode-2.png" alt=""> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> 复制代码
<template> <div class="tab-bar-item" @click="itemClick"> <!-- 插槽会被直接替换,所有最好在插槽外面包装一个div --> <div v-if="!isActive"> <slot name="item-icon"></slot></div> <div v-else> <slot name="item-icon-active"></slot> </div> <div :class="{active:isActive}"> <slot name="item-text" ></slot> </div> </div> </template> <script> export default { name: "TabBarItem", props: { path: String, }, computed: { isActive() { // /home -> item1(/home) =true // 否则为false // indexOf !== -1 就找到了 return this.$route.path.indexOf(this.path) !== -1 }, }, methods: { itemClick() { // 需要用户返回,用push,不需要就用replace this.$router.replace(this.path); } } } </script> <style > .tab-bar-item{ flex: 1; text-align: center; height: 49px; font-size: 14px; } .tab-bar-item img{ width: 24px; height: 24px; margin-top: 3px; /* 去除图片自身带的空隙 */ vertical-align: middle; } .active { color: rgb(255, 0, 149); } </style> 复制代码
<tab-bar> <tab-bar-item path="/home" activeColor="blue"> <img slot="item-icon" src="@/assets/img/tabbar/shouye.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/shouye-2.png" alt=""> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item path="/category" activeColor="red"> <img slot="item-icon" src="@/assets/img/tabbar/icon_category.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/icon_category-2.png" alt=""> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item path="/cart"> <img slot="item-icon" src="@/assets/img/tabbar/gouwuche.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/gouwuche-2.png" alt=""> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item path="/profile" activeColor="green"> <img slot="item-icon" src="@/assets/img/tabbar/wode.png" alt=""> <img slot="item-icon-active" src="@/assets/img/tabbar/wode-2.png" alt=""> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> 复制代码
<template> <div class="tab-bar-item" @click="itemClick"> <!-- 插槽会被直接替换,所有最好在插槽外面包装一个div --> <div v-if="!isActive"> <slot name="item-icon"></slot></div> <div v-else> <slot name="item-icon-active"></slot> </div> <div :style="activeStyle"> <slot name="item-text" ></slot> </div> </div> </template> <script> export default { name: "TabBarItem", props: { path: String, activeColor: { type: String, default: 'red' } }, data() { return { // isActive: false, } }, computed: { isActive() { // /home -> item1(/home) =true // 否则为false // indexOf !== -1 就找到了 return this.$route.path.indexOf(this.path) !== -1 }, activeStyle() { return this.isActive ? {color: this.activeColor} : {} } }, methods: { itemClick() { // 需要用户返回,用push,不需要就用replace this.$router.replace(this.path); } } } </script> <style > .tab-bar-item{ flex: 1; text-align: center; height: 49px; font-size: 14px; } .tab-bar-item img{ width: 24px; height: 24px; margin-top: 3px; /* 去除图片自身带的空隙 */ vertical-align: middle; } </style> 复制代码