消息提示行为是开发中很是常见的功能,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>
标签给开发者预留拓展空间,使用构造函数的方法来拓展组件逻辑并定义一些快捷方法。我的感受学习一些成熟组件的源码可以学到很多东西,收获很多。