修改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

圖1-1

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

圖1-5

看到這個是引用了message組件同級的vc-notification,繼續跟一下

圖1-6

index.js直接暴露的Notification,繼續跟,找到newInstance

圖1-7

可以看到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的定義(太長了,我縮了部分無關緊要的,想看具體代碼的可以去搜下倉庫)
圖1-8
這里定義了整個組件,在圖中第109行,看到了noticeNodes的定義(論命名的重要性),這些節點通過notices遍歷的,而notices中的內容,可以看到methods中的add(notice),明白了吧
在map中看到使用了_createVNode,這里使用了一個新的參數 Notice ,接着找一下這個Notice的定義

圖1-10

發現是引用的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,添加成功

📑結論

  1. 設置 package.json
    "script":{
    +    "postinstall":"patch-package"
    }
  2. 安裝依賴
    npm install animate.css --save
    npm install patch-package --save-dev
  3. 修改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]);
    }
  4. 打補丁
    npx patch-package ant-design-vue
    npx patch-package
  5. 重啟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'
})


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM