Vue2 中封装组件-消息提示 Message
本文主要分享了消息提示 Message组件的整个开发流程及其难点的解决方法,主要有基本介绍、样式结构、业务逻辑、挂载方法这四块内容。
Vue2 中封装组件-消息提示 Message
由于我在开发的个人博客前台中需要自行封装许多复用组件,所以跟大家分享一下我认为比较难的组件—消息提示 Message 的整个开发流程及其难点的解决方法。
1.Message 组件的基本介绍
1.1 最终效果图
最终的效果图如下:
1.2 Options 参数
Message 组件的主要参数见下表:
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
content | 消息内容 | String | —— | “”(空字符串) |
type | 消息类型 | String | info/error/success/warn | info |
duration | 显示时间 | Number | —— | 2000(ms) |
container | 组件的父容器 | HTMLElement | —— | document.body |
callback | 回调函数(在消息消失后执行,如果不传则不执行) | Function | —— | undefined |
1.3 使用方法
因为 Message 组件在我的个人博客系统中会经常使用,所以并不是注册局部组件也不是注册全局组件,而是直接挂载到Vue.prototype
这一原型上,后面 vue 实例对象使用起来就会更加方便,只需要调用this.$showMessage()
方法。
但我们需要有一个获取 vue 实例对象中的某个 DOM 节点的方法:
- 此时我们可以借助
ref
这个属性,通过this.$refs.xxxx
来获取该 DOM 节点
具体代码如下:
<template>
<div class="container" ref="container">
<button @click="handleClick"></button>
</div>
</template>
<script>
export default {
methods: {
handleClick() {
this.$showMessage({
content: "消息提示弹出",
type: "success",
duration: 1000,
container: this.$refs.container,
callback: () => {
console.log("消息提示消失,执行回调函数");
},
});
},
},
};
</script>
<style>
.test-container {
width: 500px;
height: 400px;
border: 2px solid;
margin: 0 auto;
position: relative;
}
</style>
2.Message 组件的样式结构
Message 组件的样式结构如图所示:
2.1 HTML 结构
从图中我们可以看出该组件的 HTML 结构还是比较简单的,就是是一个message
容器包裹着一个icon
图标与content
字体内容。
因为结构比较简单,使用我就没写 HTML 代码,而是直接用 JS 代码来生成元素,再通过添加 class 类名的方式来增添组件的样式,再进行相应的业务逻辑控制。
但大家还是可以看一下下面的 HTML 代码,这样可以对该组件的结构有更直观的了解。
<!-- 该组件没写 HTML 代码,而是直接用 JS 代码来生成元素 -->
<!-- 下面的代码只便于读者对该组件的结构有更直观的了解 -->
<div class="message">
<span class="icon">
<Icon :type="type"></Icon>
</span>
<div>{{content}}</div>
</div>
至于 icon 图标这块我是直接使用了自己已封装好的Icon组件
,该组件实现起来比较简单,主要是通过传入 type 这 prop 属性来控制 Icon 图标的类型,我这边就直接贴代码:
Icon.Vue 文件
代码如下:
<template>
<i class="iconfont icon-container" :class="fontClass"></i>
</template>
<script>
const classMap = {
home: "iconzhuye",
success: "iconzhengque",
error: "iconcuowu",
close: "iconguanbi",
warn: "iconjinggao",
info: "iconxinxi",
blog: "iconblog",
code: "iconcode",
about: "iconset_about_hov",
weixin: "iconweixin",
mail: "iconemail",
github: "icongithub",
qq: "iconsign_qq",
arrowUp: "iconiconfonticonfonti2copy",
arrowDown: "iconiconfonticonfonti2",
empty: "iconempty",
chat: "iconliuyan",
};
export const types = Object.keys(classMap);
export default {
props: {
type: {
type: String,
required: true,
},
},
computed: {
// 图标类样式
fontClass() {
return classMap[this.type];
},
},
};
</script>
<style scoped>
/* 导入远程iconfont样式库 */
@import "//at.alicdn.com/t/font_2164449_nalfgtq7il.css";
.iconfont {
color: inherit;
font-size: inherit;
}
</style>
2.2 CSS 样式
从上面的样式结构图可看出 Message 组件的内部样式也比较简单,主要是:
message容器
中的子元素居中显示,我是通过 flex 布局是实现的。message容器
的背景颜色随 type 这 prop 属性而变化,我是可通过不同 class 类名来进行控制。- 另外我使用了 Less 预处理器,并开启了 CSS Modules。
showMessage.module.less文件
具体代码如下:
@import "../styles/var.less";
@import "../styles/mixin.less";
.message {
/*message在container父容器中居中
该居中方案不能使用flex布局 因为flex会影响到父容器的其他子节点的布局*/
.self-center();
z-index: 999; //让该组件的层叠上下文置于顶层
border-radius: 5px;
padding: 10px 30px;
line-height: 2;
color: #fff;
box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.5); //增加盒子阴影
transition: 0.4s; //过渡时间
white-space: nowrap; //防止宽度被挤压而导致文字分行
/*message内部子元素垂直居中*/
display: flex;
align-items: center;
/*message容器的初始状态,便于后续增加渐入淡出的效果 */
transform: translate(-50%, -50% + 25px);
opacity: 0;
&-info {
background: @primary;
}
&-success {
background: @success;
}
&-warn {
background: @warn;
}
&-error {
background: @danger;
}
}
.icon {
font-size: 20px;
margin-right: 7px;
}
var.less
变量文件:
// 提供less变量
@danger: #cc3600; // 危险、错误
@primary: #6b9eee; // 主色调、链接
@words: #373737; // 大部分文字、深色文字
@lightWords: #999; // 少部分文字、浅色文字
@warn: #dc6a12; // 警告
@success: #7ebf50; // 成功
@gray: #b4b8bc; // 灰色
@dark: #202020; // 深色
mixin.less
混入文件:
// 提供混合样式
.self-center(@pos: absolute) {
position: @pos;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
3. Message 组件的业务逻辑
Message 组件虽然看起来比较简单,但这组件在许多情况下都要使用,要考虑其通用性,所以该组件的业务逻辑还是比较复杂的,我认为主要的难点有:
- 获取 Icon 组件根元素的 DOM 节点
- Message 组件的渐入淡出的动态效果
3.1 获取 Icon 组件根元素 DOM 节点
如果我们直接导入 Icon.Vue 组件文件并直接进行使用的话,得到的会是一个 Vue 实例对象,而且我们无法通过该对象来操作其根元素 DOM 节点。
所以此时我们要借助 vue 中的 render 渲染函数进行封装一个工具函数——getComponentRootDom。
getComponentRootDom.js文件
具体代码如下:
import Vue from "vue";
/**
获取某个组件渲染的Dom根元素
*/
export default function (comp, props) {
const vm = new Vue({
render: (h) => h(comp, { props }),
});
vm.$mount();
return vm.$el;
}
3.2 Message 组件的渐入淡出的动态效果
3.2.1 渐入效果
渐入效果的代码其实是很简单的,但会出现渐入效果丢失的问题。
在我大量参阅资料后,发现是浏览器异步渲染机制所导致。(后续我也会对该部分内容进行详细的讲解,敬请期待)
- 渐入效果丢失主要原因是:当时正处于 message 容器刚加入 container 父容器的时刻,message 容器尚未渲染完成,所以后面的样式代码会直接覆盖前面的样式代码。
- 解决办法:在初始状态和正常位置状态之间加入一段会导致**重排(reflow)**的代码如:读取 DOM 节点的位置信息等操作。
message.clientHeight;
目前对浏览器异步渲染机制不熟悉的朋友,参考以下两篇博客:
渐入效果的代码如下:
/*
message容器初始状态的样式代码:
transition: 0.4s;//过渡时间
transform: translate(-50%, -50% + 25px);
opacity: 0;
*/
//渐入效果:初始状态 --> 正常位置状态
container.appendChild(message); // 将message容器加入到父容器中
message.clientHeight; //造成reflow导致浏览器强行渲染
// 正常位置状态的样式
message.style.opacity = 1;
message.style.transform = `translate(-50%, -50%)`;
3.2.2 淡出效果
淡出效果的代码也很简单,主要难点有:
- 何时删除 message 容器,监听什么事件?
- 参阅资料后发现动画结束之后会触发
transitionend
事件。
- 参阅资料后发现动画结束之后会触发
有了这个事件就后面的处理很好办了,我们可以先使用setTimeout
方法进行延迟duration
ms,在监听 message 容器的transitionend
事件进行元素删除与执行回调函数的操作。
淡出效果的代码如下:
/*
正常位置状态: message容器的样式代码:
transition: 0.4s;//过渡时间
message.style.opacity = 1;
message.style.transform = `translate(-50%, -50%)`;
*/
// 淡出效果:正常位置状态 --> 消失状态
//message容器动画的过渡时间
const transitionDuration = parseFloat(
getComputedStyle(message).transitionDuration
);
//进行延迟(duration + transitionDuration)ms
setTimeout(() => {
//消失状态的样式
message.style.opacity = 0;
message.style.transform = "translate(-50%, -50% - 25px)";
//监听transitionend事件
message.addEventListener(
"transitionend",
function () {
message.remove(); //删除message容器
callback && callback(); // 有回调函数就直接执行
},
{ once: true }
);
}, duration + transitionDuration);
3.3 业务逻辑的完整代码
下面我们来看看 message 组件业务逻辑的完整代码。
showMessage.js文件
代码如下:
import getComponentRootDom from "./getComponentRootDom";
import Icon from "@/components/Icon";
import styles from "./showMessage.module.less";
/**
* 消息提示
* @param {String} content 消息内容
* @param {String} type 消息类型 info error success warn
* @param {Number} duration 多久后消失
* @param {HTMLElement} container 容器,消息会显示到该容器的正中间;如果不传,则显示到整个页面的正中间
* @param {Function} callback 回调函数,该函数会在弹出消息消失后执行,如果不传,则不执行
*/
export default function (options = {}) {
//设置参数的默认值
const content = options.content || "";
const type = options.type || "info";
const duration = options.duration || 2000;
const container = options.container || document.body;
const callback = options.callback || undefined;
//JS代码生成message元素
const message = document.createElement("div");
//得到Icon组件的根元素DOM节点
const iconDom = getComponentRootDom(Icon, {
type,
});
//message容器中增加相应的子元素
message.innerHTML = `<span class="${styles.icon}">${iconDom.outerHTML}</span><div>${content}</div>`;
//添加样式
message.classList.add(styles.message); //添加message类名
message.classList.add(styles[`message-${type}`]); //添加消息类型类名
// 由于需要满足 子绝父相 这一条件来进行居中定位
// 所以需要判断容器的position值
if (options.container) {
if (getComputedStyle(container).position === "static") {
container.style.position = "relative";
}
}
container.appendChild(message); // 将message容器加入到父容器中
//渐入效果:初始状态 --> 正常位置状态
message.clientHeight; //造成reflow导致浏览器强行渲染
// 正常位置状态的样式
message.style.opacity = 1;
message.style.transform = `translate(-50%, -50%)`;
// 淡出效果:正常位置状态 --> 消失状态
//message容器动画的过渡时间
const transitionDuration = parseFloat(
getComputedStyle(message).transitionDuration
);
//进行延迟(duration + transitionDuration)ms
setTimeout(() => {
//消失状态的样式
message.style.opacity = 0;
message.style.transform = "translate(-50%, -50% - 25px)";
//监听transitionend事件
message.addEventListener(
"transitionend",
function () {
message.remove(); //删除message容器
callback && callback(); // 有回调函数就直接执行
},
{ once: true }
);
}, duration + transitionDuration);
}
4. Message 组件的挂载方法
由于 Message 组件在我的个人博客系统中会经常使用,为了使用起来更简单与灵活,所以并没有选择注册局部组件与注册全局组件这两个常用方法,而是选择直接挂载到Vue.prototype
这一原型上,后面 vue 实例对象使用起来就会更加方便,调用起来也更加灵活,只需要调用this.$showMessage()
方法。
main.js 入口文件
相关代码如下:
import Vue from "vue";
import App from "./App.vue";
//引入消息弹窗方法 并挂载到vue原型对象
import showMessage from "./utils/showMessage";
Vue.prototype.$showMessage = showMessage;
new Vue({
render: (h) => h(App),
}).$mount("#app");
结语
这是我目前所了解的知识面中最好的解答,当然也有可能存在一点的误区。
所以如果对本文存在疑惑,可以去评论区进行留言,欢迎大家指出文中的错误观点。
码字不易,觉得有帮助的朋友点赞,关注走一波。
更多推荐
所有评论(0)