先上效果圖...
Toast需求:
- 彈出Toast自動關(guān)閉
- 多少(N)秒后自動關(guān)閉
- 彈出Toast用戶可點擊關(guān)閉
- 用戶點擊關(guān)閉后回調(diào)(fn)
- 保證只有一個Toast彈出赘那,不會同時出現(xiàn)兩個飞苇。
用法:
import Vue from 'vue'
import plugin from './plugin/index'
Vue.use(plugin)
this.$toast('操作成功!!帝牡!')
Github:歡迎Star
這里是分割線
開始面對面挥转,看代碼...
不管在哪里都可以直接使用this.$toast
,具體如何實現(xiàn)锥咸??细移? 請看Vue官方文檔
1搏予、main.js
Vue.prototype.$toast = function () {
console.log('我是蘇宋霖');
}
2、App.vue
<template>
<div id="app">
<button @click="xxx">點我</button>
</div>
</template>
export default {
name: 'app',
methods: {
xxx() {
this.$toast()
},
}
}
點擊按鈕控制臺是不是打印出了我是蘇宋霖
弧轧,做到這里你已經(jīng)成功一半了??
但是這樣并不是我們想要的雪侥,使用插件形式install
,讓用戶主動去使用精绎。
繼續(xù)改造?
新建plugin/index.js
export default {
install(Vue,options){
Vue.prototype.$toast = function(message){
console.log(message);
}
}
}
main.js
import plugin from './plugin/index'
Vue.use(plugin)
使用
this.$toast('我是蘇宋霖')
控制一樣打印出我是蘇宋霖
, 說明你距離成功不遠了 (這句話跟高中老師一樣速缨,經(jīng)常對我們學生說前腳已經(jīng)跨進北大的校門了,后來我們畢業(yè)了才知道北大是可以隨便進出代乃,哪怕登記一下就好)
Vue.use
會自動阻止多次注冊相同插件旬牲,屆時即使多次調(diào)用也只會注冊一次該插件。
繼續(xù)改造??
新建components/Toast.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
name:'Toast'
}
</script>
在引入Toast.vue之前有個問題如何在js使用vue實例 搁吓?原茅?
1、方應(yīng)杭的Vue 動態(tài)創(chuàng)建實例
2堕仔、滴滴的cube-ui專門為這個場景實現(xiàn)了一個create-api, 可以將任意自定義組件制作成調(diào)用時動態(tài)創(chuàng)建的插件
plugin/index.js 引入Toast.vue
import Toast from '@/components/Toast.vue'
export default {
install(Vue,options){
Vue.prototype.$toast = function(message){
let Constructor = Vue.extend(Toast)
let toast = new Constructor()
toast.$mount()
document.body.appendChild(toast.$el) //添加到頁面中
}
}
}
通過??代碼可以實現(xiàn)動態(tài)創(chuàng)建實例(別用原生js實現(xiàn)如: let div = document.createElement("div"); document.body.appendChild(div)
员咽,這樣無法使用到vue的各種生命周期及Api)
繼續(xù)改造???
Toast.vue 文件不變,index.js通過插槽傳值給Toast.vue
問題:如果在js中使用插槽傳值爸ぁ贝室??仿吞? 我的天
看文檔渲染函數(shù) & JSX
this.$slots.default // 子節(jié)點數(shù)組
注意這里接收的是數(shù)組 數(shù)組 數(shù)組
import Toast from '@/components/Toast.vue'
export default {
install(Vue,options){
Vue.prototype.$toast = function(message){
let Constructor = Vue.extend(Toast)
let toast = new Constructor()
toast.$slots.default = [message] //slots必須要放在mount之前
toast.$mount()
document.body.appendChild(toast.$el)
}
}
}
添加簡單樣式
<style lang="scss" scoped>
$font-size:14px;
$toast-height:40px;
$toast-bg:rgba(0, 0, 0, 0.75);
.toast {
font-size: $font-size;
line-height: 1.8;
height: $toast-height;
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
color: white;
background: $toast-bg;
border-radius: 4px;
box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.50);
padding: 0 16px;
}
</style>
繼續(xù)改造????
實現(xiàn)3s后自動關(guān)閉,
props: {
// 自動關(guān)閉
autoClose: {
type: Boolean,
default: true
},
// 關(guān)閉時間
autoCloseDelay: {
type: Number,
default: 3
}
},
mounted() {
if (this.autoClose) {
setTimeout(() => {
this.close();
}, this.autoCloseDelay * 1000);
}
},
methods: {
close() {
this.$el.remove(); //刪除
this.$destroy(); //清除綁定的一些事件
}
}
繼續(xù)改造?????
彈出Toast用戶可點擊關(guān)閉
<template>
<div class="toast">
<slot></slot>
<div class="line"></div>
<span class="close" v-if="closeButton" @click="onClickclose">{{closeButton.text}}</span>
</div>
</template>
<script>
export default {
name: "Toast",
props: {
//點擊關(guān)閉
closeButton: {
type: Object,
default() {
return {
text: "關(guān)閉",
callback: undefined
};
}
}
},
methods: {
close() {
this.$el.remove(); //刪除
this.$destroy(); //清除綁定的一些事件
},
onClickclose() {
this.close();
if (this.closeButton && typeof this.closeButton.callback === "function") {
this.closeButton.callback();
}
}
}
};
</script>
plugin/index.js
import Toast from '@/components/Toast.vue'
export default {
install(Vue,options){
Vue.prototype.$toast = function(message,toaseOptions){
let Constructor = Vue.extend(Toast)
let toast = new Constructor({
propsData:toaseOptions
})
toast.$slots.default = [message]
toast.$mount()
document.body.appendChild(toast.$el)
}
}
}
使用
this.$toast('我是蘇宋霖',{
closeButton:{
text:'知道了',
callback(){
console.log('蘇宋霖點擊知道了');
}
}
})
手動測試發(fā)現(xiàn)一個問題滑频,內(nèi)容過多的時候Toast并沒有變化,
關(guān)閉按鈕被擠壓
如下:
改造吧支持多行文字
使用js去給line添加高度,因為父元素的高度變成min-height唤冈。
//html
<div class="toast" ref="toast">
<slot></slot>
<div class="line" ref="line"></div>
<span class="close" v-if="closeButton" @click="onClickclose">{{closeButton.text}}</span>
</div>
//js
mounted(){
this.updateStyle()
},
methods: {
updateStyle() {
this.$nextTick(() => {
this.$refs.line.style.height =
`${this.$refs.toast.getBoundingClientRect().height}px`
})
},
}
//css 新增的
<style lang="scss" scoped>
$toast-min-height: 40px;
.toast {
min-height: $toast-min-height;
.close {
flex-shrink: 0;
}
}
</style>
繼續(xù)改造??????
Toast的位置
在.toast上加:class="toastClasses"
,通過props傳過來的屬性position
動態(tài)添加class
//html
<div class="toast" ref="toast" :class="toastClasses">
<slot></slot>
<div class="line" ref="line"></div>
<span class="close" v-if="closeButton" @click="onClickclose">{{closeButton.text}}</span>
</div>
//js
props:{
// 位置
position: {
type: String,
default: "top",
validator(value) {
return ["top", "bottom", "middle"].indexOf(value) >= 0;
}
}
}
computed: {
toastClasses() {
return {
[`position-${this.position}`]: true
};
}
},
//css
&.position-top {
top: 0;
transform: translateX(-50%);
}
&.position-bottom {
bottom: 0;
transform: translateX(-50%);
}
&.position-middle {
top: 50%;
transform: translate(-50%, -50%);
}
使用
<template>
<div id="app">
<button @click="xxx1">點擊 上</button>
<button @click="xxx2">點擊 中</button>
<button @click="xxx3">點擊 下</button>
</div>
</template>
<script>
export default {
name: 'app',
methods: {
xxx1(){
this.xxx('top')
},
xxx2(){
this.xxx('middle')
},
xxx3(){
this.xxx('bottom')
},
xxx(position) {
this.$toast('我是蘇宋霖我是蘇宋霖我是蘇宋霖我是蘇宋霖我是蘇宋霖我是蘇宋霖我是蘇宋霖',{
position,
closeButton:{
text:'知道了',
callback(){
console.log('蘇宋霖點擊知道了');
}
}
})
},
},
}
</script>
新問題:
用戶重復(fù)點擊Toast峡迷,會出現(xiàn)多個DOM??
解決方案:
如果已經(jīng)有一個Toast绘搞,就把之前的給刪了
plugin/index.js
import Toast from '@/components/Toast.vue'
export default {
install(Vue,options){
let currentToast ;
Vue.prototype.$toast = function(message,toaseOptions){
if(currentToast){currentToast.close()}//如果有Toast就刪除上一個
currentToast = createToast({Vue,propsData:toaseOptions,message})
}
}
}
function createToast({Vue,propsData,message}) {
let Constructor = Vue.extend(Toast)
let toast = new Constructor({ propsData})
toast.$slots.default = [message]
toast.$mount()
document.body.appendChild(toast.$el)
return toast
}
ps:樣式啥的請自行修改彤避。
輪子完畢!:幌健琉预! 接下來測試用例..
未完待續(xù)...
提示:
npm 配置淘寶源:npm config set registry https://registry.npm.taobao.org/