Skip to content

Commit

Permalink
feat: text loader
Browse files Browse the repository at this point in the history
  • Loading branch information
wkylin committed Jan 10, 2025
1 parent 0f1cb75 commit 624ad0f
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .cspell/custom-dictionary-workspace.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ CWGNC
depcheck
devchat
doloribus
elementor
elit
ENOENT
EPSG
Expand All @@ -47,6 +48,7 @@ jsencrypt
kend
lamina
languagedetector
Laravel
markercluster
Menlo
mfsu
Expand Down
9 changes: 9 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"version": "0.2",
"ignorePaths": [],
"dictionaryDefinitions": [],
"dictionaries": [],
"words": [],
"ignoreWords": [],
"import": []
}
62 changes: 62 additions & 0 deletions src/components/stateless/FloatAny/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useRef } from 'react'

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

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
import { motion, useAnimationFrame, useMotionValue } from 'motion/react'
import clsx from 'clsx'

const FloatAny = ({
children,
speed = 0.5,
amplitude = [10, 30, 30],
rotationRange = [15, 15, 7.5],
timeOffset = 0,
className,
}) => {
const x = useMotionValue(0)
const y = useMotionValue(0)
const z = useMotionValue(0)
const rotateX = useMotionValue(0)
const rotateY = useMotionValue(0)
const rotateZ = useMotionValue(0)

// Use refs for animation values to avoid recreating the animation frame callback
const time = useRef(0)

useAnimationFrame(() => {
time.current += speed * 0.02

// Smooth floating motion on all axes
const newX = Math.sin(time.current * 0.7 + timeOffset) * amplitude[0]
const newY = Math.sin(time.current * 0.6 + timeOffset) * amplitude[1]
const newZ = Math.sin(time.current * 0.5 + timeOffset) * amplitude[2]

// 3D rotations with different frequencies for more organic movement
const newRotateX = Math.sin(time.current * 0.5 + timeOffset) * rotationRange[0]
const newRotateY = Math.sin(time.current * 0.4 + timeOffset) * rotationRange[1]
const newRotateZ = Math.sin(time.current * 0.3 + timeOffset) * rotationRange[2]

x.set(newX)
y.set(newY)
z.set(newZ)
rotateX.set(newRotateX)
rotateY.set(newRotateY)
rotateZ.set(newRotateZ)
})

return (
<motion.div

Check notice on line 45 in src/components/stateless/FloatAny/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unresolved JSX component

Unresolved component motion.div
style={{
x,
y,
z,
rotateX,
rotateY,
rotateZ,
transformStyle: 'preserve-3d',
}}
className={clsx('will-change-transform', className)}
>
{children}
</motion.div>
)
}

export default FloatAny
77 changes: 77 additions & 0 deletions src/components/stateless/TextLoader/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useState, useEffect } from 'react'

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

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package

import { motion, AnimatePresence } from 'motion/react'

export function LoadingText({ text, dots }) {
return (
<div className="relative">
<AnimatePresence mode="wait">
<motion.div

Check notice on line 9 in src/components/stateless/TextLoader/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unresolved JSX component

Unresolved component motion.div
key={text}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
className="w-full text-lg font-medium"
>
{text}
{dots}
</motion.div>
</AnimatePresence>
</div>
)
}

const TextLoader = ({ messages, interval = 2000, dotCount = 3, direction = 'vertical' }) => {
const [currentIndex, setCurrentIndex] = useState(0)
const [dots, setDots] = useState('')

useEffect(() => {
const messageInterval = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % messages.length)
}, interval)

const dotInterval = setInterval(() => {
setDots((prev) => (prev.length >= dotCount ? '' : `${prev}.`))
}, 500)

return () => {
clearInterval(messageInterval)
clearInterval(dotInterval)
}
}, [messages.length, interval, dotCount])

if (direction === 'horizontal') {
return (
<div className="flex items-center justify-start w-full gap-3 px-3 py-2 border rounded-sm">
<motion.div

Check notice on line 47 in src/components/stateless/TextLoader/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unresolved JSX component

Unresolved component motion.div
className="size-5 md:size-6 border-[3px] text-primary-foreground border-t-transparent rounded-full"
animate={{ rotate: 360 }}
transition={{
duration: 1,
repeat: Number.POSITIVE_INFINITY,
ease: 'linear',
}}
/>
<LoadingText text={messages[currentIndex]} dots={dots} />
</div>
)
}

return (
<div className="flex flex-col items-center justify-center gap-4 py-1">
<motion.div

Check notice on line 63 in src/components/stateless/TextLoader/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unresolved JSX component

Unresolved component motion.div
className="size-10 md:size-12 border-[3px] text-primary-foreground border-t-transparent rounded-full"
animate={{ rotate: 360 }}
transition={{
duration: 1,
repeat: Number.POSITIVE_INFINITY,
ease: 'linear',
}}
/>
<LoadingText text={messages[currentIndex]} dots={dots} />
</div>
)
}

export default TextLoader
38 changes: 37 additions & 1 deletion src/pages/home/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,17 @@ import ShinyText from '@stateless/ShinyText'
import BlurText from '@stateless/BlurText'
import Meteors from '@stateless/Meteors'
import CompareAll from '@stateless/CompareAll'
import TextLoader from '@stateless/TextLoader'
import FloatAny from '@stateless/FloatAny'

import firstImage from '@assets/images/88-300x160.jpg'
import secondImage from '@assets/images/2-300x160.jpg'

import { oneApiChat, prettyObject, randomNum } from '@utils/aidFn'

Check warning on line 55 in src/pages/home/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused import

Unused import specifier randomNum
import { fireConfetti } from '@utils/confetti'
import styles from './index.module.less'

const boxCount = Array.apply(null, Array(10))
const boxList = Array.apply(null, Array(20))

const code = {

Check warning on line 61 in src/pages/home/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant code
fileName: './explanations.ts',
Expand Down Expand Up @@ -251,6 +254,39 @@ const Home = () => {
<section style={{ marginBottom: 15, fontSize: 20 }}>
<BlurText text="Isn't this so cool?!" delay={50} />
</section>
<section style={{ marginBottom: 15, width: 360, fontSize: 20 }}>
<TextLoader
messages={[
'Preparing your experience',
'Loading awesome content',
'Almost there',
'Just a moment',
'Getting things ready',
]}
/>
</section>
<section
style={{
marginBottom: 15,
position: 'relative',
width: 360,
height: 160,
overflow: 'hidden',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
}}
>
{boxList.map((box, i) => (
<FloatAny
key={i}
timeOffset={i * 0.8}
amplitude={[15 + Math.random() * 20, 25 + Math.random() * 30, 20 + Math.random() * 25]}
rotationRange={[10 + Math.random() * 10, 10 + Math.random() * 10, 5 + Math.random() * 5]}
speed={0.3 + Math.random() * 0.4}
>
<section style={{ width: 20, height: 20, borderRadius: 20, background: '#aaa' }} />
</FloatAny>
))}
</section>

<section
style={{
Expand Down

0 comments on commit 624ad0f

Please sign in to comment.