vuejs怎样封装一个插件(以封装vue-toast为例扩展)
插件介绍
插件通常会为 Vue 添加全局功能。插件的范围没有限制——一般有下面几种:
- 1.添加全局方法或者属性,如: vue-custom-element
- 2.添加全局资源:指令/过滤器/过渡等,如 vue-touch
- 3.通过全局 mixin 方法添加一些组件选项,如: vue-router
- 4.添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
- 5.一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router
Vue.js 的插件应当有一个公开方法 install
。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象:
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或属性 Vue.myGlobalMethod = function () { // 逻辑... } // 2. 添加全局资源 Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // 逻辑... } ... }) // 3. 注入组件 Vue.mixin({ created: function () { // 逻辑... } ... }) // 4. 添加实例方法 Vue.prototype.$myMethod = function (methodOptions) { // 逻辑... } }
使用方式很简单,通过全局方法Vue.use()来使用插件。
下面开发一个简单的在控制台打印消息插件,新建一个toast.js:
var Toast = {}; Toast.install = function (Vue, options) { Vue.prototype.$msg = 'Hello World'; } export default Toast;
在main.js中引入该toast.js并使用vue.use()安装该toast插件:
import Vue from 'vue'; import Toast from './toast.js'; Vue.use(Toast);
然后我们在任意vue页面就可以调用:
export default { mounted(){ console.log(this.$msg); // Hello World } }
开发vue-toast插件
该插件实现的效果:
- 1.提示内容可以显示在不同的位置(顶部、底部、中间),调用方式this.$toast.top('填写的提示内容')、this.$toast.center('填写的提示内容')、this.$toast.bottom('填写的提示内容');
- 2.通过this.$toast('填写的提示内容')调用该插件,默认展示在底部;
- 3.toast有默认的展示持续时间,也可配置toast展示的持续时间。
toast.js实现代码如下:
let Toast = {}; Toast.install = function(Vue,options) { let opts = { type: 'bottom', // 显示位置 duration: 2500 // 持续时间 } for(let prop in options) { if(options.hasOwnProperty(prop)) { opts[prop] = options[prop]; } } Vue.prototype.$toast = (tips,type) => { if(type) { opts.type = type; } if(document.getElementsByClassName('vue-toast').length) { return false; } let ToastEl = Vue.extend({ template: `<div class="vue-toast vue-toast__${opts.type}">${tips}</div>` }) let toastElement = new ToastEl().$mount().$el; document.body.append(toastElement); setTimeout(() => { document.body.removeChild(toastElement); },opts.duration); } ['top','center','bottom'].forEach((type) => { Vue.prototype.$toast[type] = (tips) => { return Vue.prototype.$toast(tips,type) } }) } export default Toast;
优化vue-toast
调用方式更通用,如下所示:
this.toast = this.$toast({ message: '这是一段信息', position: 'top', duration: 0 });
可以手动关闭弹框
this.toast.close();
实现方式就是封装成一个构造函数,整体代码如下:
function Toast() { this.toastTimer = false; // toastTimer:toast定时器 this.toastVM = null; // toastVM:存储toast VM this.install = function(Vue,options) { let opts = { message: '', // 文本内容 position: 'bottom', // 显示位置 duration: 2500, // 展示时长(ms),值为 0 时,toast 不会消失 className: '' // 自定义类名 } /** toast提示方法 * @params {Object|String} config 配置,如果为字符串=message * */ Vue.prototype.$toast = (config) => { let option = {}; let self = this; Object.assign(option,opts,options); if(typeof config === 'object') { Object.assign(option,config) } else { // 如果是字符串,传递的是message文本内容 option.message = config; } if(this.toastTimer) { // 如果toast还在,则取消上次消失时间 clearTimeout(this.toastTimer); this.toastVM.show = false; } if(!this.toastVM) { let ToastEl = Vue.extend({ data() { return { show: false, message: option.message, className: option.className } }, methods: { // 关闭toast close() { self.toastVM.show = false; } }, render(createElement) { if(!this.show) { return false; } return createElement( 'div', { class: ['lx-toast',`lx-toast-${option.position}`,this.className], show: this.show, domProps: { innerHTML: this.message } }, ) } }) this.toastVM = new ToastEl(); document.body.append(this.toastVM.$mount().$el); } this.toastVM.show = true; this.toastVM.message = option.message; this.toastVM.className = option.className; this.toastVM.position = option.position; if(option.duration != 0) {// 为0的时候一直显示 this.toastTimer = setTimeout(() => { this.toastVM.show = this.toastTimer = false; },option.duration) } return this.toastVM; } } } export default new Toast();
使用方式:
<template> <div class="test"> <button @click="open">开启弹框</button> <button @click="close">关闭弹框</button> </div> </template> <script> export default { data () { return { toast: null } }, methods: { open() { this.toast = this.$toast({ message: '这是一段信息', position: 'top', duration: 0 }); }, close() { this.toast.close(); } } } </script>
添加loading方法
完整代码:
function Toast() { this.toastTimer = false; // toastTimer:toast定时器 this.toastVM = null; // toastVM:存储toast VM this.showLoad = false; // 存储loading显示状态 this.loadNode = null; // 存储loading节点元素 this.install = function(Vue,options) { let opts = { message: '', // 文本内容 position: 'bottom', // 显示位置 duration: 2500, // 展示时长(ms),值为 0 时,toast 不会消失 className: '' // 自定义类名 } /** toast提示方法 * @params {Object|String} config 配置,如果为字符串=message * */ Vue.prototype.$toast = (config) => { let option = {}; let self = this; Object.assign(option,opts,options); if(typeof config === 'object') { Object.assign(option,config) } else { // 如果是字符串,传递的是message文本内容 option.message = config; } if(this.toastTimer) { // 如果toast还在,则取消上次消失时间 clearTimeout(this.toastTimer); this.toastVM.show = false; } if(!this.toastVM) { let ToastEl = Vue.extend({ data() { return { show: false, message: option.message, className: option.className } }, methods: { // 关闭toast close() { self.toastVM.show = false; } }, render(createElement) { if(!this.show) { return false; } return createElement( 'div', { class: ['lx-toast',`lx-toast-${option.position}`,this.className], show: this.show, domProps: { innerHTML: this.message } }, ) } }) this.toastVM = new ToastEl(); document.body.append(this.toastVM.$mount().$el); } this.toastVM.show = true; this.toastVM.message = option.message; this.toastVM.className = option.className; this.toastVM.position = option.position; if(option.duration != 0) {// 为0的时候一直显示 this.toastTimer = setTimeout(() => { this.toastVM.show = this.toastTimer = false; },option.duration) } return this.toastVM; } /** $loading加载 * @params {Object|String} config 配置,如果为字符串=message * */ Vue.prototype.$loading = (config) => { let option = {}; let self = this; if(typeof config === 'object') { Object.assign(option,config) } else { // 传递的字符串 option.message = config; } if(option.type == 'close') { if(this.loadNode) { this.loadNode.show = this.showLoad = false; } } else { if(this.showLoad && this.loadNode) { this.loadNode.message = option.message; return false; } const loadEl = Vue.extend({ data() { return { show: false, message: option.message } }, methods: { close() { self.loadNode.show = self.showLoad = false; } }, render(h) { if(!this.show) { return false; } return h('div',{ class: 'lx-load-mark', show: this.show },[ h('div',{ class: 'lx-load-box' },[ h('div',{ class: this.message ? 'lx-loading':'lx-loading-nocontent' },Array.apply(null,{length: 12}).map((value,index) => { return h('div',{ class: ['loading_leaf',`loading_leaf_${index}`] }) })), h('div',{ class: 'lx-load-content', domProps: { innerHTML: this.message } }) ]) ]) } }) this.loadNode = new loadEl(); document.body.appendChild(this.loadNode.$mount().$el); this.loadNode.show = this.showLoad = true; } return this.loadNode; } ['open','close'].forEach(type => { Vue.prototype.$loading[type] = (message) => { return Vue.prototype.$loading({type,message}) } }) } } export default new Toast();
完整的调用方式:
<template> <div class="test"> <button @click="open">开启弹框</button> <button @click="close">关闭弹框</button> </div> </template> <script> export default { data () { return { toast: null } }, mounted() { let loading = this.$loading({ message: '这是loading文字' }) setTimeout(() => { // 5s 后关闭弹框 loading.close(); },5000); }, methods: { open() { this.toast = this.$toast({ message: '这是一段信息', position: 'top', duration: 0 }); }, close() { this.toast.close(); } } } </script>
toast.css代码:
.lx-toast { position: fixed; bottom: 100px; left: 50%; -webkit-box-sizing: border-box; box-sizing: border-box; max-width: 80%; height: 40px; line-height: 20px; padding: 10px 20px; transform: translateX(-50%); -webkit-transform: translateX(-50%); text-align: center; z-index: 9999; font-size: 14px; color: #fff; border-radius: 5px; background: rgba(0, 0, 0, 0.7); animation: show-toast .5s; -webkit-animation: show-toast .5s; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .lx-toast.lx-word-wrap { width: 80%; white-space: inherit; height: auto; } .lx-toast.lx-toast-top { top: 50px; bottom: inherit; } .lx-toast.lx-toast-center { top: 50%; margin-top: -20px; bottom: inherit; } @-webkit-keyframes show-toast { from { opacity: 0; } to { opacity: 1; } } @keyframes show-toast { from { opacity: 0; } to { opacity: 1; } } .lx-load-mark { position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: 9999; } .lx-load-box { position: fixed; z-index: 3; width: 7.6em; min-height: 7.6em; top: 180px; left: 50%; margin-left: -3.8em; background: rgba(0, 0, 0, 0.7); text-align: center; border-radius: 5px; color: #FFFFFF; } .lx-load-content { margin-top: 64%; font-size: 14px; } .lx-loading, .lx-loading-nocontent { position: absolute; width: 0px; left: 50%; top: 38%; } .lx-loading-nocontent { top: 50%; } .loading_leaf { position: absolute; top: -1px; opacity: 0.25; } .loading_leaf:before { content: " "; position: absolute; width: 9.14px; height: 3.08px; background: #d1d1d5; -webkit-box-shadow: rgba(0, 0, 0, 0.0980392) 0px 0px 1px; box-shadow: rgba(0, 0, 0, 0.0980392) 0px 0px 1px; border-radius: 1px; -webkit-transform-origin: left 50% 0px; transform-origin: left 50% 0px; } .loading_leaf_0 { -webkit-animation: opacity-0 1.25s linear infinite; animation: opacity-0 1.25s linear infinite; } .loading_leaf_0:before { -webkit-transform: rotate(0deg) translate(7.92px, 0px); transform: rotate(0deg) translate(7.92px, 0px); } .loading_leaf_1 { -webkit-animation: opacity-1 1.25s linear infinite; animation: opacity-1 1.25s linear infinite; } .loading_leaf_1:before { -webkit-transform: rotate(30deg) translate(7.92px, 0px); transform: rotate(30deg) translate(7.92px, 0px); } .loading_leaf_2 { -webkit-animation: opacity-2 1.25s linear infinite; animation: opacity-2 1.25s linear infinite; } .loading_leaf_2:before { -webkit-transform: rotate(60deg) translate(7.92px, 0px); transform: rotate(60deg) translate(7.92px, 0px); } .loading_leaf_3 { -webkit-animation: opacity-3 1.25s linear infinite; animation: opacity-3 1.25s linear infinite; } .loading_leaf_3:before { -webkit-transform: rotate(90deg) translate(7.92px, 0px); transform: rotate(90deg) translate(7.92px, 0px); } .loading_leaf_4 { -webkit-animation: opacity-4 1.25s linear infinite; animation: opacity-4 1.25s linear infinite; } .loading_leaf_4:before { -webkit-transform: rotate(120deg) translate(7.92px, 0px); transform: rotate(120deg) translate(7.92px, 0px); } .loading_leaf_5 { -webkit-animation: opacity-5 1.25s linear infinite; animation: opacity-5 1.25s linear infinite; } .loading_leaf_5:before { -webkit-transform: rotate(150deg) translate(7.92px, 0px); transform: rotate(150deg) translate(7.92px, 0px); } .loading_leaf_6 { -webkit-animation: opacity-6 1.25s linear infinite; animation: opacity-6 1.25s linear infinite; } .loading_leaf_6:before { -webkit-transform: rotate(180deg) translate(7.92px, 0px); transform: rotate(180deg) translate(7.92px, 0px); } .loading_leaf_7 { -webkit-animation: opacity-7 1.25s linear infinite; animation: opacity-7 1.25s linear infinite; } .loading_leaf_7:before { -webkit-transform: rotate(210deg) translate(7.92px, 0px); transform: rotate(210deg) translate(7.92px, 0px); } .loading_leaf_8 { -webkit-animation: opacity-8 1.25s linear infinite; animation: opacity-8 1.25s linear infinite; } .loading_leaf_8:before { -webkit-transform: rotate(240deg) translate(7.92px, 0px); transform: rotate(240deg) translate(7.92px, 0px); } .loading_leaf_9 { -webkit-animation: opacity-9 1.25s linear infinite; animation: opacity-9 1.25s linear infinite; } .loading_leaf_9:before { -webkit-transform: rotate(270deg) translate(7.92px, 0px); transform: rotate(270deg) translate(7.92px, 0px); } .loading_leaf_10 { -webkit-animation: opacity-10 1.25s linear infinite; animation: opacity-10 1.25s linear infinite; } .loading_leaf_10:before { -webkit-transform: rotate(300deg) translate(7.92px, 0px); transform: rotate(300deg) translate(7.92px, 0px); } .loading_leaf_11 { -webkit-animation: opacity-11 1.25s linear infinite; animation: opacity-11 1.25s linear infinite; } .loading_leaf_11:before { -webkit-transform: rotate(330deg) translate(7.92px, 0px); transform: rotate(330deg) translate(7.92px, 0px); } @-webkit-keyframes opacity-0 { 0% { opacity: 0.25; } 0.01% { opacity: 0.25; } 0.02% { opacity: 1; } 60.01% { opacity: 0.25; } 100% { opacity: 0.25; } } @keyframes opacity-0 { 0% { opacity: 0.25; } 0.01% { opacity: 0.25; } 0.02% { opacity: 1; } 60.01% { opacity: 0.25; } 100% { opacity: 0.25; } } @-webkit-keyframes opacity-1 { 0% { opacity: 0.25; } 8.34333% { opacity: 0.25; } 8.35333% { opacity: 1; } 68.3433% { opacity: 0.25; } 100% { opacity: 0.25; } } @keyframes opacity-1 { 0% { opacity: 0.25; } 8.34333% { opacity: 0.25; } 8.35333% { opacity: 1; } 68.3433% { opacity: 0.25; } 100% { opacity: 0.25; } } @-webkit-keyframes opacity-2 { 0% { opacity: 0.25; } 16.6767% { opacity: 0.25; } 16.6867% { opacity: 1; } 76.6767% { opacity: 0.25; } 100% { opacity: 0.25; } } @keyframes opacity-2 { 0% { opacity: 0.25; } 16.6767% { opacity: 0.25; } 16.6867% { opacity: 1; } 76.6767% { opacity: 0.25; } 100% { opacity: 0.25; } } @-webkit-keyframes opacity-3 { 0% { opacity: 0.25; } 25.01% { opacity: 0.25; } 25.02% { opacity: 1; } 85.01% { opacity: 0.25; } 100% { opacity: 0.25; } } @keyframes opacity-3 { 0% { opacity: 0.25; } 25.01% { opacity: 0.25; } 25.02% { opacity: 1; } 85.01% { opacity: 0.25; } 100% { opacity: 0.25; } } @-webkit-keyframes opacity-4 { 0% { opacity: 0.25; } 33.3433% { opacity: 0.25; } 33.3533% { opacity: 1; } 93.3433% { opacity: 0.25; } 100% { opacity: 0.25; } } @keyframes opacity-4 { 0% { opacity: 0.25; } 33.3433% { opacity: 0.25; } 33.3533% { opacity: 1; } 93.3433% { opacity: 0.25; } 100% { opacity: 0.25; } } @-webkit-keyframes opacity-5 { 0% { opacity: 0.270958333333333; } 41.6767% { opacity: 0.25; } 41.6867% { opacity: 1; } 1.67667% { opacity: 0.25; } 100% { opacity: 0.270958333333333; } } @keyframes opacity-5 { 0% { opacity: 0.270958333333333; } 41.6767% { opacity: 0.25; } 41.6867% { opacity: 1; } 1.67667% { opacity: 0.25; } 100% { opacity: 0.270958333333333; } } @-webkit-keyframes opacity-6 { 0% { opacity: 0.375125; } 50.01% { opacity: 0.25; } 50.02% { opacity: 1; } 10.01% { opacity: 0.25; } 100% { opacity: 0.375125; } } @keyframes opacity-6 { 0% { opacity: 0.375125; } 50.01% { opacity: 0.25; } 50.02% { opacity: 1; } 10.01% { opacity: 0.25; } 100% { opacity: 0.375125; } } @-webkit-keyframes opacity-7 { 0% { opacity: 0.479291666666667; } 58.3433% { opacity: 0.25; } 58.3533% { opacity: 1; } 18.3433% { opacity: 0.25; } 100% { opacity: 0.479291666666667; } } @keyframes opacity-7 { 0% { opacity: 0.479291666666667; } 58.3433% { opacity: 0.25; } 58.3533% { opacity: 1; } 18.3433% { opacity: 0.25; } 100% { opacity: 0.479291666666667; } } @-webkit-keyframes opacity-8 { 0% { opacity: 0.583458333333333; } 66.6767% { opacity: 0.25; } 66.6867% { opacity: 1; } 26.6767% { opacity: 0.25; } 100% { opacity: 0.583458333333333; } } @keyframes opacity-8 { 0% { opacity: 0.583458333333333; } 66.6767% { opacity: 0.25; } 66.6867% { opacity: 1; } 26.6767% { opacity: 0.25; } 100% { opacity: 0.583458333333333; } } @-webkit-keyframes opacity-9 { 0% { opacity: 0.687625; } 75.01% { opacity: 0.25; } 75.02% { opacity: 1; } 35.01% { opacity: 0.25; } 100% { opacity: 0.687625; } } @keyframes opacity-9 { 0% { opacity: 0.687625; } 75.01% { opacity: 0.25; } 75.02% { opacity: 1; } 35.01% { opacity: 0.25; } 100% { opacity: 0.687625; } } @-webkit-keyframes opacity-10 { 0% { opacity: 0.791791666666667; } 83.3433% { opacity: 0.25; } 83.3533% { opacity: 1; } 43.3433% { opacity: 0.25; } 100% { opacity: 0.791791666666667; } } @keyframes opacity-10 { 0% { opacity: 0.791791666666667; } 83.3433% { opacity: 0.25; } 83.3533% { opacity: 1; } 43.3433% { opacity: 0.25; } 100% { opacity: 0.791791666666667; } } @-webkit-keyframes opacity-11 { 0% { opacity: 0.895958333333333; } 91.6767% { opacity: 0.25; } 91.6867% { opacity: 1; } 51.6767% { opacity: 0.25; } 100% { opacity: 0.895958333333333; } } @keyframes opacity-11 { 0% { opacity: 0.895958333333333; } 91.6767% { opacity: 0.25; } 91.6867% { opacity: 1; } 51.6767% { opacity: 0.25; } 100% { opacity: 0.895958333333333; } }