消息提示行爲是開發中很是常見的功能,Element 爲咱們提供了很是好用和美觀的消息提示組件。這裏就簡單學習下 Notice 組件的 CSS 和代碼邏輯。
Notice 包括了五類組件:css
本文中不一樣角度來學習這些組件(這些組件有不少類似性,因此一塊兒學習啦~)。html
下面是參照 element ui 寫的一個小demo,嘗試着瞭解下其中的 CSS
貼代碼:vue
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>My Notice</title> <style> .success-color { background-color: #f0f9eb; color: #67c23a; } .info-color { background-color: #f4f4f5; color: #909399; } .warning-color { background-color: #fdf6ec; color: #e6a23c; } .error-color { background-color: #fef0f0; color: #f56c6c; } .alert-container { position: relative; padding: 8px 16px; border-radius: 5px; opacity: 1; align-items: center; overflow: hidden; display: flex; } .success-text { font-size: 13px; line-height: 18px; padding: 0 8px; color: #67c23a; } .close { font-size: 13px; position: absolute; top: 12px; right: 15px; cursor: pointer; } .loading-background { position: fixed; z-index: 2000; background-color: rgba(26, 26, 26, 0.9); margin: 0; top: 0; right: 0; bottom: 0; left: 0; transition: opacity 0.3s; z-index: 2000; } .loading-div { top: 50%; position: absolute; margin-top: -20px; height: 40px; width: 100%; align-items: center; justify-content: center; display: flex; } .loading-text { color: #f0f9eb; font-size: 15px; overflow: hidden; z-index: 2001; } .el-message { min-width: 380px; box-sizing: border-box; border-radius: 4px; border-width: 1px; border-style: solid; border-color: #ebeef5; position: fixed; left: 50%; top: 20px; transform: translateX(-50%); background-color: #edf2fc; transition: opacity 0.3s, transform 0.4s; overflow: hidden; padding: 15px 15px 15px 20px; display: flex; align-items: center; } </style> </head> <body> <div id="app"> <div class="alert-container success-color"> <span class="success-text">成功提示的文案</span> <span class="close">X</span> </div> <button id="showLoading">顯示加載中</button> <button id="showMessage">顯示信息</button> <div class="el-message" id="message" style="display:none;"> <p>這是一條消息</p> </div> </div> <script> var alertContainer = document.getElementsByClassName("alert-container")[0] var close = document.getElementsByClassName("close")[0] close.onclick = function () { alertContainer.style = "display:none;" } var app = document.getElementById("app") var bg = document.createElement("div") bg.className = "loading-background" var div = document.createElement("div") div.className = "loading-div" var text = document.createElement("span") text.className = "loading-text" text.textContent = "加載中……" div.appendChild(text) bg.appendChild(div) document.getElementById("showLoading").onclick = function () { if (document.getElementsByClassName("loading-background").length > 0) { return; } app.appendChild(bg) setTimeout(() => { if (document.getElementsByClassName("loading-background").length > 0) { app.removeChild(bg) } }, 3000) } document.getElementById("showMessage").onclick = function () { document.getElementById("message").style = "display:block;" setTimeout(() => { document.getElementById("message").style = "display:none;" }, 3000) } </script> </body> </html>
代碼的運行結果請看 ->這裏<-!node
以上代碼實現了數組
只實現這三個功能的緣由是另外兩個功能和擴展功能都是基於這個demo擴展的。
Alert 是一個靜態的文本內容,只是外部包裹了帶樣式的容器。可能再多個隱藏按鈕和消息圖標。
Loading 和 MessageBox 其實的基本邏輯是插入或顯示新的內容,並在顯示完成後消失。須要注意的就是後面要添加一層遮罩陰影,遮罩若是非全屏使用 position:absolute 而全屏則使用 position:fixed 覆蓋。另外就是注意 z-index 屬性將組件放到視圖最上層。
Message 和 Notification 其實就是文本內容、圖標和按鈕組合容器的現實和隱藏過程。它們的過渡動畫使用的是 vue 的進入/離開 & 列表過渡來實現。服務器
其實從樣式上,上面的 demo 已經實現了大體的樣子了。下面來看看組件的一些邏輯。源碼內容比較多,因此就以問答的方式有目的的來看源碼。app
Alert 由圖標、文本內容、描述內容和關閉按鈕組成:dom
<transition name="el-alert-fade"> <div class="el-alert" :class="[typeClass, center ? 'is-center' : '']" v-show="visible" role="alert" > <!-- 圖標 --> <i class="el-alert__icon" :class="[ iconClass, isBigIcon ]" v-if="showIcon"></i> <div class="el-alert__content"> <!-- 標題 --> <span class="el-alert__title" :class="[ isBoldTitle ]" v-if="title">{{ title }}</span> <slot> <!-- 插槽,默認插入描述文本 --> <p class="el-alert__description" v-if="description">{{ description }}</p> </slot> <!-- 關閉圖標按鈕 --> <i class="el-alert__closebtn" :class="{ 'is-customed': closeText !== '', 'el-icon-close': closeText === '' }" v-show="closable" @click="close()">{{closeText}}</i> </div> </div> </transition>
組件很簡單,註釋上都寫了~組件還作了 slot
插槽拓展,能夠在 <el-alert>
標籤內插入自定義內容。ide
顯示文本使用 {{ text }}
指令來顯示內容。
顯示HTML使用 v-html
指令來渲染顯示。函數
<!-- 顯示文本 --> <p v-if="!dangerouslyUseHTMLString" class="el-message__content">{{ message }}</p> <!-- 顯示HTML --> <p v-else v-html="message" class="el-message__content"></p>
使用 vue 的 render 函數生成 VNode 對象傳給組件做爲組件 slot
插槽的默認顯示結果。
if (isVNode(instance.message)) { instance.$slots.default = [instance.message]; instance.message = null; }
使用 Notice 系列組件時,發現組件的顯示和消失都是有過渡動畫的。界面看着更加友好和舒服。實現方式就是使用了 vue 的進入/離開 & 列表過渡來實現效果。
對於 loading 和 message-box ,在沒有界面時會在 body 最後添加組件內容,顯示事後使用 v-show="false"
(display:none;
) 隱藏,隨後就調用顯示和隱藏界面。
// 將 message-box 組件加入到 body 中 document.body.appendChild(instance.$el);
對於 Alert 因爲一開始就顯示,只是刪除按鈕,因此只需修改 v-show
屬性隱藏便可。
對於 message 和 notification,這兩個組件能夠屢次彈出,逐個關閉(自動或手動)。因此這兩個組件是保存在一個數組中,而後進行渲染的,關閉某個組件就是一個將組件從組件數組中移除的過程。
// id 組件id // useOnClose 自定義關閉函數 Message.close = function(id, userOnClose) { for (let i = 0, len = instances.length; i < len; i++) { if (id === instances[i].id) { // 找到組件,執行自定義關閉函數並從數組中移除 if (typeof userOnClose === 'function') { userOnClose(instances[i]); } instances.splice(i, 1); break; } } };
對於 message-box 有兩種函數回調:callback 函數和 Promise 函數。
if (typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { msgQueue.push({ options: merge({}, defaults, MessageBox.defaults, options), callback: callback, resolve: resolve, reject: reject }); showNextMsg(); }); } else { msgQueue.push({ options: merge({}, defaults, MessageBox.defaults, options), callback: callback }); showNextMsg(); }
固然,到這一步只是將組件配置和回調函數組成對象,並無執行。執行實在 showNextMsg
方法中。showNextMsg
方法用於組合當前組件 options
並寫入到 DOM 中,而後顯示組件。其中有這麼一段關於回調的:
// 若是 currentMsg.options.callback 爲 undefined if (options.callback === undefined) { instance.callback = defaultCallback; }
因此再看看 defaultCallback
函數對象:
const defaultCallback = action => { if (currentMsg) { let callback = currentMsg.callback; if (typeof callback === 'function') { if (instance.showInput) { callback(instance.inputValue, action); } else { callback(action); } } if (currentMsg.resolve) { if (action === 'confirm') { if (instance.showInput) { currentMsg.resolve({ value: instance.inputValue, action }); } else { currentMsg.resolve(action); } } else if (action === 'cancel' && currentMsg.reject) { currentMsg.reject(action); } } } };
這裏就能夠看到回調函數和 Promise 的調用和傳參過程。當組件「關閉」的時候執行 callback 方法回調:
doClose() { …… setTimeout(() => { if (this.action) this.callback(this.action, this); }); },
至此實現了回調及其傳參。
偏移量計算在 Notification 的構造方法中計算得到當前組件 verticalOffset
。
const Notification = function(options) { // 服務器渲染 if (Vue.prototype.$isServer) return; options = options || {}; const userOnClose = options.onClose; // 自定義關閉 const id = 'notification_' + seed++; // 組件 id const position = options.position || 'top-right'; // 位置 // 關閉事件 options.onClose = function() { Notification.close(id, userOnClose); }; // 組件實例 instance = new NotificationConstructor({ data: options }); // vnode if (isVNode(options.message)) { instance.$slots.default = [options.message]; options.message = 'REPLACED_BY_VNODE'; } instance.id = id; instance.vm = instance.$mount(); // 添加實例 document.body.appendChild(instance.vm.$el); instance.vm.visible = true; instance.dom = instance.vm.$el; instance.dom.style.zIndex = PopupManager.nextZIndex(); // 偏移量計算 let verticalOffset = options.offset || 0; instances.filter(item => item.position === position).forEach(item => { verticalOffset += item.$el.offsetHeight + 16; }); verticalOffset += 16; instance.verticalOffset = verticalOffset; // 傳入數組 instances.push(instance); return instance.vm; };
偏移量 verticalOffset
在組件 package\notification/src/main.vue
中使用。
<div :style="positionStyle" ></div>
computed: { // 正則匹配 position 是 top 仍是 bottom verticalProperty() { return /^top-/.test(this.position) ? 'top' : 'bottom'; }, // 最後返回的內聯樣式 positionStyle() { return { [this.verticalProperty]: `${ this.verticalOffset }px` }; } }
若是 position
是 bottom-left
偏移量 verticalOffset
爲 20,那麼返回的內聯樣式就是:
{ bottom: 20px; }
至此實現了偏移的功能。
這裏簡單學習了一下 Notice 系列組件的樣式和邏輯。從中學到了:
<slot>
標籤給開發者預留拓展空間,使用構造函數的方法來拓展組件邏輯並定義一些快捷方法。我的感受學習一些成熟組件的源碼可以學到很多東西,收穫很多。