师兄最近深陷React的坑不能自拔~觉得React似乎比angular或者ionic要更有意思(不愧脸书),在业务代码上跑多了,就不断有想法:项目中使用了ant-mobile 但是这个UI框架给人的感觉总是怪怪的(感觉是后娘样的,只能凑合两字形容),于是师兄索性针对于气死人不偿命的模态框做出一个 react版的百变模态框,还可以扩展哦。(目前还没有上网查,应该已经有人做出来了,不管了,正文要开始了~(期待))
一、先上效果图
ActionSheet
modal
以上两图是师兄目前做出的两个demo
是不是动感十足,丝毫不逊色于阿里的ant,而且还要比他更强大。
why?
原因有三:
一、彻头彻尾的DIY
例如demo1的actionSheet 里面的内容都是用react的JSX实现,也就是说除了外面的框框(白板)里面全部可以自定义,感觉就像自己写的一样6。(后面会展示代码)
二、动画可以自定义
css动画样式随便修改,想用什么都可以,(淡入淡出、上来下去...)还有师兄自己研究的坐标飞入动画(哪来哪去)
三、扩展性强,可以直接DIY自己的组件
觉得demo太少?自己动手丰衣足食,该组件支持扩展直接自定义自己的组件模块,然后直接插入即可使用
二、思路
讲了那么多,都是外在的效果核心还是思路。
外部一个顶级父类或者总出入口,主要负责模块的调度和动画的执行,而下级则是自定义的model,比如上面的actionsheet和modal,下级主要负责样式和model的位置同时定义属于自己的动画,这样解耦扩展性很强。
三、核心代码
一、使用
这里有必要讲一下,这个demo中的actionsheet可以做到动态刷新,也就是例如上面那个demo1中actionsheet中的题目按钮,当点击某一个,即能便为选中状态。
/**
* 更新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() }}>
*/}
{this.props.skin.title}
{ this.props.closeModelByAnimate(this.props.closeFnc) }} />
{/* dangerouslySetInnerHTML={{__html:content}} */}
{this.props.skin.content}
)
}
}
export default XActionSheet;
看到这是是不是觉得actionSheet这个demo很简单啊
四、结语
首先要多动手,然后才能发现问题,发现问题后,想办法解决,方法总比困难多。
路漫漫其修远兮,吾将上下而求索。