作者:Stony Chen
先看看最终的成果,如下图:
组件成果以及款式咱们参考了https://v4.mui.com/components...,但组件属性和Material-UI有些不一样。另外咱们还多做了一个Avatar组件盘绕的涟漪成果。
须要实现的组件以及属性列表:
Step 1 咱们用上面的命令初始化一个React我的项目
应用 yarn 创立React我的项目
$ yarn create react-app avatar-demo --template typescript
而后咱们进入我的项目并启动
$ cd avatar-demo$ yarn start
Step 2 高级配置
装置 craco , craco-less , react-icons 和 classnames
$ yarn add @craco/craco$ yarn add craco-less$ yarn add react-icons$ yarn add classnames
批改 package.json 里的 scripts 属性
/* package.json */"scripts": {- "start": "react-scripts start",- "build": "react-scripts build",- "test": "react-scripts test",- "eject": "react-scripts eject"+ "start": "craco start",+ "build": "craco build",+ "test": "craco test",+ "eject": "craco eject"}
根目录下新增 craco.config.js
const CracoLessPlugin = require("craco-less")module.exports = { plugins: [ { plugin:CracoLessPlugin, options:{ lessLoaderOptions:{ lessOptions:{ modifyVars :{ // "@prmary-color":"#0073d5" }, javascriptEnabled:true } } } } ]}
而后重新启动我的项目,能够看到我的项目运行如下图
Step 3 文件目录筹备
筹备示例头像文件,并搁置在public目录下
筹备如下代码文件构造,此处咱们没有应用less的module构造
Step 4 创立Avatar组件
简略粗犷一点,说什么都不如间接上代码,此处咱们应用ripple属性作为开关来管制外围涟漪成果,以及能够配置涟漪成果色彩。另外size应用number,能够随便批改,不再应用large, small之类,更加灵便
\src\components\avatar\index.tsx
import classNames from "classnames"import "./index.less"export type AvatarProps = { alt?: string src?: string size?: number icon?: any ripple?: boolean rippleColor?: string [key:string]: any}const Avatar = (props:AvatarProps) => { const { children, alt, src, size, icon, ripple, rippleColor, className, style, ...others } = props const classes = classNames({ avatar: true, [className!]: className }) const finalSize = size || 40 const finalStyle = { ...style, width: `${finalSize}px`, height: `${finalSize}px` } const rippleStyle = { border: `2px solid ${rippleColor}`, width: `${finalSize}px`, height: `${finalSize}px` } if(ripple){ return ( <div className="ripple-container" style={{width:`${finalSize}px`,height:`${finalSize}px`}}> <div className={classes} style={finalStyle} {...others}> {src ? <img alt={alt} src={src}></img>:children} {icon} </div> <span className="ripple" style={rippleStyle}></span> </div> ) } else { return ( <div className={classes} style={finalStyle} {...others}> {src ? <img alt={alt} src={src}></img>:children} {icon} </div> ) }}export default Avatar
以及款式, 此处咱们应用了动画成果来做涟漪成果,并参考了Material UI的成果
\src\components\avatar\index.less
.avatar { height:40px; width: 40px; border-radius: 50%; font-size: 1.25rem; line-height: 1; overflow: hidden; display: flex; align-items: center; justify-content: center; color: rgb(18, 18, 18); background: rgb(117, 117, 117); z-index: 1; img { width: 100%; height: 100%; text-align: center; object-fit: cover; color: transparent; text-indent: 10000px; }}@-webkit-keyframes rippleFrames { 0% { -webkit-transform: scale(.8); -moz-transform: scale(.8); -ms-transform: scale(.8); transform: scale(.8); opacity: 1; } 100% { -webkit-transform: scale(1.4); -moz-transform: scale(1.4); -ms-transform: scale(1.4); transform: scale(1.4); opacity: 0; }}@keyframes rippleFrames { 0% { -webkit-transform: scale(.8); -moz-transform: scale(.8); -ms-transform: scale(.8); transform: scale(.8); opacity: 1; } 100% { -webkit-transform: scale(1.4); -moz-transform: scale(1.4); -ms-transform: scale(1.4); transform: scale(1.4); opacity: 0; }}.ripple-container { position: relative; border-radius: 50%; width: 40px; height: 40px; box-sizing: border-box; .avatar { position: absolute; } .ripple { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 50%; animation: 1.2s ease-in-out 0s infinite normal none running rippleFrames; border: 2px solid red; z-index: 0; box-sizing: border-box; }}
Step 5 创立AvatarGroup组件
代码如下,此处须留神如果total大于以后的子组件,咱们须要再多创立一个Avatar组件显示 +N 的成果, 并且大小须要和后面的Avatar大小统一
\src\components\avatar-group\index.tsx
import classNames from "classnames"import Avatar from "../avatar"import "./index.less"export type AvatarGroupProps = { total?: number [key:string]: any}const AvatarGroup = (props:AvatarGroupProps) => { const { children, total, className, ...others } = props const classes = classNames({ "avatar-group": true, [className!]: className }) let finalSize = 40 if(children && children.type && children.type.name === 'Avatar') { finalSize = children.props.size || finalSize } else if (children && children.length > 0){ if(children[0].type && children[0].type.name === 'Avatar'){ finalSize = children[0].props.size || finalSize } } return ( <div className={classes} {...others}> {children} {total && total - children.length ? <Avatar size={finalSize} style={{fontSize:"0.85rem"}}>{`+${total - children.length}`}</Avatar> : null} </div> )}export default AvatarGroup
以及款式,AvatarGroup组件款式绝对就比较简单一点
\src\components\avatar-group\index.less
.avatar-group { display: flex; >* { margin-left: -8px; border: 1px solid #fff; box-sizing: border-box; } > :first-child { margin-left: 0; }}
Step 6 创立Badge组件
代码如下,同样咱们还是用ripple属性来管制Badge组件的涟漪成果。另外这里留神的是地位,咱们裸露了left,top, 然而如同在不同的电脑和浏览器,地位仿佛有点点偏差
\src\components\badge\index.tsx
import classNames from "classnames"import React from "react"import "./index.less"export type BadgeProps = { ripple?: boolean size?: number color?: string left?: string top?: string content?: React.ReactDOM | string | number [key:string]: any}const Badge = (props:BadgeProps) => { const { children, size, color, ripple, top, left, content, className, ...others } = props const classes = classNames({ "badge-container": true, ripple: ripple, [className!]: className }) let finalSize = size || 8 let location = { left, top} if(!top) { location.top ="30px" } if(!left) { location.left ="30px" } const style = { ...location, background: color, color: ripple ? color : "#fff", width: content ? "auto" : finalSize, height: finalSize, padding: content ? "0 3px" : 0, borderRadius: Math.floor(finalSize! / 2), fontSize: finalSize! - 2 > 12 ? 12 : finalSize! - 2, } return ( <div className={classes} {...others}> {children} <span className="badge" style={style}>{content}</span> </div> )}export default Badge
以及款式, 用了和后面相似的涟漪成果
\src\components\badge\index.less
@-webkit-keyframes badgeRippleFrames { 0% { -webkit-transform: scale(.8); -moz-transform: scale(.8); -ms-transform: scale(.8); transform: scale(.8); opacity: 1; } 100% { -webkit-transform: scale(2.4); -moz-transform: scale(2.4); -ms-transform: scale(2.4); transform: scale(2.4); opacity: 0; }}@keyframes badgeRippleFrames { 0% { -webkit-transform: scale(.8); -moz-transform: scale(.8); -ms-transform: scale(.8); transform: scale(.8); opacity: 1; } 100% { -webkit-transform: scale(2.4); -moz-transform: scale(2.4); -ms-transform: scale(2.4); transform: scale(2.4); opacity: 0; }}.badge-container { position: relative; .badge { position: absolute; width: 8px; height: 8px; background: rgb(68, 183, 0); color: rgb(68, 183, 0); box-shadow: #fff 0 0 0 2px; z-index: 1; box-sizing: border-box; border-radius: 50%; line-height: 1; display: flex; align-items: center; justify-content: center; } &.ripple { .badge { &::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 50%; border: 1px solid currentColor; animation: 1.2s ease-in-out 0s infinite normal none running badgeRippleFrames; content: ''; box-sizing: border-box; } } }}
Step 7 最初一步,批改App.tsx和App.less文件,展现demo
代码如下,咱们一排一排地展现不同类型的成果
\src\App.tsx
import logo from './logo.svg';import Avatar from './components/avatar';import Badge from './components/badge';import AvatarGroup from './components/avatar-group';import { MdAirplay, MdAlarm, MdShare } from 'react-icons/md'import './App.less';function App() { return ( <div className="App"> <div className='row'> <Avatar alt="Peter Pan" ripple rippleColor='#44b700' src="samples/1.jpg"></Avatar> <Avatar alt="Peter Pan" ripple src="/samples/2.jpg"></Avatar> <Avatar alt="Peter Pan" ripple rippleColor='green' src="/samples/3.jpg"></Avatar> <Avatar alt="Peter Pan" src="/samples/4.jpg"></Avatar> <Avatar alt="Peter Pan" src={logo}></Avatar> </div> <div className='row'> <Badge> <Avatar alt="Peter Pan" src="/samples/4.jpg"></Avatar> </Badge> <Badge color="yellow"> <Avatar alt="Peter Pan" src="/samples/4.jpg"></Avatar> </Badge> <Badge color="grey"> <Avatar alt="Peter Pan" src="/samples/4.jpg"></Avatar> </Badge> <Badge color="red" size={10} ripple> <Avatar alt="Peter Pan" src="samples/1.jpg"></Avatar> </Badge> <Badge color="green" size={10} ripple> <Avatar alt="Peter Pan" src="/samples/2.jpg"></Avatar> </Badge> <Badge color="blue" size={10} ripple top="2px"> <Avatar alt="Peter Pan" src="/samples/3.jpg"></Avatar> </Badge> <Badge ripple> <Avatar alt="Peter Pan" src="/samples/4.jpg"></Avatar> </Badge> <Badge content={20} size={12} color="red"> <Avatar alt="Peter Pan" src={logo}></Avatar> </Badge> </div> <div className='row'> <Avatar alt="Peter Pan" size={24} src="/samples/1.jpg"></Avatar> <Avatar alt="Peter Pan" src="/samples/2.jpg"></Avatar> <Avatar alt="Peter Pan" size={56} src="/samples/3.jpg"></Avatar> <Avatar alt="Peter Pan" size={80} src="/samples/4.jpg"></Avatar> </div> <div className='row'> <Avatar>K</Avatar> <Avatar style={{background:"yellow"}}>S</Avatar> <Avatar style={{background:"blue"}}>J</Avatar> <Avatar style={{background:"red"}}>N</Avatar> <Avatar icon={<MdAlarm/>}></Avatar> <Avatar icon={<MdAirplay/>}></Avatar> <Avatar icon={<MdShare/>}></Avatar> </div> <div className='row'> <AvatarGroup total={20}> <Avatar alt="Peter Pan" src="/samples/1.jpg"></Avatar> <Avatar alt="Peter Pan" src="/samples/2.jpg"></Avatar> <Avatar alt="Peter Pan" src="/samples/3.jpg"></Avatar> <Avatar alt="Peter Pan" src="/samples/4.jpg"></Avatar> </AvatarGroup> </div> <div className='row'> <AvatarGroup total={20}> <Avatar alt="Peter Pan" ripple rippleColor='red' src="/samples/1.jpg"></Avatar> <Avatar alt="Peter Pan" src="/samples/2.jpg"></Avatar> <Avatar alt="Peter Pan" src="/samples/3.jpg"></Avatar> <Avatar alt="Peter Pan" src="/samples/4.jpg"></Avatar> </AvatarGroup> </div> </div> );}export default App;
以及款式
\src\App.less
.App { text-align: center; padding: 30px;}.row { margin-bottom: 50px; display: flex; flex-wrap: wrap; >* { margin: 0 20px 20px 0; }}
总结
通过以上的辛苦致力,咱们终于可能失去后面效果图所示的完满成绩。做这个demo有点匆忙,还有一些不尽意的中央,还待改善,以及做的过程中可能遇到的一些问题:
- 这里只是作为demo,并未应用module化的less款式。
- 我在想如果AvatarGroup蕴含一个带Badge的Avatar的话,款式可能会乱掉。不过我感觉不应该这么应用吧,AvatarGroup的子组件必须是Avatar组件,如果不是则能够加个报错提醒。
- 一开始发现自己不会查看Material UI上的涟漪@keyframes动画成果,起初我用safari浏览能够查到@keyframes。
参考:
https://v4.mui.com/components...