一、创建react-ts项目
yarn create vite myreact --template react-ts
二、安装依赖
yarn
三、启动项目
npm run dev
四、新建目录
五、pages目录新建个index.tsx,代码来源AntDesgin,然后改造为TS
import TweenOne from 'rc-tween-one';
import React from 'react';
import '../style/index.css'
import Login from "../components/login";
class GridLayout {
private cellWidth: number;
private cellHeight: number;
private gridY: number;
private gridX: number;
private grid: Array<any>;
constructor(rect: number, width: number, height: number) {
this.gridX = Math.floor(width / rect);
this.gridY = Math.floor(height / rect);
this.cellWidth = width / this.gridX;
this.cellHeight = height / this.gridY;
this.grid = [];
for (let i = 0; i < this.gridY; i += 1) {
this.grid[i] = [];
for (let s = 0; s < this.gridX; s += 1) {
this.grid[i][s] = [];
}
}
}
getCells = (e: { x: number; radius: number; y: number; }) => {
const gridArray = [];
const w1 = Math.floor((e.x - e.radius) / this.cellWidth);
const w2 = Math.ceil((e.x + e.radius) / this.cellWidth);
const h1 = Math.floor((e.y - e.radius) / this.cellHeight);
const h2 = Math.ceil((e.y + e.radius) / this.cellHeight);
for (let c = h1; c < h2; c += 1) {
for (let l = w1; l < w2; l += 1) {
gridArray.push(this.grid[c][l]);
}
}
return gridArray;
}
hasCollisions = (t: { x: number; y: number; radius: number; }) => (
this.getCells(t).some(e => e.some((v: any) => this.collides(t, v)))
)
collides = (t: { x: any; y: any; radius: any; }, a: { x: number; y: number; radius: any; }) => {
if (t === a) {
return false;
}
const n = t.x - a.x;
const i = t.y - a.y;
const r = t.radius + a.radius;
return n * n + i * i < r * r;
}
add = (value:any) => {
this.getCells(value).forEach((item) => {
item.push(value);
});
}
}
const getPointPos = (width: number, height: number, length: number) => {
const grid = new GridLayout(150, width, height);
const posArray = [];
const num = 500;
const radiusArray = [20, 35, 60];
for (let i = 0; i < length; i += 1) {
let radius;
let pos;
let j = 0;
for(let j =0; j< num; j+=1) {
radius = radiusArray[Math.floor(Math.random() * radiusArray.length)];
pos = { x: Math.random() * (width - radius * 2) + radius, y: Math.random() * (height - radius * 2) + radius, radius };
if (!grid.hasCollisions(pos)) {
break;
}
}
posArray.push(pos);
grid.add(pos);
}
return posArray;
};
const getDistance = (t: { x: any; y: any; }, a: { x: any; y: any; }) => (Math.sqrt((t.x - a.x) * (t.x - a.x) + (t.y - a.y) * (t.y - a.y)));
class Point extends React.PureComponent {
render() {
const { tx, ty, x, y, opacity, backgroundColor, radius, ...props }:Readonly<any>= this.props;
let transform;
let zIndex = 0;
let animation:any = {
y: (Math.random() * 2 - 1) * 20 || 15,
duration: 3000,
delay:Math.random() * 1000,
yoyo: true,
repeat: -1,
};
if (tx && ty) {
if (tx !== x && ty !== y) {
const distance = getDistance({ x, y }, { x: tx, y: ty });
const g = Math.sqrt(2000000 / (0.1 * distance * distance));
transform = `translate(${g * (x - tx) / distance}px, ${g * (y - ty) / distance}px)`;
} else if (tx === x && ty === y) {
transform = `scale(${80 / radius})`;
animation = { y: 0, yoyo: false, repeat: 0, duration: 300 };
zIndex = 1;
}
}
return (
<div
style={{
left: x - radius,
top: y - radius,
width: radius * 1.8,
height: radius * 1.8,
opacity,
zIndex,
transform,
}}
{...props}
>
<TweenOne
animation={animation}
style={{
backgroundColor,
}}
// @ts-ignore
className={`${this.props.className}-child`}
/>
</div>
);
}
}
class LinkedAnimate extends React.Component {
static defaultProps = {
className: 'linked-animate-demo',
};
num = 60;// 点的个数
private box: any;
constructor(props: {} | Readonly<{}>) {
super(props);
this.state = {
data: getPointPos(1280, 600, this.num).map(item => ({
...item,
opacity: Math.random() * 0.2 + 0.05,
backgroundColor: `rgb(${Math.round(Math.random() * 40 + 120)},230,255)`,
})),
tx: 0,
ty: 0,
};
}
onMouseMove = (e: { clientX: any; clientY: any; }) => {
const cX = e.clientX;
const cY = e.clientY;
const boxRect = this.box.getBoundingClientRect();
// @ts-ignore
const pos = this.state.data.map((item: { x: any; y: any; radius: any; }) => {
const { x, y, radius } = item;
return { x, y, distance: getDistance({ x: cX - boxRect.x, y: cY - boxRect.y }, { x, y }) - radius };
}).reduce((a: { distance: number; }, b: { distance: number; }) => {
if (!a.distance || a.distance > b.distance) {
return b;
}
return a;
});
if (pos.distance < 60) {
this.setState({
tx: pos.x,
ty: pos.y,
});
} else {
this.onMouseLeave();
}
}
onMouseLeave = () => {
this.setState({
tx: 0,
ty: 0,
});
}
render() {
// @ts-ignore
const { className } = this.props;
const { data, tx, ty }:any = this.state;
return (
<div className={`${className}-wrapper`}>
<Login />
<div
className={`${className}-box`}
ref={(c) => { this.box = c; }}
onMouseMove={this.onMouseMove}
onMouseLeave={this.onMouseLeave}
>
{data.map((item:any, i:any) => (
<Point {...item} tx={tx} ty={ty} key={i.toString()} className={`${className}-block`} />
))}
</div>
</div>
);
}
}
export default LinkedAnimate
六、style目录下建个index.css
.linked-animate-demo-wrapper {
overflow: hidden;
height: 100%;
background: GhostWhite;
position: absolute;
width: 100%;
}
.linked-animate-demo-box {
position: absolute;
width: 50%;
height: 50%;
display: block;
left: 0%;
top: 0;
bottom: 0;
right: 70%;
margin: auto;
}
.linked-animate-demo-block {
position: absolute;
transition: transform .45s ease;
}
.linked-animate-demo-block-child {
border-radius: 100%;
width: 100%;
height: 100%;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
七、components下建立个login.tsx
import React from "react";
import {Button, Input, Space} from 'antd';
import Icon,{UserOutlined, EyeInvisibleOutlined, EyeTwoTone } from '@ant-design/icons';
import 'antd/dist/antd.css'
import './login.css'
import {Link} from "react-router-dom"
class Login extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
defaultactive : false,
}
}
render() {
const PasswordSvg = () => (
<svg d="1629272463432" className="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="14838" width="16" height="16">
<path
d="M825.5 832.2h-627c-72.7 0-131.9-59.1-131.9-131.9V323.6c0-72.7 59.1-131.9 131.9-131.9h626.9c72.7 0 131.9 59.1 131.9 131.9v376.7c0 72.8-59.1 131.9-131.8 131.9z m-627-602.7c-51.9 0-94.2 42.2-94.2 94.2v376.7c0 51.9 42.3 94.2 94.2 94.2h626.9c51.9 0 94.2-42.2 94.2-94.2V323.6c0-51.9-42.3-94.2-94.2-94.2H198.5z"
fill="#2c2c2c" p-id="14839"></path>
<path
d="M247.6 646.7h528.8v37.7H247.6zM830.4 388.7h-75.3V351h75.3v37.7z m-131.8 0h-75.3V351h75.3v37.7z m-131.9 0h-75.3V351h75.3v37.7z m-131.8 0h-75.3V351h75.3v37.7z m-131.9 0h-75.3V351H303v37.7zM830.4 547.9h-75.3v-37.7h75.3v37.7z m-131.8 0h-75.3v-37.7h75.3v37.7z m-131.9 0h-75.3v-37.7h75.3v37.7z m-131.8 0h-75.3v-37.7h75.3v37.7z m-131.9 0h-75.3v-37.7H303v37.7z"
fill="#2c2c2c" p-id="14840"></path>
</svg>
);
const PasswordIcon = (props: any) => <Icon component={PasswordSvg} {...props} />;
return (
<div id={"login"}>
<Space direction={"vertical"} align={"center"} size={18} >
<h3>Login Your Account</h3>
<Input placeholder=" Enter username" allowClear={true} className={"userInfo"} prefix={<UserOutlined />}/>
<Input.Password
placeholder=" Enter password"
iconRender={visible => (visible ? <EyeTwoTone/> : <EyeInvisibleOutlined/>)}
className={"userInfo"}
prefix={<PasswordIcon />}
/>
<Link to={'/test'}> <Button type="primary" className={"userInfo"} >Sign In</Button></Link>
</Space>
</div>
)
}
}
export default Login
login.css
#login{
background-color: GhostWhite;
width: 20%;
height: 40%;
text-align: center;
position: relative;
left: 68%;
top: 10%;
z-index: 1;
opacity: 0.8;
}
.userInfo {
width: 260px;
font: 16px bold;
}
h3{
font-size: 24px;
}
八、assets目录下准备几张icon
logo.svg\icon.svg\keyboard.svg
九、main.tsx修改
import React from 'react'
import ReactDOM from 'react-dom'
import LinkedAnimate from './pages/index'
import Home from "./pages/home";
import {BrowserRouter, Route, Switch} from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route path="/test" component={Home}/>
<Route path="/" component={LinkedAnimate}/>
</Switch>
</BrowserRouter>,
document.getElementById('root')
十、pages新建个home.tsx,代码依然是antdesign上的
import {Layout, Menu} from 'antd';
import {
AppstoreOutlined,
BarChartOutlined,
CloudOutlined,
ShopOutlined,
TeamOutlined,
UserOutlined,
UploadOutlined,
VideoCameraOutlined,
} from '@ant-design/icons';
import React from 'react';
const {Header, Content, Footer, Sider} = Layout;
class Home extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
render() { let a = {
width:'100%',
height:'40px'}
return (
<Layout>
<Sider
style={{
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
}}
>
<div className="logo" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['4']}>
<div style={a}>111</div>
<Menu.Item key="1" icon={<UserOutlined />}>
Home
</Menu.Item>
<Menu.Item key="2" icon={<VideoCameraOutlined />}>
Period
</Menu.Item>
<Menu.Item key="3" icon={<UploadOutlined />}>
Stroies
</Menu.Item>
<Menu.Item key="4" icon={<BarChartOutlined />}>
Tasks
</Menu.Item>
<Menu.Item key="5" icon={<CloudOutlined />}>
Bugs
</Menu.Item>
<Menu.Item key="6" icon={<AppstoreOutlined />}>
Statistics
</Menu.Item>
<Menu.Item key="7" icon={<TeamOutlined />}>
UserInfo
</Menu.Item>
<Menu.Item key="8" icon={<ShopOutlined />}>
RoleInfo
</Menu.Item>
</Menu>
</Sider>
<Layout className="site-layout" style={{ marginLeft: 200 }}>
<Header className="site-layout-background" style={{ padding: 0 }} />
<Content style={{ margin: '24px 16px 0', overflow: 'initial' }}>
<div className="site-layout-background" style={{ padding: 24, textAlign: 'center' }}>
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>Ant Design ©2018 Created by Ant UED</Footer>
</Layout>
</Layout>
)
}
}
export default Home
十一、启动看看效果,初学阶段,先记录一下