Skip to content

Commit

Permalink
feat: motion react
Browse files Browse the repository at this point in the history
  • Loading branch information
wkylin committed Dec 26, 2024
1 parent 08bb1be commit 8c3d5b2
Show file tree
Hide file tree
Showing 13 changed files with 366 additions and 84 deletions.
35 changes: 35 additions & 0 deletions src/components/stateless/DynamicBackground/icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'

Check failure on line 1 in src/components/stateless/DynamicBackground/icons.js

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
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: <ReactIcon /> },
{ size: 'sm', type: 'outline', icon: null },
{ size: 'lg', type: 'outline', icon: <AngularIcon /> },
{ size: 'md', type: 'outline', icon: null },
{ size: 'md', type: 'fill', icon: <VueIcon /> },
{ size: 'sm', type: 'outline', icon: null },
{ size: 'md', type: 'outline', icon: null },
{ size: 'sm', type: 'outline', icon: null },
{ size: 'lg', type: 'outline', icon: <JSIcon /> },
{ size: 'md', type: 'fill', icon: null },
{ size: 'md', type: 'outline', icon: null },
{ size: 'md', type: 'outline', icon: null },
{ size: 'sm', type: 'fill', icon: <ReduxIcon /> },
{ size: 'sm', type: 'fill', icon: null },
{ size: 'md', type: 'outline', icon: null },
{ size: 'lg', type: 'fill', icon: <TSIcon /> },
{ size: 'sm', type: 'outline', icon: null },
{ size: 'sm', type: 'outline', icon: null },
{ size: 'md', type: 'fill', icon: <NPMIcon /> },
{ size: 'sm', type: 'outline', icon: null },
{ size: 'sm', type: 'outline', icon: null },
]

export default icons
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/components/stateless/DynamicBackground/icons/deno.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/components/stateless/DynamicBackground/icons/npm.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/components/stateless/DynamicBackground/icons/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/components/stateless/DynamicBackground/icons/redux.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/components/stateless/DynamicBackground/icons/vue.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions src/components/stateless/DynamicBackground/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React, { useEffect, useRef, useState } from 'react'

Check failure on line 1 in src/components/stateless/DynamicBackground/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
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 (
<div className={styles.hero} ref={heroRef}>
{hasMounted && (
<div className={styles.iconLayer}>
{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 (
<span
key={i}
ref={(refItem) =>
(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}
</span>
)
})}
</div>
)}
<div className={styles.heroText}>
<h1>
Grow your <br /> <span>Frontend Skills</span>
</h1>
<h3>
with <span>React</span>, <span>Angular</span>, <span>Vue</span>, <span>Typescript</span>, <span>Redux</span>,{' '}
<span>Node</span>, <span>GraphQL</span>,<span>CSS</span>, <span>HTML</span>, <span>Webpack</span>,{' '}
<span>Rollup</span>, <span>ESLint</span>, <span>Prettier</span>, <span>Stylelint</span>,<span>PostCSS</span>,{' '}
<span>Styled Components</span>, <span>Emotion</span>, <span>Material UI</span>, <span>Ant Design</span>,{' '}
<span>Bootstrap</span>, <span>Foundation</span>,<span>React Native</span>, <span>Flutter</span>,{' '}
<span>Python</span>, <span>Go</span>,<span>JavaScript</span>, and <span>NPM</span>
</h3>
</div>
</div>
)
}

export default DynamicBackground
108 changes: 108 additions & 0 deletions src/components/stateless/DynamicBackground/index.module.less
Original file line number Diff line number Diff line change
@@ -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;
}
}
33 changes: 19 additions & 14 deletions src/pages/home/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -382,18 +382,20 @@ const Home = () => {
<section style={{ margin: 20 }}>
<ResponsiveMasonry columnsCountBreakPoints={{ 350: 1, 750: 2, 900: 6 }}>
<Masonry gutter="10px">
<LazyLoadImage src="https://picsum.photos/id/1/300/100" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/2/300/200" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/3/300/150" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/4/300/150" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/5/300/200" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/6/300/100" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/7/300/150" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/8/300/200" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/1/300/100" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/2/300/200" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/3/300/150" alt="Strawberries" />
<LazyLoadImage src="https://picsum.photos/id/4/300/150" alt="Strawberries" />
<section style={{ height: 100, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 200, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 150, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 150, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 200, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 100, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 200, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 150, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 100, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 150, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 100, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 200, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 150, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
<section style={{ height: 150, width: 300, border: '1px solid #ccc', background: '#aaa' }}></section>
</Masonry>
</ResponsiveMasonry>
</section>
Expand Down Expand Up @@ -434,7 +436,7 @@ const Home = () => {
</div>
</Marquee>
</section>
<section style={{ margin: '20px 0' }}>
<section style={{ margin: 20 }}>
<ShiCode
preCode={`
const GroceryItem: React.FC<GroceryItemProps> = ({ item }) => {
Expand All @@ -449,6 +451,9 @@ const GroceryItem: React.FC<GroceryItemProps> = ({ item }) => {
`}
/>
</section>
<section style={{ margin: 20 }}>
<DynamicBackground />
</section>
</FixTabPanel>
)
}
Expand Down
Loading

0 comments on commit 8c3d5b2

Please sign in to comment.