修改AntDesignVue为Message组件添加一个Animate.css动画
后端仔一枚,对前端不是特别熟,不足请谅解。
最近使用AntDesign时感觉message组件的出现动画不是很明显,仅有box-shadow做区分,并且动画不是特别显眼,于是打算为message的弹出动画添加一个额外动画
动画效果使用 Animate.css
关于patch-package:允许创建并保留对 npm 依赖项的修复。食用方法在Github:
GitHub - ds300/patch-package: Fix broken node modules instantly ?????♀???
不想看过程的可以直接跳到底部看结论
??环境
Vue版本:3.x
AntDesignVue版本:2.2.8
其他依赖包:
安装 $ npminstall animate.css --save
安装 $ npm install patch-package --save-dev
?过程
- 找到组件DOM
通过浏览器控制台找到Message组件生成的DOM

找到Message的容器,看到class-name为ant-message-notice-content明确节点后开始抠代码
- 查看组件源码-分析
先去到node_modules/ant-design-vue/es/message/index.js查看代码
import _extends from "@babel/runtime/helpers/esm/extends"; import { createVNode as _createVNode } from "vue"; import Notification from '../vc-notification'; import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled'; import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled'; import InfoCircleFilled from '@ant-design/icons-vue/InfoCircleFilled'; var defaultDuration = 3; var defaultTop; var messageInstance; var key = 1; var prefixCls = 'ant-message'; var transitionName = 'move-up'; ...直接看到这个prefixCls,知道了这里的ant-message-notice-content是拼接出来的。
所以直接Ctrl+F搜索一下最末尾的-content
getMessageInstance(function (instance) { instance.notice({ key: target, duration: duration, style: args.style || {}, class: args.class, content: function content() { return _createVNode("div", { "class": "".concat(prefixCls, "-custom-content").concat(args.type ? " ".concat(prefixCls, "-").concat(args.type) : '') }, [args.icon || iconNode, _createVNode("span", null, [args.content])]); }, onClose: callback }); });只找到了-custom-content,通过DOM可以看到这个class是容器内的,也就是包着图标和文本的那一层,显然这不是想要的
看到这个-custom-content是_createVNode创建(这里是通过as取的别名,就是vue中的createVNode,语法:createVNode(标签, {属性},内容))后return出去的,要找的应该是这个节点的上一层。
这里是通过getMessageInstance的instance的notice调的,先找到getMessageInstance方法的定义:
function getMessageInstance(callback) { if (messageInstance) { callback(messageInstance); return; }Notification.newInstance({ prefixCls: prefixCls, transitionName: transitionName, style: { top: defaultTop }, getContainer: getContainer, maxCount: maxCount }, function (instance) { if (messageInstance) { callback(messageInstance); return; } messageInstance = instance; callback(instance); }); }
看到一个关键词newInstance,顾名思义创建一个实例,并且第二个参数中调用了callback,也就是getMessageInstance调用时入参的function
找一下Notification在哪儿

看到这个是引用了message组件同级的vc-notification,继续跟一下

index.js直接暴露的Notification,继续跟,找到newInstance

可以看到165行到170行是找Message的挂载节点,稍加思考~嗯,这里就是整个Message组件的创建的地方了。
回到newInstance,定义了一个名为newNotificationInstance的函数,声明两个入参数,一个属性,一个回调函数(论命名的重要性),属性就不管了,主要看一下callback这个。
176到191行调用了callback,回头再看一下这玩意儿(当时给我整晕了,都贴出来才清晰)
就是node_modules/ant-design-vue/es/message/index.js中的getMessageInstance。
有两处,一处定义:
function getMessageInstance(callback) { if (messageInstance) { callback(messageInstance); return; } Notification.newInstance({ prefixCls: prefixCls, transitionName: transitionName, style: { top: defaultTop }, getContainer: getContainer, maxCount: maxCount }, function (instance) { if (messageInstance) { callback(messageInstance); return; } messageInstance = instance; callback(instance); }); }一处调用:
getMessageInstance(function (instance) { instance.notice({ key: target, duration: duration, style: args.style || {}, class: args.class, content: function content() { return _createVNode("div", { "class": "".concat(prefixCls, "-custom-content").concat(args.type ? " ".concat(prefixCls, "-").concat(args.type) : '') }, [args.icon || iconNode, _createVNode("span", null, [args.content])]); }, onClose: callback }); });在调用的地方,这个回调函数的instance参数,就是Notification.js中newInstance中176到191行的入参,可以看到有个notice函数。
在图1-7中,第181行代码 self.$refs.notification.add(noticeProps) 添加了Message的消息元素。看到是通过notification的add添加的。在图1-7中的194-199行代码看到了notification
201行通过_createVNode创建的DOM,看到Notification作为整个参数被传进去了,进去看一下
Notification的定义(太长了,我缩了部分无关紧要的,想看具体代码的可以去搜下仓库)
这里定义了整个组件,在图中第109行,看到了noticeNodes的定义(论命名的重要性),这些节点通过notices遍历的,而notices中的内容,可以看到methods中的add(notice),明白了吧在map中看到使用了_createVNode,这里使用了一个新的参数Notice ,接着找一下这个Notice的定义

发现是引用的Notice.js,跟一下这个js
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; import { createVNode as _createVNode } from "vue"; import PropTypes from '../_util/vue-types'; import { getComponent, getSlot } from '../_util/props-util'; import BaseMixin from '../_util/BaseMixin'; export default { mixins: [BaseMixin], props: { duration: PropTypes.number.def(1.5), closable: PropTypes.looseBool, prefixCls: PropTypes.string, update: PropTypes.looseBool, closeIcon: PropTypes.any, onClose: PropTypes.func }, watch: { duration: function duration() { this.restartCloseTimer(); } }, mounted: function mounted() { this.startCloseTimer(); }, updated: function updated() { if (this.update) { this.restartCloseTimer(); } }, beforeUnmount: function beforeUnmount() { this.clearCloseTimer(); this.willDestroy = true; // beforeUnmount调用后依然会触发onMouseleave事件 }, methods: { close: function close(e) { if (e) { e.stopPropagation(); }this.clearCloseTimer();
this.__emit('close'); }, startCloseTimer: function startCloseTimer() { var _this = this;
this.clearCloseTimer();
if (!this.willDestroy && this.duration) { this.closeTimer = setTimeout(function () { _this.close(); }, this.duration * 1000); } }, clearCloseTimer: function clearCloseTimer() { if (this.closeTimer) { clearTimeout(this.closeTimer); this.closeTimer = null; } }, restartCloseTimer: function restartCloseTimer() { this.clearCloseTimer(); this.startCloseTimer(); } }, render: function render() { var _className;
var prefixCls = this.prefixCls, closable = this.closable, clearCloseTimer = this.clearCloseTimer, startCloseTimer = this.startCloseTimer, close = this.close, $attrs = this.$attrs; var componentClass = "".concat(prefixCls, "-notice"); var className = (_className = {}, _defineProperty(_className, "".concat(componentClass), 1), _defineProperty(_className, "".concat(componentClass, "-closable"), closable), _className); var closeIcon = getComponent(this, 'closeIcon'); return _createVNode("div", { "class": className, "style": $attrs.style || { right: '50%' }, "onMouseenter": clearCloseTimer, "onMouseleave": startCloseTimer }, [_createVNode("div", { "class": "".concat(componentClass, "-content") }, [getSlot(this)]), closable ? _createVNode("a", { "tabindex": "0", "onClick": close, "class": "".concat(componentClass, "-close") }, [closeIcon || _createVNode("span", { "class": "".concat(componentClass, "-close-x") }, null)]) : null]); } };
上半部分都看不懂,看到末尾发现在render函数中有几个关键字 -notice 这个是Message渲染DOM的容器class,包括在下面的 -content 这个就是Message消息的外层。
这里尝试改一下-content(就是这儿,我试过了),修改为: -content animate__animated animate__shakeX
然后执行生成一个补丁 npx patch-package ant-design-vue
应用 npx patch-package
重启应用
再看一下DOM,添加成功

??结论
- 设置 package.json
"script":{ + "postinstall":"patch-package" }
- 安装依赖
npm install animate.css --savenpm install patch-package --save-dev - 修改Notice.js源码(+号)
render: function render() { var _className; var prefixCls = this.prefixCls, closable = this.closable, clearCloseTimer = this.clearCloseTimer, startCloseTimer = this.startCloseTimer, close = this.close, $attrs = this.$attrs; var componentClass = "".concat(prefixCls, "-notice"); var className = (_className = {}, _defineProperty(_className, "".concat(componentClass), 1), _defineProperty(_className, "".concat(componentClass, "-closable"), closable), _className); var closeIcon = getComponent(this, 'closeIcon'); return _createVNode("div", { "class": className, "style": $attrs.style || { right: '50%' }, "onMouseenter": clearCloseTimer, "onMouseleave": startCloseTimer }, [_createVNode("div", { + "class": "".concat(componentClass, "-content animate__animated animate__shakeX") }, [getSlot(this)]), closable ? _createVNode("a", { "tabindex": "0", "onClick": close, "class": "".concat(componentClass, "-close") }, [closeIcon || _createVNode("span", { "class": "".concat(componentClass, "-close-x") }, null)]) : null]); }
- 打补丁
npx patch-package ant-design-vuenpx patch-package - 重启APP-Over
??自定义AnimateClass
经过上面的修改,虽然可以添加动画,但是动画类是写死的,现在将他改为一个动态的值,方便使用各种特效,随心所欲的更换。 注意+号的位置,是需要修改的 message调用时的入参代码: (路径:node_modules\ant-design-vue\es\message\index.js)['success', 'info', 'warning', 'error', 'loading'].forEach(function (type) { //api[type] = function (content, duration, onClose) {
+ api[type] = function(content,duration,onClose,animateClass) if (isArgsProps(content)) { return api.open(_extends(_extends({}, content), { type: type })); } if (typeof duration === 'function') { onClose = duration; duration = undefined; } return api.open({ content: content, duration: duration, type: type, onClose: onClose,
+ animateClass:animateClass }); }; });
我们修改上述代码,添加一个参数名为animateClass。这个api.open最终会执行getMessageInstance函数,将参数传到Notification中。所以接着修改getMessageInstance调用时的参数,添加animateClass
function notice(args) { var duration = args.duration !== undefined ? args.duration : defaultDuration; var Icon = iconMap[args.type]; var iconNode = Icon ? _createVNode(Icon, null, null) : ''; var target = args.key || key++; var closePromise = new Promise(function (resolve) { var callback = function callback() { if (typeof args.onClose === 'function') { args.onClose(); }return resolve(true); };
getMessageInstance(function (instance) { instance.notice({ key: target, duration: duration, style: args.style || {}, class: args.class, + animateClass: args.animateClass, content: function content() { return _createVNode("div", { "class": "".concat(prefixCls, "-custom-content").concat(args.type ? " ".concat(prefixCls, "-").concat(args.type) : '') }, [args.icon || iconNode, _createVNode("span", null, [args.content])]); }, onClose: callback }); }); });
var result = function result() { if (messageInstance) { messageInstance.removeNotice(target); } };
result.then = function (filled, rejected) { return closePromise.then(filled, rejected); };
result.promise = closePromise; return result; }
api.open就是调用的notice方法,通过args将值取到。
然后到Notification.js修改defineComponent中的render的这段
var noticeNodes = notices.map(function (notice, index) { var update = Boolean(index === notices.length - 1 && notice.updateKey); var key = notice.updateKey ? notice.updateKey : notice.key; var content = notice.content, duration = notice.duration, closable = notice.closable, onClose = notice.onClose, style = notice.style, className = notice.class, + animateClass=notice.animateClass; var close = createChainedFunction(remove.bind(_this, notice.key), onClose); var noticeProps = { prefixCls: prefixCls, duration: duration, closable: closable, update: update, closeIcon: getComponent(_this, 'closeIcon'), onClose: close, onClick: notice.onClick || noop, style: style, class: className, key: key, + animateClass:animateClass }; return _createVNode(Notice, noticeProps, { default: function _default() { return [typeof content === 'function' ? content() : content]; } }); });
再到Notice.js中修改:
props: { duration: PropTypes.number.def(1.5), closable: PropTypes.looseBool, prefixCls: PropTypes.string, update: PropTypes.looseBool, closeIcon: PropTypes.any, onClose: PropTypes.func, + animateClass:"" }
render: function render() { var _className; var prefixCls = this.prefixCls, closable = this.closable, clearCloseTimer = this.clearCloseTimer, startCloseTimer = this.startCloseTimer, close = this.close, $attrs = this.$attrs, + animateClass = this.animateClass; var componentClass = "".concat(prefixCls, "-notice"); var className = (_className = {}, _defineProperty(_className, "".concat(componentClass), 1), _defineProperty(_className, "".concat(componentClass, "-closable"), closable), _className); var closeIcon = getComponent(this, 'closeIcon'); return _createVNode("div", { "class": className, "style": $attrs.style || { right: '50%' }, "onMouseenter": clearCloseTimer, "onMouseleave": startCloseTimer }, [_createVNode("div", { //"class": "".concat(componentClass, "-content")
+ "class":"".concat(componentClass,"-content animate__animated ").concat(animateClass) }, [getSlot(this)]), closable ? _createVNode("a", { "tabindex": "0", "onClick": close, "class": "".concat(componentClass, "-close") }, [closeIcon || _createVNode("span", { "class": "".concat(componentClass, "-close-x") }, null)]) : null]); }
然后生成补丁 npx patch-package ant-design-vue
应用补丁 npx patch-package
再调用message时就可以写成这样:
message.info({ content:'test', animateClass:'animate__rubberBand' })