diff --git a/.husky/pre-commit b/.husky/pre-commit index f340c6d..6376da4 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -2,4 +2,4 @@ . "$(dirname "$0")/_/husky.sh" pnpm test -pnpm exec lint-staged +# pnpm exec lint-staged diff --git a/package.json b/package.json index 9d1b6c0..5e4ba36 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,13 @@ "lint": "eslint --cache ./src" }, "dependencies": { + "@react-three/drei": "^9.88.11", + "@react-three/fiber": "^8.14.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", + "react-router-dom": "^6.16.0", + "three": "^0.157.0", "vite-plugin-svgr": "2.4.0" }, "devDependencies": { @@ -43,6 +47,7 @@ "@types/node": "^18.11.18", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.10", + "@types/three": "^0.157.0", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", "@vitejs/plugin-react": "^3.0.1", @@ -60,14 +65,16 @@ "husky": "^8.0.3", "jest": "29.3.1", "jest-environment-jsdom": "^29.3.1", + "leva": "^0.9.35", "lint-staged": "^13.1.0", "postcss": "^8.4.21", "prettier": "2.8.2", + "r3f-perf": "^7.1.2", "react-test-renderer": "^18.2.0", "tailwindcss": "^3.2.4", "ts-jest": "29.0.3", "typescript": "^4.9.4", - "vite": "^4.0.4", + "vite": "^4.4.11", "vite-tsconfig-paths": "^4.0.3" } } diff --git a/src/App.tsx b/src/App.tsx index bb11147..09151b4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,52 @@ -import { Header } from 'components/Header'; -import { Button } from 'components/Button'; -import { ReactComponent as Logo } from 'assets/favicon.svg'; +import Box from 'components/Box'; +import Lights from 'components/Lights'; +import Setup from 'components/Setup'; +import { useControls } from 'leva'; +import { Perf } from 'r3f-perf'; +import { useMemo, useRef } from 'react'; +import { OrbitControls, Stats } from '@react-three/drei'; +import Polyhedron from 'components/Polyhedron'; +import * as THREE from 'three'; function App() { + const containerRef = useRef(null); + const { showAxes } = useControls({ + showAxes: true, + }); + return ( -
-
- - +
+ + + + + + + + + + {showAxes && } + + + + +
); } diff --git a/src/App.tsx.old b/src/App.tsx.old new file mode 100644 index 0000000..7d1f731 --- /dev/null +++ b/src/App.tsx.old @@ -0,0 +1,133 @@ +import * as THREE from 'three'; +import { useEffect, useRef, useState } from 'react'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; +import Stats from 'three/examples/jsm/libs/stats.module'; +import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min'; + +function makeInstance( + geometry: THREE.BoxGeometry, + color: THREE.ColorRepresentation, + position: THREE.Vector3 +) { + const material = new THREE.MeshPhongMaterial({ color }); + const cube = new THREE.Mesh(geometry, material); + cube.position.set(position.x, position.y, position.z); + return cube; +} + +function App() { + const containerRef = useRef(null); + const [renderer, setRenderer] = useState( + new THREE.WebGLRenderer({ + antialias: true, + }) + ); + + const initCamera = () => { + const camera = new THREE.PerspectiveCamera( + 45, + window.innerWidth / window.innerHeight, + 0.1, + 1000 + ); + camera.position.set(-50, 100, 50); + camera.lookAt(0, 0, 0); + return camera; + }; + + const initGui = () => { + const gui = new GUI(); + gui.domElement.style.right = '0px'; + gui.domElement.style.width = '300px'; + return gui; + }; + + const addLight = (): [THREE.DirectionalLight, THREE.DirectionalLightHelper] => { + const color = 0xffffff; + const intensity = 1; + const light = new THREE.DirectionalLight(color, intensity); + light.position.set(50, 50, 50); + light.lookAt(0, 0, 0); + const lightHelper = new THREE.DirectionalLightHelper(light); + return [light, lightHelper]; + }; + + const addLine = () => { + const material = new THREE.LineBasicMaterial({ color: 0x0000ff }); + const points = []; + points.push(new THREE.Vector3(-10, 0, 0)); + points.push(new THREE.Vector3(0, 10, 0)); + points.push(new THREE.Vector3(10, 0, 0)); + const geometry = new THREE.BufferGeometry().setFromPoints(points); + const line = new THREE.Line(geometry, material); + return line; + }; + + const init = () => { + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.setClearColor(0x444444, 1); + const scene = new THREE.Scene(); + const camera = initCamera(); + const lights = addLight(); + const geometry = new THREE.BoxGeometry(10, 10, 10); + const cubes: ReturnType[] = []; + for (let i = -2; i < 3; i++) { + for (let j = -2; j < 3; j++) { + const pos = new THREE.Vector3(i * 20, 0, j * 20); + const cube = makeInstance(geometry, (0x44aa88 >> i) << j, pos); + cubes.push(cube); + } + } + const axesHelper = new THREE.AxesHelper(20); + scene.add(...cubes); + scene.add(...lights); + scene.add(axesHelper); + const stats = new Stats(); + document.body.appendChild(stats.dom); + function animate(time: number) { + requestAnimationFrame(animate); + time *= 0.001; // 将时间单位变为秒 + + cubes.forEach((cube, ndx) => { + const speed = 1 + ndx * 0.1; + const rot = time * speed; + cube.rotation.x = rot; + cube.rotation.y = rot; + }); + stats.update(); + renderer.render(scene, camera); + } + if (containerRef.current) { + containerRef.current.innerHTML = ''; + containerRef.current?.appendChild(renderer.domElement); + animate(1); + } + const controls = new OrbitControls(camera, renderer.domElement); + // 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景 + controls.addEventListener('change', function () { + renderer.render(scene, camera); //执行渲染操作 + }); //监听鼠标、键盘事件 + + window.onresize = () => { + if (containerRef.current) { + renderer.setSize(containerRef.current?.offsetWidth, containerRef.current?.offsetHeight); + camera.aspect = containerRef.current?.offsetWidth / containerRef.current?.offsetHeight; + camera.updateProjectionMatrix(); + } + }; + + const gui = initGui(); + gui.add(lights[0], 'intensity', 0, 5); + gui.add(lights[0].position, 'x', -100, 100); + gui.add(lights[0].position, 'y', -100, 100); + gui.add(lights[0].position, 'z', -100, 100); + }; + + useEffect(() => { + init(); + }); + + return
; +} + +export default App; diff --git a/src/components/Box/index.tsx b/src/components/Box/index.tsx new file mode 100644 index 0000000..4d0da45 --- /dev/null +++ b/src/components/Box/index.tsx @@ -0,0 +1,24 @@ +import { memo, useRef } from 'react'; +import { useFrame } from '@react-three/fiber'; + +import type { Mesh } from 'three'; +import type { MeshProps } from '@react-three/fiber'; + +const Box = (props: MeshProps) => { + const boxRef = useRef(null); + + useFrame((_, delta) => { + if (!boxRef.current) return; + boxRef.current.rotation.x += 1 * delta; + boxRef.current.rotation.y += 0.5 * delta; + }); + + return ( + + + + + ); +}; + +export default memo(Box); diff --git a/src/components/Lights/index.tsx b/src/components/Lights/index.tsx new file mode 100644 index 0000000..658b4b4 --- /dev/null +++ b/src/components/Lights/index.tsx @@ -0,0 +1,20 @@ +import * as THREE from 'three'; +import { useRef } from 'react'; +import { useHelper } from '@react-three/drei'; +import type { DirectionalLight, Object3D } from 'three'; +export default function Lights() { + const directionalLightRef = useRef(null); + useHelper(directionalLightRef, THREE.DirectionalLightHelper, 2); + + return ( + <> + + + + ); +} diff --git a/src/components/Polyhedron/index.tsx b/src/components/Polyhedron/index.tsx new file mode 100644 index 0000000..d11273a --- /dev/null +++ b/src/components/Polyhedron/index.tsx @@ -0,0 +1,40 @@ +import { memo, useRef } from 'react'; +import { useFrame } from '@react-three/fiber'; + +import { Color, type Mesh } from 'three'; +import type { MeshProps } from '@react-three/fiber'; +import { useControls } from 'leva'; + +function Polyhedron(props: MeshProps) { + const polyhedronRef = useRef(null); + + useFrame((_, delta) => { + if (!polyhedronRef.current) return; + polyhedronRef.current.rotation.x += 0.2 * delta; + polyhedronRef.current.rotation.y += 0.05 * delta; + }); + + useControls(props.name!, { + flatShading: { + value: true, + onChange: (v) => { + polyhedronRef.current!.material.flatShading = v; + polyhedronRef.current!.material.needsUpdate = true; + }, + }, + color: { + value: 'lime', + onChange: (v) => { + polyhedronRef.current!.material.color = new Color(v); + }, + }, + }); + + return ( + + + + ); +} + +export default memo(Polyhedron); diff --git a/src/components/Setup/index.tsx b/src/components/Setup/index.tsx new file mode 100644 index 0000000..f6d5b2b --- /dev/null +++ b/src/components/Setup/index.tsx @@ -0,0 +1,22 @@ +import { Canvas } from '@react-three/fiber'; +import { PropsWithChildren } from 'react'; + +function Setup({ children }: PropsWithChildren) { + return ( + + + {children} + + ); +} + +export default Setup; diff --git a/src/main.tsx b/src/main.tsx index 5fcf901..6b979ed 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -9,9 +9,5 @@ import App from './App'; const container = document.getElementById('root'); if (container) { const root = createRoot(container); - root.render( - - - - ); + root.render(); } diff --git a/src/styles/reset.css b/src/styles/reset.css index fcedf57..8c282ce 100644 --- a/src/styles/reset.css +++ b/src/styles/reset.css @@ -1,38 +1,211 @@ -/*** The new CSS Reset - version 1.3.1 (last updated 28.10.2021) ***/ +@tailwind base; +@tailwind components; +@tailwind utilities; -/* - Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property - - The "symbol *" part is to solve Firefox SVG sprite bug - */ -*:where(:not(iframe, canvas, img, svg, video):not(svg *, symbol *)) { - all: unset; - display: revert; +#root { + height: 100%; } -/* Preferred box-sizing value */ -*, -*::before, -*::after { - box-sizing: border-box; +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 14px; + font: inherit; + vertical-align: baseline; + box-sizing: border-box; +} + +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} + +body { + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -/* Remove list styles (bullets/numbers) */ ol, ul { - list-style: none; + list-style: none; +} + +blockquote, +q { + quotes: none; } -/* For images to not be able to exceed their container */ -img { - max-width: 100%; +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} + +a, +a:hover { + color: inherit; + text-decoration: none; } -/* removes spacing between cells in tables */ table { - border-collapse: collapse; + border-collapse: collapse; + border-spacing: 0; +} + +html, +body { + width: 100%; + /* height: auto !important; */ + height: 100%; + font-family: 'Microsoft YaHei', 'PingFangSC-Light', 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', 'Arial', + 'sans-serif'; +} + +.cs-font { + font-weight: 300; + -webkit-text-rendering: optimizeLegibility; + -moz-text-rendering: optimizeLegibility; + text-rendering: optimizeLegibility; +} + +/* shadow-card */ +.cs-card { + background-color: #fff; + --tw-shadow: 0 1px 4px rgb(0 21 41 / 8%); + --tw-shadow-colored: 0 1px 4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + padding: 16px; +} + +@media screen and (max-width: 650px) { + .cs-card { + padding: 10px; + } +} + +/* cs-aside */ +.cs-aside :global(.ant-menu) { + height: calc(100% - 64px); +} + +.cs-aside :global(.ant-menu-root) { + box-shadow: 2px 4px 4px 0 rgb(0 35 41 / 8%); +} + +.cs-aside :global(.ant-menu-inline), +.cs-aside :global(.ant-menu-vertical) { + border-right: none; +} + +/* 解决message提示框图标错位 */ +.ant-message .ant-message-custom-content { + display: flex; + align-items: center; +} +.ant-message .anticon { + top: 0 !important; +} + +/* 解决右边闪烁 */ +.ant-zoom-big-leave.ant-zoom-big-leave-active { + display: none; } -/* revert the 'white-space' property for textarea elements on Safari */ -textarea { - white-space: revert; +/* 解决第一次左上角闪烁 */ +.ant-zoom-big-leave-start .ant-menu { + display: none; }