Skip to content

Commit

Permalink
feat: compare
Browse files Browse the repository at this point in the history
  • Loading branch information
wkylin committed Jan 6, 2025
1 parent e14c29f commit ba96152
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 3 deletions.
Binary file added src/assets/images/2-300x160.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/8-300x160.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/88-300x160.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
205 changes: 205 additions & 0 deletions src/components/stateless/CompareAll/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
'use client'

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

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
import React, { useState, useEffect, useRef, useCallback } from 'react'
import { AnimatePresence, motion } from 'motion/react'
import { GitMerge } from 'lucide-react'
import clsx from 'clsx'

const Compare = ({
firstImage = '',
secondImage = '',
className,
firstImageClassName,
secondImageClassname,
initialSliderPercentage = 50,
slideMode = 'hover',
showHandlebar = true,
autoplay = false,
autoplayDuration = 5000,
}) => {
const [sliderXPercent, setSliderXPercent] = useState(initialSliderPercentage)
const [isDragging, setIsDragging] = useState(false)

const sliderRef = useRef(null)

const [isMouseOver, setIsMouseOver] = useState(false)

Check warning on line 24 in src/components/stateless/CompareAll/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant isMouseOver

const autoplayRef = useRef(null)

const startAutoplay = useCallback(() => {
if (!autoplay) return

const startTime = Date.now()
const animate = () => {
const elapsedTime = Date.now() - startTime
const progress = (elapsedTime % (autoplayDuration * 2)) / autoplayDuration
const percentage = progress <= 1 ? progress * 100 : (2 - progress) * 100

setSliderXPercent(percentage)
autoplayRef.current = setTimeout(animate, 16) // ~60fps
}

animate()
}, [autoplay, autoplayDuration])

const stopAutoplay = useCallback(() => {
if (autoplayRef.current) {
clearTimeout(autoplayRef.current)
autoplayRef.current = null
}
}, [])

useEffect(() => {
startAutoplay()
return () => stopAutoplay()
}, [startAutoplay, stopAutoplay])

function mouseEnterHandler() {
setIsMouseOver(true)
stopAutoplay()
}

function mouseLeaveHandler() {
setIsMouseOver(false)
if (slideMode === 'hover') {
setSliderXPercent(initialSliderPercentage)
}
if (slideMode === 'drag') {
setIsDragging(false)
}
startAutoplay()
}

const handleStart = useCallback(
(clientX) => {

Check warning on line 73 in src/components/stateless/CompareAll/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused parameter clientX
if (slideMode === 'drag') {
setIsDragging(true)
}
},
[slideMode]
)

const handleEnd = useCallback(() => {
if (slideMode === 'drag') {
setIsDragging(false)
}
}, [slideMode])

const handleMove = useCallback(
(clientX) => {
if (!sliderRef.current) return
if (slideMode === 'hover' || (slideMode === 'drag' && isDragging)) {
const rect = sliderRef.current.getBoundingClientRect()
const x = clientX - rect.left
const percent = (x / rect.width) * 100
requestAnimationFrame(() => {
setSliderXPercent(Math.max(0, Math.min(100, percent)))
})
}
},
[slideMode, isDragging]
)

const handleMouseDown = useCallback((e) => handleStart(e.clientX), [handleStart])
const handleMouseUp = useCallback(() => handleEnd(), [handleEnd])
const handleMouseMove = useCallback((e) => handleMove(e.clientX), [handleMove])

const handleTouchStart = useCallback(
(e) => {
if (!autoplay) {
handleStart(e.touches[0].clientX)
}
},
[handleStart, autoplay]
)

const handleTouchEnd = useCallback(() => {
if (!autoplay) {
handleEnd()
}
}, [handleEnd, autoplay])

const handleTouchMove = useCallback(
(e) => {
if (!autoplay) {
handleMove(e.touches[0].clientX)
}
},
[handleMove, autoplay]
)

return (
<div
ref={sliderRef}
className={clsx('w-[300px] h-[160px] overflow-hidden', className)}
style={{
position: 'relative',
cursor: slideMode === 'drag' ? 'grab' : 'col-resize',
}}
onMouseMove={handleMouseMove}
onMouseLeave={mouseLeaveHandler}
onMouseEnter={mouseEnterHandler}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
onTouchMove={handleTouchMove}
>
<AnimatePresence initial={false}>
<motion.div

Check notice on line 148 in src/components/stateless/CompareAll/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unresolved JSX component

Unresolved component motion.div
className="h-full w-px absolute top-0 m-auto z-30 bg-gradient-to-b from-transparent from-[5%] to-[95%] via-indigo-500 to-transparent"
style={{
left: `${sliderXPercent}%`,
top: '0',
zIndex: 40,
}}
transition={{ duration: 0 }}
>
<div className="w-36 h-full [mask-image:radial-gradient(100px_at_left,white,transparent)] absolute top-1/2 -translate-y-1/2 left-0 bg-gradient-to-r from-indigo-400 via-transparent to-transparent z-20 opacity-50" />
<div className="w-10 h-1/2 [mask-image:radial-gradient(50px_at_left,white,transparent)] absolute top-1/2 -translate-y-1/2 left-0 bg-gradient-to-r from-cyan-400 via-transparent to-transparent z-10 opacity-100" />
<div className="w-10 h-3/4 top-1/2 -translate-y-1/2 absolute -right-10 [mask-image:radial-gradient(100px_at_left,white,transparent)]"></div>
{showHandlebar && (
<div className="h-5 w-5 rounded-md top-1/2 -translate-y-1/2 bg-white z-30 -right-2.5 absolute flex items-center justify-center shadow-[0px_-1px_0px_0px_#FFFFFF40]">
<GitMerge className="w-4 h-4 text-black" />
</div>
)}
</motion.div>
</AnimatePresence>
<div className="relative z-20 w-full h-full overflow-hidden pointer-events-none">
<AnimatePresence initial={false}>
{firstImage ? (
<motion.div

Check notice on line 170 in src/components/stateless/CompareAll/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unresolved JSX component

Unresolved component motion.div
className={clsx(
'absolute inset-0 z-20 flex-shrink-0 w-full h-full select-none overflow-hidden',
firstImageClassName
)}
style={{
clipPath: `inset(0 ${100 - sliderXPercent}% 0 0)`,
}}
transition={{ duration: 0 }}
>
<img
alt="first image"
src={firstImage}
className={clsx('absolute inset-0 z-20 flex-shrink-0 w-full h-full select-none', firstImageClassName)}
draggable={false}
/>
</motion.div>
) : null}
</AnimatePresence>
</div>

<AnimatePresence initial={false}>
{secondImage ? (
<motion.img

Check notice on line 193 in src/components/stateless/CompareAll/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unresolved JSX component

Unresolved component motion.img
className={clsx('absolute top-0 left-0 z-[19] w-full h-full select-none', secondImageClassname)}
alt="second image"
src={secondImage}
draggable={false}
/>
) : null}
</AnimatePresence>
</div>
)
}

export default Compare
1 change: 0 additions & 1 deletion src/components/stateless/ShinyText/index.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
background: linear-gradient(120deg, rgb(0 0 0 / 0%) 40%, rgb(0 0 0 / 80%) 50%, #000 60%);
background-size: 200% 100%;
background-clip: text;
background-clip: text;
display: inline-block;
animation: shine 5s linear infinite;
}
Expand Down
17 changes: 15 additions & 2 deletions src/pages/home/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ import { ReactSignature } from '@stateless/ReactSignature'
// import AdvancedCodeBlock from '@stateless/AdvancedCodeBlock'
import ShinyText from '@stateless/ShinyText'
import BlurText from '@stateless/BlurText'

import Meteors from '@stateless/Meteors'
import CompareAll from '@stateless/CompareAll'

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 49 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))

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

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant boxCount
Expand Down Expand Up @@ -239,6 +242,16 @@ const Home = () => {
>
<Meteors number={40} />
</section>
<section style={{ margin: '20px 0' }}>
<CompareAll
firstImage={firstImage}
secondImage={secondImage}
firstImageClassName="object-cover object-left-top"
secondImageClassName="object-cover object-left-top"
className="h-[160px]"
slideMode="drag"
/>
</section>
<section className={styles.line}></section>
<section>
<AvatarCard avatar="https://picsum.photos/seed/picsum/300/160" text="Hi, I'm a developer." />
Expand Down

0 comments on commit ba96152

Please sign in to comment.