-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'unit/27-click-and-drag'
- Loading branch information
Showing
3 changed files
with
231 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { useRef, useState } from 'react'; | ||
import { LayoutCol1 } from '@/Layouts'; | ||
import { Title } from '@/Components'; | ||
import styles from './styles.module.scss'; | ||
|
||
const items = Array.from({ length: 25 }, (el, i) => ({ | ||
name: (i + 1).toString().padStart(2, 0), | ||
id: i, | ||
})); | ||
|
||
const getXPosition = (event, areaDOM) => { | ||
if (!areaDOM) return 0; | ||
|
||
const { pageX } = event; | ||
const pageSpace = areaDOM.getBoundingClientRect().left; | ||
return pageX - pageSpace; | ||
}; | ||
|
||
const MOVE_RATIO = 5; // 實際移動:滑鼠移動 | ||
|
||
const ClickAndDragPage = () => { | ||
const scrollAreaRef = useRef(); | ||
const [isDown, setIsDown] = useState(false); | ||
const [scrollInfo, setScrollInfo] = useState({ startX: 0, scrollLeft: 0 }); | ||
|
||
const handleMouseDown = e => { | ||
setIsDown(true); | ||
|
||
const scrollArea = scrollAreaRef.current; | ||
const startX = getXPosition(e, scrollArea); | ||
const { scrollLeft } = scrollArea; // scroll bar &左邊距離 | ||
setScrollInfo({ startX, scrollLeft }); | ||
}; | ||
const handleMouseLeave = () => { | ||
setIsDown(false); | ||
}; | ||
const handleMouseUp = () => { | ||
setIsDown(false); | ||
}; | ||
const handleMouseMove = e => { | ||
if (!isDown) return; | ||
e.preventDefault(); | ||
const scrollArea = scrollAreaRef.current; | ||
|
||
const maxScrollLeft = scrollArea.scrollWidth - scrollArea.clientWidth; | ||
const newX = getXPosition(e, scrollArea); | ||
const moveDistance = (scrollInfo.startX - newX) * MOVE_RATIO; | ||
const newScrollLeft = scrollInfo.scrollLeft + moveDistance; | ||
|
||
// (scrollWidth:捲動範圍總寬 / clientWidth:可視範圍寬 / scrollLeft:超出可視範圍的寬) | ||
scrollArea.scrollLeft = newScrollLeft > maxScrollLeft ? maxScrollLeft : newScrollLeft; | ||
}; | ||
|
||
return ( | ||
<LayoutCol1 baseClassName={styles.page}> | ||
<Title | ||
title={ | ||
<> | ||
<span>Click</span> <span>and</span> <span>Drag</span> | ||
</> | ||
} | ||
size='m' | ||
titleClassName={styles.title} | ||
/> | ||
<div className={styles.container}> | ||
<div | ||
ref={scrollAreaRef} | ||
role='button' | ||
tabIndex={0} | ||
className={`${styles.scroll_items} ${isDown ? styles.active : ''}`} | ||
onMouseDown={handleMouseDown} | ||
onMouseLeave={handleMouseLeave} | ||
onMouseUp={handleMouseUp} | ||
onMouseMove={handleMouseMove} | ||
> | ||
{items.map(({ id, name }) => ( | ||
<div className={styles.item} key={id}> | ||
<p>{name}</p> | ||
</div> | ||
))} | ||
</div> | ||
<div className={styles.shadow} /> | ||
</div> | ||
</LayoutCol1> | ||
); | ||
}; | ||
|
||
export default ClickAndDragPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
$color_1: #ffbab2; | ||
$color_2: #f4d97d; | ||
$color_3: #a6d9e6; | ||
|
||
.page { | ||
background: #fff; | ||
background: radial-gradient( | ||
closest-side, | ||
rgba($color: $color_1, $alpha: 45%) 20%, | ||
#fff0 20% | ||
) | ||
10px -10px, | ||
radial-gradient(closest-side, rgba($color: $color_2, $alpha: 45%) 20%, #fff0 20%) 0 | ||
10px, | ||
radial-gradient(closest-side, rgba($color: $color_3, $alpha: 45%) 20%, #fff0 20%) -10px -10px; | ||
background-size: 40px 40px; | ||
} | ||
|
||
.title { | ||
width: fit-content; | ||
padding: 1rem 2rem 1.2rem; | ||
|
||
background: linear-gradient(#fff, #fff) padding-box, | ||
linear-gradient(to right, $color_1, $color_2, $color_3) border-box; | ||
border: 8px solid transparent; | ||
border-radius: 50px; | ||
|
||
& span:nth-child(1) { | ||
color: $color_1; | ||
} | ||
|
||
& span:nth-child(2) { | ||
color: $color_2; | ||
} | ||
|
||
& span:nth-child(3) { | ||
color: $color_3; | ||
} | ||
} | ||
|
||
.container { | ||
position: relative; | ||
z-index: 99; | ||
|
||
overflow: hidden; | ||
display: flex; | ||
|
||
width: 100%; | ||
height: 100%; | ||
|
||
background: #c2c2c2; | ||
border-radius: 0.5rem; | ||
} | ||
|
||
.shadow { | ||
pointer-events: none; | ||
|
||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
|
||
width: 100%; | ||
height: 100%; | ||
|
||
box-shadow: inset 0 0 1rem 4px rgba($color: #000, $alpha: 40%); | ||
} | ||
|
||
.scroll_items { | ||
cursor: grab; | ||
user-select: none; | ||
|
||
overflow-x: overlay; | ||
display: flex; | ||
align-items: center; | ||
|
||
width: 100%; | ||
height: 100%; | ||
padding: 1rem 5rem 1rem 0; | ||
|
||
perspective: 300px; | ||
perspective-origin: center 60%; | ||
|
||
&::-webkit-scrollbar { | ||
height: 16px; | ||
appearance: none; | ||
} | ||
|
||
&::-webkit-scrollbar-thumb { | ||
background-color: #737373; | ||
background-clip: content-box; | ||
border: 4px solid transparent; | ||
border-radius: 100px; | ||
} | ||
|
||
&::-webkit-scrollbar-track { | ||
background: #fff0; | ||
} | ||
} | ||
|
||
.active { | ||
cursor: grabbing; | ||
background-color: #ececec; | ||
transition: background-color 0.2s ease-out; | ||
} | ||
|
||
.item { | ||
display: flex; | ||
flex-shrink: 0; | ||
align-items: center; | ||
justify-content: center; | ||
|
||
width: 200px; | ||
height: calc(100% - 100px); | ||
|
||
box-shadow: inset 0 0 0 4px #fff; | ||
|
||
& > p { | ||
font-size: 2rem; | ||
color: #fff; | ||
} | ||
|
||
&:nth-child(even) { | ||
transform: scaleX(1.07) rotateY(20deg); | ||
} | ||
|
||
&:nth-child(odd) { | ||
transform: scaleX(1.07) rotateY(-20deg); | ||
} | ||
|
||
&:nth-child(3n + 1) { | ||
background: $color_1; | ||
} | ||
|
||
&:nth-child(3n + 2) { | ||
background: $color_2; | ||
} | ||
|
||
&:nth-child(3n + 3) { | ||
background: $color_3; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters