diff --git a/src/components/stateless/DynamicBackground/icons.js b/src/components/stateless/DynamicBackground/icons.js
new file mode 100644
index 00000000..180d9282
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/icons.js
@@ -0,0 +1,35 @@
+import React from 'react'
+import ReactIcon from './icons/react.svg'
+import AngularIcon from './icons/angular.svg'
+import JSIcon from './icons/javascript.svg'
+import NPMIcon from './icons/npm.svg'
+import TSIcon from './icons/typescript.svg'
+import VueIcon from './icons/vue.svg'
+import ReduxIcon from './icons/redux.svg'
+
+const icons = [
+ { size: 'md', type: 'outline', icon: null },
+ { size: 'lg', type: 'fill', icon: },
+ { size: 'sm', type: 'outline', icon: null },
+ { size: 'lg', type: 'outline', icon: },
+ { size: 'md', type: 'outline', icon: null },
+ { size: 'md', type: 'fill', icon: },
+ { size: 'sm', type: 'outline', icon: null },
+ { size: 'md', type: 'outline', icon: null },
+ { size: 'sm', type: 'outline', icon: null },
+ { size: 'lg', type: 'outline', icon: },
+ { size: 'md', type: 'fill', icon: null },
+ { size: 'md', type: 'outline', icon: null },
+ { size: 'md', type: 'outline', icon: null },
+ { size: 'sm', type: 'fill', icon: },
+ { size: 'sm', type: 'fill', icon: null },
+ { size: 'md', type: 'outline', icon: null },
+ { size: 'lg', type: 'fill', icon: },
+ { size: 'sm', type: 'outline', icon: null },
+ { size: 'sm', type: 'outline', icon: null },
+ { size: 'md', type: 'fill', icon: },
+ { size: 'sm', type: 'outline', icon: null },
+ { size: 'sm', type: 'outline', icon: null },
+]
+
+export default icons
diff --git a/src/components/stateless/DynamicBackground/icons/angular.svg b/src/components/stateless/DynamicBackground/icons/angular.svg
new file mode 100644
index 00000000..eaf72674
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/icons/angular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/stateless/DynamicBackground/icons/deno.svg b/src/components/stateless/DynamicBackground/icons/deno.svg
new file mode 100644
index 00000000..4ba620d5
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/icons/deno.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/stateless/DynamicBackground/icons/javascript.svg b/src/components/stateless/DynamicBackground/icons/javascript.svg
new file mode 100644
index 00000000..ff5b132a
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/icons/javascript.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/stateless/DynamicBackground/icons/npm.svg b/src/components/stateless/DynamicBackground/icons/npm.svg
new file mode 100644
index 00000000..b04d3892
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/icons/npm.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/stateless/DynamicBackground/icons/react.svg b/src/components/stateless/DynamicBackground/icons/react.svg
new file mode 100644
index 00000000..5434c606
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/icons/react.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/src/components/stateless/DynamicBackground/icons/redux.svg b/src/components/stateless/DynamicBackground/icons/redux.svg
new file mode 100644
index 00000000..04eb989f
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/icons/redux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/stateless/DynamicBackground/icons/typescript.svg b/src/components/stateless/DynamicBackground/icons/typescript.svg
new file mode 100644
index 00000000..c2e4d58e
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/icons/typescript.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/stateless/DynamicBackground/icons/vue.svg b/src/components/stateless/DynamicBackground/icons/vue.svg
new file mode 100644
index 00000000..4c66f2a6
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/icons/vue.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/stateless/DynamicBackground/index.jsx b/src/components/stateless/DynamicBackground/index.jsx
new file mode 100644
index 00000000..689fc944
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/index.jsx
@@ -0,0 +1,127 @@
+import React, { useEffect, useRef, useState } from 'react'
+import { useDebouncedCallback } from 'use-debounce'
+import icons from './icons'
+import styles from './index.module.less'
+
+const setThresholds = (size, x, y) => {
+ if (size === 'sm') {
+ return {
+ hoverX: x * 0.03,
+ hoverY: y * 0.03,
+ }
+ }
+
+ if (size === 'md') {
+ return {
+ hoverX: x * 0.06,
+ hoverY: y * 0.06,
+ }
+ }
+
+ if (size === 'lg') {
+ return {
+ hoverX: x * 0.1,
+ hoverY: y * 0.1,
+ }
+ }
+}
+
+const DynamicBackground = () => {
+ const [hasMounted, setHasMounted] = useState(false)
+ const heroRef = useRef()
+ const iconsRef = useRef([])
+ const iconCount = icons?.length
+ const { offsetWidth: width, offsetHeight: height } = heroRef?.current ?? {}
+
+ const onHover = ({ clientX, clientY }) => {
+ iconsRef.current.forEach((item) => {
+ const { xPos, yPos } = item?.initialPositions ?? {}
+ const size = item.size
+ const { hoverX, hoverY } = setThresholds(size, clientX, clientY)
+
+ if (item.refItem) {
+ item.refItem.style.transform = `translate(${xPos + hoverX}px, ${yPos + hoverY}px)`
+ }
+ })
+ }
+ const onHoverDebounced = useDebouncedCallback(onHover, 10)
+
+ useEffect(() => {
+ setHasMounted(true)
+ const refVal = heroRef?.current
+ if (!refVal) return
+ refVal.addEventListener('mousemove', onHoverDebounced)
+ refVal.addEventListener('resize', onHoverDebounced)
+
+ return () => {
+ if (!refVal) return
+ refVal.removeEventListener('mousemove', onHoverDebounced)
+ refVal.removeEventListener('resize', onHoverDebounced)
+ }
+ }, [])
+
+ return (
+
+ {hasMounted && (
+
+ {icons.map(({ size, type, icon }, i) => {
+ const rows = Math.ceil(Math.sqrt(iconCount))
+ const cols = Math.ceil(iconCount / rows)
+
+ const cellWidth = width / cols
+ const cellHeight = height / rows
+
+ const row = Math.floor(i / cols)
+ const col = i % cols
+
+ const xOffset = Math.random() * (cellWidth - 75)
+ const yOffset = Math.random() * (cellHeight - 75)
+
+ const baseX = col * cellWidth
+ const baseY = row * cellHeight
+
+ const xPos = baseX + xOffset
+ const yPos = baseY + yOffset
+
+ return (
+
+ (iconsRef.current[i] = {
+ refItem,
+ initialPositions: {
+ xPos,
+ yPos,
+ },
+ size,
+ })
+ }
+ className={`${styles.icon} ${styles[type]} ${styles[size]}`}
+ style={{
+ transform: `translateX(${xPos}px) translateY(${yPos}px)`,
+ }}
+ >
+ {icon && icon}
+
+ )
+ })}
+
+ )}
+
+
+ Grow your
Frontend Skills
+
+
+ with React, Angular, Vue, Typescript, Redux,{' '}
+ Node, GraphQL,CSS, HTML, Webpack,{' '}
+ Rollup, ESLint, Prettier, Stylelint,PostCSS,{' '}
+ Styled Components, Emotion, Material UI, Ant Design,{' '}
+ Bootstrap, Foundation,React Native, Flutter,{' '}
+ Python, Go,JavaScript, and NPM
+
+
+
+ )
+}
+
+export default DynamicBackground
diff --git a/src/components/stateless/DynamicBackground/index.module.less b/src/components/stateless/DynamicBackground/index.module.less
new file mode 100644
index 00000000..85fdd061
--- /dev/null
+++ b/src/components/stateless/DynamicBackground/index.module.less
@@ -0,0 +1,108 @@
+.hero {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ margin: 0 auto;
+ padding: 50px 0;
+ position: relative;
+ height: 600px;
+ overflow: hidden;
+ background: radial-gradient(circle, hsl(30deg 100% 8% / 100%) 0%, rgb(0 0 0) 100%);
+}
+
+.heroText {
+ text-align: center;
+ color: #fff;
+ max-width: 750px;
+ z-index: 10;
+
+ h1 {
+ font-size: 76px;
+ margin: 0 0 30px;
+ font-weight: 300;
+
+ span {
+ background: linear-gradient(30deg, #ff8000 40%, #ad1d1d 70%);
+ background-clip: text;
+ color: transparent;
+ }
+ }
+
+ h3 {
+ font-size: 20px;
+ font-weight: 300;
+ }
+
+ a {
+ margin-top: 20px;
+ display: inline-block;
+ color: #fff;
+ text-decoration: none;
+ font-size: 20px;
+ border-radius: 8px;
+ background: #ad1d1d;
+ padding: 20px 30px;
+ }
+}
+
+.iconLayer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 100%;
+ width: 100%;
+}
+
+.icon {
+ position: absolute;
+ border-radius: 12px;
+ transition: transform 1s ease-out;
+ animation: icon-fade-in 7s ease forwards;
+ padding: 10px;
+
+ svg {
+ width: 100%;
+ height: 100%;
+ }
+
+ path {
+ color: #fff;
+ }
+
+ &.outline {
+ border: 1px solid #414141;
+ }
+
+ &.fill {
+ background: #414141;
+ }
+
+ &.sm {
+ height: 40px;
+ width: 40px;
+ z-index: 1;
+ }
+
+ &.md {
+ height: 60px;
+ width: 60px;
+ z-index: 3;
+ }
+
+ &.lg {
+ height: 80px;
+ width: 80px;
+ z-index: 5;
+ }
+}
+
+@keyframes icon-fade-in {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 0.6;
+ }
+}
diff --git a/src/pages/home/index.jsx b/src/pages/home/index.jsx
index 4f4624c6..56a64c19 100644
--- a/src/pages/home/index.jsx
+++ b/src/pages/home/index.jsx
@@ -33,7 +33,7 @@ import TagCloud from '@stateless/TagCloud'
import ShiCode from '@stateless/ShiCode'
// import SlideLinear from '@stateless/SlideLinear'
// import Masonry from '@container/masonryContainer'
-
+import DynamicBackground from '@stateless/DynamicBackground'
import { oneApiChat, prettyObject } from '@utils/aidFn'
import { fireConfetti } from '@utils/confetti'
@@ -382,18 +382,20 @@ const Home = () => {
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -434,7 +436,7 @@ const Home = () => {
-
+
= ({ item }) => {
@@ -449,6 +451,9 @@ const GroceryItem: React.FC = ({ item }) => {
`}
/>
+
)
}
diff --git a/src/pages/motion/index.jsx b/src/pages/motion/index.jsx
index e0dd0120..2c6dbd9a 100644
--- a/src/pages/motion/index.jsx
+++ b/src/pages/motion/index.jsx
@@ -265,15 +265,9 @@ const ParallaxVert = () => {
>
-
{
repeat: Infinity,
}}
>
-
+ */}
+
+
{
+ console.log(latest.x, latest.y)
+ }}
+ onAnimationStart={() => {
+ console.log('Animation started')
+ setIsAnimation(true)
+ }}
+ onAnimationComplete={(definition) => {
+ setIsAnimation(false)
+ console.log('Completed animating', definition)
+ }}
+ >
+
+ {
+ await controller2.start({ x: 100 })
+ await controller2.start({ y: -200 })
+ await controller2.start({ x: -100 })
+ controller2.start({ y: 0 })
+ }}
+ >
+ 开始
+
+
+
{
width: 800,
height: 400,
border: '1px solid #aaa',
- margin: '0 auto',
+ margin: '20px auto',
}}
ref={constraintsRef}
>
@@ -320,7 +368,7 @@ const ParallaxVert = () => {
position: 'sticky',
top: '40px',
left: 0,
- zIndex: 10,
+ zIndex: 3,
}}
>
拖动元素
@@ -389,65 +437,7 @@ const ParallaxVert = () => {
-
-
{
- console.log(latest.x, latest.y)
- }}
- onAnimationStart={() => {
- console.log('Animation started')
- setIsAnimation(true)
- }}
- onAnimationComplete={(definition) => {
- setIsAnimation(false)
- console.log('Completed animating', definition)
- }}
- >
-
- {
- await controller2.start({ x: 100 })
- await controller2.start({ y: -200 })
- await controller2.start({ x: -100 })
- controller2.start({ y: 0 })
- }}
- >
- 开始
-
-
-
+