美文网首页ReactJS
Recat 自定义模态框 随心所欲CSS动画

Recat 自定义模态框 随心所欲CSS动画

作者: 凌风x | 来源:发表于2018-10-11 16:20 被阅读15次

    师兄最近深陷React的坑不能自拔~觉得React似乎比angular或者ionic要更有意思(不愧脸书),在业务代码上跑多了,就不断有想法:项目中使用了ant-mobile 但是这个UI框架给人的感觉总是怪怪的(感觉是后娘样的,只能凑合两字形容),于是师兄索性针对于气死人不偿命的模态框做出一个 react版的百变模态框,还可以扩展哦。(目前还没有上网查,应该已经有人做出来了,不管了,正文要开始了~(期待))

    一、先上效果图

    ActionSheet

    demo1.gif

    modal demo2.gif

    以上两图是师兄目前做出的两个demo
    是不是动感十足,丝毫不逊色于阿里的ant,而且还要比他更强大。
    why?
    原因有三:

    一、彻头彻尾的DIY

    例如demo1的actionSheet 里面的内容都是用react的JSX实现,也就是说除了外面的框框(白板)里面全部可以自定义,感觉就像自己写的一样6。(后面会展示代码)

    二、动画可以自定义

    css动画样式随便修改,想用什么都可以,(淡入淡出、上来下去...)还有师兄自己研究的坐标飞入动画(哪来哪去)

    三、扩展性强,可以直接DIY自己的组件

    觉得demo太少?自己动手丰衣足食,该组件支持扩展直接自定义自己的组件模块,然后直接插入即可使用

    二、思路

    讲了那么多,都是外在的效果核心还是思路。
    外部一个顶级父类或者总出入口,主要负责模块的调度和动画的执行,而下级则是自定义的model,比如上面的actionsheet和modal,下级主要负责样式和model的位置同时定义属于自己的动画,这样解耦扩展性很强。

    三、核心代码

    一、使用

    这里有必要讲一下,这个demo中的actionsheet可以做到动态刷新,也就是例如上面那个demo1中actionsheet中的题目按钮,当点击某一个,即能便为选中状态。

    demo3.gif
    /**
             * 更新actionSheet
             * @param {*} screenX 
             * @param {*} screenY 
             */
            updateXAST(screenX, screenY) {
                    console.log(this.props.selfData, "pppppp");
                    let content = (
                            <div className="seleted-question-act">
                                    <div className="seleted-question-act-btn">
                                            {this.props.selfData.map((item, index) => (
                                                    <button key={index} className={item['finishInfo']['finished'] ? "selected" : "noSelected"}
                                                            onClick={(e) => { this.actionSheetBtnClick(item, index); }}>{(index + 1)}</button>
                                            ))}
                                    </div>
                                    <div className="big-submitBtn-div"><button className="big-submitBtn"
                                            onClick={(e) => {
                                                    this.submitAnswer();
                                            }}>提交答案</button>
                                    </div>
                            </div>);
                    let skins = {
                            type: "XActionSheet",//"XModel",       
                            title: "这是actionshee的标题",//标题
                            showTitle: false,
                            content: content,//内容jsx
                            top: "",//距离顶部 :20%
                            auto_height: false,//是否是自动扩展 true/false
                            clickElementClose: "",//点击元素 关闭model  
                            x: screenX || this.state.skin.x,
                            y: screenY || this.state.skin.y,
                            //flyModel: true,
                    }
                    UtilService.showSuperModel(skins)
                    // this.setState({skin:skins},()=>{superModel.initModel(skins)});
                    //this.setState({ skin: skins }, () => { UtilService.showSuperModel(skins) });
    //UtilService 中的代码
    let superModel = new SuperModel();
      static showSuperModel(skin) {
        superModel.initModel(skin);
      }
    
            }
    

    二、暂且叫超级model吧

    它是一个总控中心负责模块选择和动画的执行

    
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './superModel.css';
    import '../../app/animate.css'
    /**
     * 可调用模块
     */
    import { XModel } from '../sonModel/XModel/XModel';//自定义模态框
    import { XActionSheet } from '../sonModel/XActionSheet/XActionSheet'
    import { UtilService } from '../UtilService/utilService';
    
    //关键字 
    let _ROOT = "__AppModel_Root";
    let _MODEL = "AppModel";
    let _BODY = "statement-body-m";
    let _BACK = "AppModel_background";
    
    
    let superModelSkin = {
        _ROOT: "",
        _MODEL: "",
        _BODY: "",
        _BACK: "",
        _ACTION: "",
    };
    let superModelOption;
    let _HTML ;
    export class SuperModel extends React.Component {
        ID;
        model;
        content_m;
        constructor(props) {
            super(props);
    
            this.showModel = this.showModel.bind(this);
            this.getModel = this.getModel.bind(this);
            this.closeModelByAnimate = this.closeModelByAnimate.bind(this);
            this.ID=Math.random();
        }
    
        componentDidMount() {
            if(!this.ID)this.ID=UtilService.getRandomStr();
            if (!this.model) this.getModel();
            this.showModel();
        }
        componentWillUnmount() { 
            this.clearSelf();
        }
        componentWillReceiveProps(nextProps,nextState) {}
        componentWillUpdate( nextProps,  nextState){}
    
    
        createSuperModel() {
            let temp = document.getElementById(superModelSkin._ROOT);
            if (!temp) {
                let html = document.createElement("div");
                html.setAttribute("id", superModelSkin._ROOT);
                document.getElementsByTagName("body")[0].appendChild(html);
            }
        }
    
        //初始化
        initModel(skin) {
            if(!this.ID)this.ID=Math.random();
            superModelOption = skin;
            this.skinSwitch(skin);//选择皮肤
    
            this.createSuperModel();
    
            console.log(this.model,"model");
            if (!this.model) {
                console.log("====================ReactDOM.render======================");
                ReactDOM.render(<SuperModel skin={superModelOption} ></SuperModel>, document.getElementById(superModelSkin._ROOT));
            }
        }
    
        paramHandler(skin) {
            if (skin.top) {//距离顶部
                if (this.content_m) {
                    this.content_m.style.marginTop = 0;
                    this.content_m.style.top = skin.top;
                }
            }
    
            if (skin.closeClear) {
                let closeClear = skin.closeClear;
                for (let i = 0; i < closeClear.length; i++) {
                    if (closeClear[i].indexOf(".")) {
                        //待做
                    }
                }
            }
        }
    
        showModel() {
            superModelOption = this.props.skin;
            //参数处理
            this.paramHandler(superModelOption);
            //选择模块组件
            //this.modelSwitch();
            //通过动画展示actionSheet
            this.model && this.showComponentByFade();
        }
    
        getModel() {
            if (this.model) console.log("model alive");
            if (!this.model) this.model = document.getElementById(superModelSkin._MODEL);
            if (!this.content_m) this.content_m = document.getElementsByClassName(superModelSkin._BODY)[0];
        }
        /**
         * 供外部方法直接关闭
         * @param {*} option 
         */
        closeModelByOutUser(option) {
            return new Promise((resolve, reject) => {
                this.getModel();
                this.closeModelByAnimate(option, () => {
                    resolve();
                });
            });
        }
        /**
         * 内部关闭
         */
        closeModelByAnimate(options, callback) {
            let option = options ? options : {
                x: superModelOption.x || this.props.skin.x,
                y: superModelOption.y || this.props.skin.y,
                flyModel: superModelOption.flyModel || this.props.skin.flyModel,
            }
            animateAction.hideModelWithCss(this.model, option, callback,
                () => {
                   this.clearSelf();
                });
        }
    
        clearSelf(){
            this.ID=null;
            this.model = null;
            this.content_m=null;
            superModelOption = null;
            superModelSkin = null;
            _HTML = null;
        }
    
        showComponentByFade() {
            animateAction.initAnimateAction(superModelSkin._ACTION);
            let option = {
                x: superModelOption.x || this.props.skin.x,
                y: superModelOption.y || this.props.skin.y,
                flyModel: superModelOption.flyModel || this.props.skin.flyModel,
            }
            animateAction.showModelWithCss(this.model, option);
        }
    
        /**
         * 皮肤选择器
         * @param {*} type 
         */
        skinSwitch(skin) {
            switch (skin.type) {
                case "XModel":
                    superModelSkin = XModel.XModelSkin;
                    break;
                case "XActionSheet":
                    superModelSkin = XActionSheet.XActionSheetSkin;
                    break;
            }
            //css 动画
            if(skin.animate)superModelSkin._ACTION=skin.animate; 
        }
        /**
         * 模式选择器 看到这里就明白 扩展是怎么回事了
         */
        modelSwitch() {
            if (!this.model) this.getModel();
            //if(_HTML)return;
            let pa = superModelOption || this.props && this.props.skin;
            if (this.props) {
                switch (pa.type) {
                    case "XModel":
                        _HTML = (<XModel skin={this.props.skin}
                            closeModelByAnimate={this.closeModelByAnimate}
                            initDataCallBack={() => { }}></XModel>);
                        break;
    
                    case "XActionSheet":
                        _HTML = (<XActionSheet skin={this.props.skin}
                            closeModelByAnimate={this.closeModelByAnimate}
                            initDataCallBack={() => { }}></XActionSheet>);
                        break;
                }
            }
        }
        render() {
            //console.log(this,"render");
            if(!this.ID)this.ID=UtilService.getRandomStr().substring(0,10);
    
            this.modelSwitch();
            return (_HTML);
        }
    }
    let Fadeflag = true;
    export class AnimateAction {
        _ACTION = {
            _IN: [],
            _OUT: [],
            _OUT_TIME: 200,
        };
        initAnimateAction(action) {
            this._ACTION = action;
        }
        /**
         * 动画显示
         * @param {*} obj 
         * @param {*} option 
         * @param {*} callback 
         */
        showModelWithCss(obj, option, callback) {
            document.body.style.overflow = "hidden";
            //显示背景
            let AppBack = document.getElementsByClassName(superModelSkin._BACK)[0];
            AppBack.classList.add("backgroundFadeIn");
    
            //console.log(option);
            //监听背景点击
            obj.addEventListener("click", (event) => {
                if (event.target === event.currentTarget) {
                    this.hideModelWithCss(obj, option, callback);
                }
            });
    
            if (!option.flyModel) {//中心显示模式
                this.animateActionFnc(obj, this._ACTION._IN);
                this.animateEndFnc(obj, this._ACTION._IN);
    
                obj.style.opacity = 1;
                callback && callback();
            } else {//飞行模式
                this.show(obj, option, callback, () => {
                    this.animateActionFnc(obj, this._ACTION._IN);
                    this.animateEndFnc(obj, this._ACTION._IN);
                });
            }
        }
        /**
         * 动画隐藏
         * @param {*} obj 
         * @param {*} option 
         * @param {*} callback 
         * @param {*} modelCallBack 
         */
        hideModelWithCss(obj, option, callback, modelCallBack) {
            //console.log(option);
    
            //隐藏背景
            let AppBack = document.getElementsByClassName(superModelSkin._BACK)[0];
             AppBack.classList.add("backgroundFadeOut");
    
            if (!option.flyModel) {//中心隐藏模式
                this.animateActionFnc(obj, this._ACTION._OUT);
                setTimeout(() => {//去除model-root
                    obj.style.background = "";
                    document.body.style.overflow = "auto";
                    ReactDOM.render("", document.getElementById(superModelSkin._ROOT));
                    callback && callback();
                    modelCallBack && modelCallBack();
                }, this._ACTION._OUT_TIME);
    
            } else {//飞行模式
                this.animateActionFnc(obj, this._ACTION._IN);
                //this.animateEndFnc(obj, this._ACTION._IN);
                // obj.classList.add("fade-out-XY");
                setTimeout(() => {
                    this.hide(obj, option, callback, modelCallBack);
                }, 100);
            }
        }
        /**
             * 动画执行
             * @param {*} obj 
             * @param {*} action 
             */
        animateActionFnc(obj, action) {
            for (let v in action) {
                obj.classList.add(action[v]);
            }
        }
        /**
         * 动画结束回调
         * @param {*} obj 
         */
        animateEndFnc(obj, action) {
            let animationEnd = (function (el) {
                let animations = {
                    animation: 'animationend',
                    OAnimation: 'oAnimationEnd',
                    MozAnimation: 'mozAnimationEnd',
                    WebkitAnimation: 'webkitAnimationEnd',
                };
    
                for (let t in animations) {
                    if (el.style[t] !== undefined) {
                        return animations[t];
                    }
                }
            })(document.createElement('div'));
    
            obj.addEventListener(animationEnd, function () {
                for (let v in action) {
                    obj.classList.remove(action[v]);
                }
            })
        }
        /**
         * 
         * @param {Object} obj
         */
        show(obj, option, callback, modelCallBack) {
            // console.log("fade show", option);
            if (!obj) return;
            let count = 10;//动作总次数
            let time = 20;//每次动作时间
            let num = 0;
            let _X = parseFloat(option.x);//y轴屏幕距离
            let _Y = parseFloat(option.y);//x轴屏幕距离
            let xleft = (_X / window.screen.width) * 100;
            let xtop = (_Y / window.screen.height) * 100;
            let xv = xtop / count;//X轴缩放速率
            let yv = xleft / count//Y轴缩放速率
            let shrink = xtop && xleft ? 1 : 0;//是否定位落地
            if (shrink) {
                obj.style.top = xtop + "%";
                obj.style.left = xleft + "%";
                xtop = this.saveFloat(xtop);
                xleft = this.saveFloat(xleft);
                xv = this.saveFloat(xv);
                yv = this.saveFloat(yv);
            } else {
                obj.style.margin = "auto";//以中心点收缩
            }
            obj.style.overflow = "hidden";
            //obj.style.background = "";
            obj.style.width = 0;
            obj.style.height = 0;
            obj.style.setProperty("z-index", 99999);
            //obj.style.background = "rgba(0,0,0,0.3)";
            if (Fadeflag) {
                let st = setInterval(() => {
                    num++;
                    Fadeflag = false;
                    if (shrink) {
                        xtop = xtop <= 0 ? 0 : this.saveFloat(xtop - xv);
                        xleft = xleft <= 0 ? 0 : this.saveFloat(xleft - yv);
                        obj.style.top = xtop + "%";
                        obj.style.left = xleft + "%";
                    } else {
                        if (num >= 8) num = count;//缓速                           
                    }
                    obj.style.opacity = 0.2;// num / count;
                    obj.style.width = (num * count) + "%";
                    obj.style.height = (num * count) + "%";
                    if (num >= count) {
                        if (shrink) {
                            obj.style.top = "0px";
                            obj.style.left = "0px";
                        }
                        obj.style.width = "100%";
                        obj.style.height = "100%";
                        obj.style.opacity = 1;
                        //obj.style.background="rgba(0,0,0,0.3)";
    
                        modelCallBack && modelCallBack();
                        clearInterval(st);
                        Fadeflag = true;
                        callback && callback();
                    }
                }, time);
            }
        }
        /**
         * 淡出效果
         * @param {Object} obj
         */
        hide(obj, option, callback, modelCallBack) {
            document.body.style.overflow = "auto";
            if (!obj) return;
            let count = 10;//动作次数
            let time = 15;//每次动作时间
            let num = count;
            let _X = option.x;//y轴屏幕距离
            let _Y = option.y;//x轴屏幕距离
            let xleft = (_X / window.screen.width) * 100;
            let xtop = (_Y / window.screen.height) * 100;
            // option.x = (option.x / window.screen.width) * 100;
            // option.y = (option.y / window.screen.height) * 100;
            // let xtop = option.y;//x轴屏幕距离
            // let xleft = option.x;//y轴屏幕距离
            let mtop = 0;//x轴动作增量
            let mleft = 0;//y轴动作增量
            let xv = xtop / count;//X轴缩放速率
            let yv = xleft / count//Y轴缩放速率
            let shrink = xtop && xleft ? 1 : 0;//是否定位落地
            if (shrink) {
                xtop = this.saveFloat(xtop);
                xleft = this.saveFloat(xleft);
                xv = this.saveFloat(xv);
                yv = this.saveFloat(yv);
            } else {
                obj.style.margin = "auto";//以中心点收缩
            }
            if (Fadeflag) {
                obj.style.background = "";
                let st = setInterval(() => {
                    num--;
                    Fadeflag = false;
                    if (shrink) {
                        if (num <= count / 5) num = 0;//缓速
                        mtop = mtop > xtop ? xtop : this.saveFloat(mtop + xv);
                        mleft = mleft > xleft ? xleft : this.saveFloat(mleft + yv);
                        obj.style.top = mtop + "%";
                        obj.style.left = mleft + "%";
                    } else {
                        //if(num<=count/2)num=0;//缓速
                    }
                    obj.style.opacity = num / count;
                    obj.style.width = (num * count) + "%";
                    obj.style.height = (num * count) + "%";
                    if (num <= 0) {
                        if (shrink) {
                            obj.style.top = xtop + "%";
                            obj.style.left = xleft + "%";
                        }
                        obj.style.setProperty("z-index", -100);
                        obj.style.opacity = 0;
                        clearInterval(st);
                        Fadeflag = true;
                        ReactDOM.render("", document.getElementById(superModelSkin._ROOT));
                        callback && callback();
                        modelCallBack && modelCallBack();
                    }
                }, time);
            }
        }
        saveFloat(num, long) {
            return parseFloat(num.toFixed(long ? long : 2));
        }
    }}
    let animateAction = new AnimateAction();
    export default SuperModel;
    

    三、actionsheet

    
    import React from 'react';
    import ReactDOM from 'react-dom';
    import closeBtn from '../../../img/closeBtn.png';
    import './XActionSheet.css';
    /**
     * 底部ActionSheet
     */
    const _ROOT = "AppXActionSheet_Root";
    const _MODEL = "AppXActionSheet";
    const _BODY = "actionSheet-body-m";
    const _BACK = "AppModel_background";
    export class XActionSheet extends React.Component {
        static XActionSheetSkin={
            _ROOT:_ROOT,
            _MODEL:_MODEL,
            _BODY:_BODY,
            _BACK:_BACK,
            _ACTION: {//动画
                _IN:['slideInUp', 'animated','bounce', 'faster'],
                _OUT:['slideInDown','animated', 'bounce', 'fast'],
                _OUT_TIME:200,
            } ,
        }
        constructor(props) {
            super(props);
        }
        componentDidMount() {
        }
        componentWillUnmount() {
        }
        componentWillReceiveProps() {
        }
        render() {
            //if(!this.props.skin.title)return(<div></div>);
            return (
                <div className={_BACK}>
                    <div id={_MODEL} className="actionSheet-box-m" >
                        {/* <div style={this.props.skin.auto_height ? { height: 'auto' } : { height: "35%" }} onClick={(e) => { this.closeModelByAnimate() }}>
                        </div> */}
                        <div className={_BODY} style={this.props.skin.auto_height ? { height: 'auto' } : { height: "60%" }}>
                            <div className="actionSheet-header-m" hidden={!this.props.skin.showTitle}>
                                <div className="title-m-div">
                                    <span className="title-m" >{this.props.skin.title}</span>
                                </div>
                                <div>
                                    <img src={closeBtn} className="actionSheet-header-img-m" onClick={(e) => { this.props.closeModelByAnimate(this.props.closeFnc)  }} />
                                </div>
                            </div>
                            <div className="actionSheet-content-m" >{/* dangerouslySetInnerHTML={{__html:content}} */}
                                {this.props.skin.content}
                            </div>
                        </div>
                    </div>
                </div>
            )
        }
    }
    export default XActionSheet;
    

    看到这是是不是觉得actionSheet这个demo很简单啊

    四、结语

    首先要多动手,然后才能发现问题,发现问题后,想办法解决,方法总比困难多。
    路漫漫其修远兮,吾将上下而求索。

    相关文章

      网友评论

        本文标题:Recat 自定义模态框 随心所欲CSS动画

        本文链接:https://www.haomeiwen.com/subject/ijybaftx.html