后端仔一枚,对前端不是特别熟,不足请谅解。 

 最近使用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

⏲过程

  • 找到组件DOM

通过浏览器控制台找到Message组件生成的DOM

antdesign 删除确认 ant design message_ico

找到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在哪儿

antdesign 删除确认 ant design message_css_02

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

antdesign 删除确认 ant design message_重启_03

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

antdesign 删除确认 ant design message_css_04

可以看到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,
: transitionName,
:
: defaultTop
,
: getContainer,
: maxCount
, function (instance) {
if (messageInstance)
callback(messageInstance);
return;
        }
messageInstance = instance;
callback(instance);
);
}

一处调用:

getMessageInstance(function (instance) {
instance.notice({
: target,
: duration,
: args.style || {},
: 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])]);
,
: 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的定义(太长了,我缩了部分无关紧要的,想看具体代码的可以去搜下仓库)

antdesign 删除确认 ant design message_重启_05

这里定义了整个组件,在图中第109行,看到了noticeNodes的定义(论命名的重要性),这些节点通过notices遍历的,而notices中的内容,可以看到methods中的add(notice),明白了吧

在map中看到使用了_createVNode,这里使用了一个新的参数Notice

antdesign 删除确认 ant design message_重启_06

发现是引用的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
: [BaseMixin],
:
: PropTypes.number.def(1.5),
: PropTypes.looseBool,
: PropTypes.string,
: PropTypes.looseBool,
: PropTypes.any,
: PropTypes.func
,
:
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事件
,
:
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 ||
: '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,添加成功

antdesign 删除确认 ant design message_重启_07

📑结论

  1. 设置 package.json
"script":{
+    "postinstall":"patch-package"
}
  1. 安装依赖
    npm install animate.css --savenpm install patch-package --save-dev
  2. 修改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]);
}
  1. 打补丁
    npx patch-package ant-design-vuenpx patch-package
  2. 重启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'
})