From 88a4ee139ad2f755f8e0f3c2cae6904fbeae9ab5 Mon Sep 17 00:00:00 2001 From: yichennn Date: Tue, 23 May 2023 23:27:53 +0800 Subject: [PATCH 1/3] feat: base page --- src/Pages/27ClickAndDrag/index.jsx | 28 ++++++ src/Pages/27ClickAndDrag/styles.module.scss | 99 +++++++++++++++++++++ src/routesConfig.jsx | 3 +- 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/Pages/27ClickAndDrag/index.jsx create mode 100644 src/Pages/27ClickAndDrag/styles.module.scss diff --git a/src/Pages/27ClickAndDrag/index.jsx b/src/Pages/27ClickAndDrag/index.jsx new file mode 100644 index 0000000..a3c4630 --- /dev/null +++ b/src/Pages/27ClickAndDrag/index.jsx @@ -0,0 +1,28 @@ +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 ClickAndDragPage = () => { + return ( + + + <div className={styles.container}> + <div className={styles.scroll_items}> + {items.map(({ id, name }) => ( + <div className={styles.item} key={id}> + <p>{name}</p> + </div> + ))} + </div> + <div className={styles.shadow} /> + </div> + </LayoutCol1> + ); +}; + +export default ClickAndDragPage; diff --git a/src/Pages/27ClickAndDrag/styles.module.scss b/src/Pages/27ClickAndDrag/styles.module.scss new file mode 100644 index 0000000..50624c1 --- /dev/null +++ b/src/Pages/27ClickAndDrag/styles.module.scss @@ -0,0 +1,99 @@ +.page { + background: #fff; +} + +.container { + position: relative; + + 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 { + user-select: none; + + position: relative; + + overflow-x: overlay; + display: flex; + align-items: center; + + width: 100%; + height: 100%; + padding-left: 6rem; + + 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; + } +} + +.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: rgb(254 171 144); + } + + &:nth-child(3n + 2) { + background: rgb(175 232 232); + } + + &:nth-child(3n + 3) { + background: rgb(237 211 108); + } +} diff --git a/src/routesConfig.jsx b/src/routesConfig.jsx index b7b0b95..e1f6bc7 100644 --- a/src/routesConfig.jsx +++ b/src/routesConfig.jsx @@ -27,7 +27,7 @@ const WordToSpeechPage = lazy(() => import('./Pages/23WordToSpeech')); const StickyNavPage = lazy(() => import('./Pages/24StickyNav')); const EventPage = lazy(() => import('./Pages/25Event')); const NavDropdownPage = lazy(() => import('./Pages/26NavDropdown')); - +const ClickAndDragPage = lazy(() => import('./Pages/27ClickAndDrag')); export const routes = [ { id: 0, path: '/', element: <HomePage /> }, { id: 1, path: '01', element: <DrumPage /> }, @@ -56,4 +56,5 @@ export const routes = [ { id: 24, path: '24', element: <StickyNavPage /> }, { id: 25, path: '25', element: <EventPage /> }, { id: 26, path: '26', element: <NavDropdownPage /> }, + { id: 27, path: '27', element: <ClickAndDragPage /> }, ]; From a22029d8f0ff2a66a03820233c9ad845448414ab Mon Sep 17 00:00:00 2001 From: yichennn <yichennn1010@gmail.com> Date: Wed, 24 May 2023 22:41:08 +0800 Subject: [PATCH 2/3] feat: click and drag --- src/Pages/27ClickAndDrag/index.jsx | 54 ++++++++++++++++++++- src/Pages/27ClickAndDrag/styles.module.scss | 15 ++++-- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/Pages/27ClickAndDrag/index.jsx b/src/Pages/27ClickAndDrag/index.jsx index a3c4630..3546025 100644 --- a/src/Pages/27ClickAndDrag/index.jsx +++ b/src/Pages/27ClickAndDrag/index.jsx @@ -1,3 +1,4 @@ +import { useRef, useState } from 'react'; import { LayoutCol1 } from '@/Layouts'; import { Title } from '@/Components'; import styles from './styles.module.scss'; @@ -7,12 +8,63 @@ const items = Array.from({ length: 25 }, (el, i) => ({ 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='27 page' /> <div className={styles.container}> - <div className={styles.scroll_items}> + <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> diff --git a/src/Pages/27ClickAndDrag/styles.module.scss b/src/Pages/27ClickAndDrag/styles.module.scss index 50624c1..d0da307 100644 --- a/src/Pages/27ClickAndDrag/styles.module.scss +++ b/src/Pages/27ClickAndDrag/styles.module.scss @@ -4,6 +4,7 @@ .container { position: relative; + z-index: 99; overflow: hidden; display: flex; @@ -29,17 +30,16 @@ } .scroll_items { + cursor: grab; user-select: none; - position: relative; - overflow-x: overlay; display: flex; align-items: center; width: 100%; height: 100%; - padding-left: 6rem; + padding: 1rem 5rem 1rem 0; perspective: 300px; perspective-origin: center 60%; @@ -61,6 +61,11 @@ } } +.active { + cursor: grabbing; + background-color: #ddd; +} + .item { display: flex; flex-shrink: 0; @@ -78,11 +83,11 @@ } &:nth-child(even) { - transform: scaleX(1.07) rotateY(-20deg); + transform: scaleX(1.07) rotateY(20deg); } &:nth-child(odd) { - transform: scaleX(1.07) rotateY(20deg); + transform: scaleX(1.07) rotateY(-20deg); } &:nth-child(3n + 1) { From 03668193182752c389d1217319d2845932fba669 Mon Sep 17 00:00:00 2001 From: yichennn <yichennn1010@gmail.com> Date: Wed, 24 May 2023 23:52:31 +0800 Subject: [PATCH 3/3] feat: page style --- src/Pages/27ClickAndDrag/index.jsx | 10 ++++- src/Pages/27ClickAndDrag/styles.module.scss | 45 +++++++++++++++++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/Pages/27ClickAndDrag/index.jsx b/src/Pages/27ClickAndDrag/index.jsx index 3546025..7b25ecb 100644 --- a/src/Pages/27ClickAndDrag/index.jsx +++ b/src/Pages/27ClickAndDrag/index.jsx @@ -53,7 +53,15 @@ const ClickAndDragPage = () => { return ( <LayoutCol1 baseClassName={styles.page}> - <Title title='27 page' /> + <Title + title={ + <> + <span>Click</span> <span>and</span> <span>Drag</span> + </> + } + size='m' + titleClassName={styles.title} + /> <div className={styles.container}> <div ref={scrollAreaRef} diff --git a/src/Pages/27ClickAndDrag/styles.module.scss b/src/Pages/27ClickAndDrag/styles.module.scss index d0da307..63fe5c0 100644 --- a/src/Pages/27ClickAndDrag/styles.module.scss +++ b/src/Pages/27ClickAndDrag/styles.module.scss @@ -1,5 +1,41 @@ +$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 { @@ -63,7 +99,8 @@ .active { cursor: grabbing; - background-color: #ddd; + background-color: #ececec; + transition: background-color 0.2s ease-out; } .item { @@ -91,14 +128,14 @@ } &:nth-child(3n + 1) { - background: rgb(254 171 144); + background: $color_1; } &:nth-child(3n + 2) { - background: rgb(175 232 232); + background: $color_2; } &:nth-child(3n + 3) { - background: rgb(237 211 108); + background: $color_3; } }