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

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

一、先上效果图

ActionSheet

Recat 自定义模态框 随心所欲CSS动画_第1张图片
demo1.gif

modal
Recat 自定义模态框 随心所欲CSS动画_第2张图片
demo2.gif

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

一、彻头彻尾的DIY

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

二、动画可以自定义

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

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

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

二、思路

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

三、核心代码

一、使用

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

Recat 自定义模态框 随心所欲CSS动画_第3张图片
demo3.gif
/**
         * 更新actionSheet
         * @param {*} screenX 
         * @param {*} screenY 
         */
        updateXAST(screenX, screenY) {
                console.log(this.props.selfData, "pppppp");
                let content = (
                        
{this.props.selfData.map((item, index) => ( ))}
); 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(, 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 = ( { }}>);
                    break;

                case "XActionSheet":
                    _HTML = ( { }}>);
                    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(
); return (
{/*
{ this.closeModelByAnimate() }}>
*/}
{/* dangerouslySetInnerHTML={{__html:content}} */} {this.props.skin.content}
) } } export default XActionSheet;

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

四、结语

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

你可能感兴趣的:(Recat 自定义模态框 随心所欲CSS动画)