[React] ๋ฆฌ์กํธ ๋ง์ฐ์ค ๋๋๊ทธ ๊ฐ๋ฅํ ์์ ๋ง๋ค๊ธฐ / ๊ธฐํ ํ๋กํผํฐ
์์ ์์์ ์ฝํ ์ธ ๊ฐ ๋ถ๋ชจ ์์๋ณด๋ค ํฌ๋ค๋ฉด, ๋ถ๋ชจ ์์์ ์คํฌ๋กค์ด ์๊ธฐ๊ณ , ๋ง์ฐ์ค ํ ๋ก ์คํฌ๋กคํ ์ ์๋ค. ๋ง์ฐ์ค ํ ์ธ์๋ click / move ์ด๋ฒคํธ๋ฅผ ์ด์ฉํด ๋ง์ฐ์ค ๋๋๊ทธ๋ก ์คํฌ๋กคํ๋๋ก ๋ง๋ค ์ ์๋ค.
TL;DR
โถ ๋ง์ฐ์ค๋ฅผ ํด๋ฆญํ์ ๋
- `clientX`, `clientY` (๋ทฐํฌํธ ๊ธฐ์ค)์ขํ์, ์์์ ์คํฌ๋กค ์์น `scrollLeft`, `scrollTop` ์ ์ฅ
- ํด๋ฆญ ์ํ `true`๋ก ๋ณ๊ฒฝ
- (CSS) `cursor: grabbing user-select: none`
โท ํด๋ฆญํ ์ํ์์ ๋ง์ฐ์ค๋ฅผ ์ด๋(๋๋๊ทธ)ํ์ ๋
- โ ์ด๋์ ๋ฉ์ถ ์ง์ (clientX/Y)๊ณผ โก๋ง์ฐ์ค๋ฅผ ํด๋ฆญํ ์ง์ **(clientX/Y)**์ ๋บ ๊ฐ ๊ณ์ฐ — ์คํฌ๋กคํ ๋ฒ์
- (์์์ ํ์ฌ ์คํฌ๋กค ์์น)์ (์คํฌ๋กคํ ๊ฐ)์ ๋บ ์์น๋ก ์คํฌ๋กค ์ด๋
โธ ๋ง์ฐ์ค ํด๋ฆญ์ ํด์ ํ๊ฑฐ๋ ์ด๋ฒคํธ ์์ญ์ ๋ฒ์ด๋ฌ์ ๋
- ํด๋ฆญ ์ํ `false`๋ก ๋ณ๊ฒฝ
- (CSS) `cursor: grab`
โน ์์๋ฅผ ๊ธฐ์ค์ผ๋ก ํด๋ฆญํ ๊ณณ์ ๋ง์ปค(์ด๋ฏธ์ง) ๋ฑ์ ์ถ๊ฐํ๊ณ ์ถ์ ๋
- ๋ค์ดํฐ๋ธ ์ด๋ฒคํธ์ ์ ๊ทผํด์ `offsetX`, `offsetY` ์ขํ๊ฐ ์ ์ฅ ํ(์คํฌ๋กค ์์ญ๊น์ง ํฌํจํ ๊ฐ)
- ์์ ์ด๋ฏธ์ง ํ๊ทธ์ `left`, `top` ํฌ์ง์ ๊ฐ์ผ๋ก `offsetX`, `offsetY` ํ ๋น
๊ธฐํ ํ๋กํผํฐ
๋ฉ์๋๋ณ ์ธก์ ๊ธฐ์ค โญ๏ธ
- offset : ์ด๋ฒคํธ๊ฐ ๊ฑธ๋ ค ์๋ DOM ๊ฐ์ฒด ๊ธฐ์ค
- screen : ๋ชจ๋ํฐ ๊ธฐ์ค
- client : ๋ธ๋ผ์ฐ์ ๊ธฐ์ค
- page : ๋ฌธ์ ๊ธฐ์ค
๊ตฌํ์ ์์์ผํ ๊ธฐํ ํ๋กํผํฐ โญ๏ธ
๊ธฐํ ํ๋กํผํฐ์ ๊ฐ์ ์ซ์์ด๋ฉฐ ํฝ์ ๋จ์๋ก ์ธก์ . scrollLeft, scrollTop์ ์ ์ธํ ๋ชจ๋ ๊ธฐํ ํ๋กํผํฐ๋ ์ฝ๊ธฐ ์ ์ฉ
โถ `clientWidth`, `clientHeight` : ํด๋น ์์์ ๋ด๋ถ ๋๋น / ๋์ด
- ํฌํจ : padding
- ์ ์ธ : (์กด์ฌํ๋ฉด)์คํฌ๋กค๋ฐ ๋๋น or ๋์ด, border, margin
โท `offsetWidth`, `offsetHeight` : ํด๋น ์์์ ๋๋น / ๋์ด
- ํฌํจ : padding, ์คํฌ๋กค๋ฐ ๋๋น or ๋์ด, border
- ์ ์ธ : margin
- CSS์์ `box-sizing: border-box` ์ผ ๋ ์ง์ ํ ๋๋น / ๋์ด์ ๋์ผํจ
โธ `scrollWidth`, `scrollHeight` : ์คํฌ๋กค๋ฐ์ ์ํด ๊ฐ์ถฐ์ง ๋ถ๋ถ์ ํฌํจํ ์ฝํ ์ธ ์ ์ ์ฒด ๋๋น / ๋์ด
- ํฌํจ : padding, border
- ์ ์ธ : margin
โน `scrollLeft`, `scrollTop` (์์ ๊ฐ๋ฅ) : ์คํฌ๋กคํด์ ๊ฐ๋ ค์ง ์ฝํ ์ธ ์์ญ์ ๋๋น / ๋์ด
- scrollTop์ `0`ํน์ `1e9`์ผ๋ก ์ค์ ํด์ ์ต์๋จ / ์ตํ๋จ์ผ๋ก ์ฎ๊ธธ ์ ์์
- ์์๊ฐ ์๋, ๋ฌธ์์ ์คํฌ๋กค ์ํ๋ฅผ(๊ฐ๋ ค์ง ์ฝํ
์ธ ์์ญ ์ ๋ณด) ํ์ธํ ๋ (์ฐธ๊ณ )
- `window.pageXOffset`, `window.pageYOffset` (๋ชจ๋ ๋ธ๋ผ์ฐ์ ์์ ์ฌ์ฉ ๊ฐ๋ฅ)
- `document.documentElement.scrollTop`, `document.documentElement.scrollLeft`
โบ `mouseEvent.clientX`, `mouseEvent.clientY` : ๋ธ๋ผ์ฐ์ ํ๋ฉด ์ผ์ชฝ ์ต์๋จ์ ๊ธฐ์ค์ผ๋ก ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์ง์ ๊น์ง ์ผ๋ง๋ ๋จ์ด์ ธ ์๋์ง ๋ํ๋ด๋ ์ขํ. ๋ธ๋ผ์ฐ์ ํ๋ฉด์ด ๊ธฐ์ค์ด๋ฏ๋ก ์คํฌ๋กคํด๋ ๊ฐ์ด ๋ณํ์ง ์์.
โป `mouseEvent.PageX`, `mouseEvent.pageY` : ๋ฌธ์ ์ผ์ชฝ ์ต์๋จ์ ๊ธฐ์ค์ผ๋ก ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์ง์ ๊น์ง ์ผ๋ง๋ ๋จ์ด์ ธ ์๋์ง ๋ํ๋ด๋ ์ขํ. ๋ฌธ์๊ฐ ๊ธฐ์ค์ด๋ฏ๋ก ์คํฌ๋กคํ๋ฉด ๊ฐ๋ ๋ณํจ(์ ์ด๋ฏธ์ง์ ํ์ ์ํจ)
โผ ์์๋ฅผ ๋๊น์ง ์คํฌ๋กค ํ๋์ง ํ๋ณํ๊ธฐ
// ๊ฒฐ๊ณผ๊ฐ์ด true๋ผ๋ฉด ์์์ ๋๊น์ง ์คํฌ๋กคํ ์ํ
element.scrollHeight - element.scrollTop === element.clientHeight;
โฝ ๋ฌธ์์ ์ ์ฒด ๋์ด๊ฐ ๊ตฌํ๊ธฐ (์ฐธ๊ณ )
// ๋ฌธ์์ ์ ํํ ์ ์ฒด ๋์ด๋ฅผ ๊ตฌํ๊ธฐ ์ํ ์ฝ๋
const scrollHeight = Math.max(
document.body.scrollHeight,
document.documentElement.scrollHeight,
document.body.offsetHeight,
document.documentElement.offsetHeight,
document.body.clientHeight,
document.documentElement.clientHeight,
);
์ ์ด๋ฐ ๋ฐฉ์์ผ๋ก ๋ฌธ์ ์ ์ฒด ๋์ด๋ฅผ ๊ตฌํด์ผ ํ๋ ๊ฑธ๊น์? ์ด์ ๋ ์์๋ณด์ง ์๋ ๊ฒ ๋ซ์ต๋๋ค. ์ด๋ฐ ์ด์ํ ๊ณ์ฐ๋ฒ์ ์์ฃผ ์ค๋ ์ ๋ถํฐ ์์๊ณ ๊ทธ๋ค์ง ๋ ผ๋ฆฌ์ ์ด์ง ์์ ์ด์ ๋ก ๋ง๋ค์ด์ก๊ธฐ ๋๋ฌธ์ ๋๋ค.
— JavaScript Info
๋ทฐํฌํธ ๊ธฐ์ค ์์ ์์น (์ฐธ๊ณ ์ฉ)
`getBoundingClientRect()` ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ธ๋ผ์ฐ์ ํ๋ฉด์ ๊ธฐ์ค์ผ๋กํ ์์์ ์์น ์ขํ๋ฅผ ์กฐํํ ์ ์๋ค
const clientRect = mapRef.current.getBoundingClientRect(); // React
const clientRect = elem.getBoundingClientRect(); // Vanilla JS
console.log(clientRect.x);
// ...
- `clientRect.x`, `clientRect.left` : ํ๋ฉด ์ข์ธก๋ถํฐ ํด๋น ์๋ฆฌ๋จผํธ์ ์ผ์ชฝ ๋ณ๊น์ง์ ๊ฑฐ๋ฆฌ
- `clientRect.y`, `clientRect.top` : ํ๋ฉด ์๋จ๋ถํฐ ํด๋น ์๋ฆฌ๋จผํธ์ ์์ชฝ ๋ณ๊น์ง์ ๊ฑฐ๋ฆฌ
- `clientRect.right` : ํ๋ฉด ์ข์ธก์์ ํด๋น ์๋ฆฌ๋จผํธ์ ์ค๋ฅธ์ชฝ ๋ณ๊น์ง์ ๊ฑฐ๋ฆฌ
- `clientRect.bottom` : ํ๋ฉด ์๋จ์์ ํด๋น ์๋ฆฌ๋จผํธ์ ์๋์ชฝ ๋ณ๊น์ง์ ๊ฑฐ๋ฆฌ
- `clientRect.width` : ํด๋น ์๋ฆฌ๋จผํธ์ ๋๋น (์ฝํ ์ธ + padding + border)
- `clientRect.height` : ํด๋น ์๋ฆฌ๋จผํธ์ ๋์ด (์ฝํ ์ธ + padding + border)
์๋ฆฌ๋จผํธ
์ด๋ฏธ์ง ์ฌ์ด์ฆ๊ฐ ๋ถ๋ชจ ์์๋ณด๋ค ํฌ๋ค๊ณ ๊ฐ์ ํ๋ค(๊ทธ๋์ผ ์คํฌ๋กคํ ์ ์์ผ๋ฏ๋ก).
`div`(S.Map)์ `scrollLeft`, `scrollTop` (๊ธฐํ ํ๋กํผํฐ)์์ฑ์ ์ ๊ทผํด์ผํ๋ฏ๋ก `useRef()`๋ก ์์ฑํ ๊ฐ์ฒด๋ฅผ `div` ํ๊ทธ์ `ref` ์์ฑ์ ํ ๋นํ๋ค. ๊ทธ๋ผ Ref ๊ฐ์ฒด์ `.current` ๊ฐ์ `div` ํ๊ทธ๋ฅผ ๊ฐ๋ฆฌํค๊ฒ ๋๋ค. ๊ทธ ํ ์๋์ฒ๋ผ ์ฌ์ฉํ ์ ์๋ค.
// scrollLeft๋ ์๋ค. ์๋๋ scrollTop๋ง ์์๋ก ๋ฌ
mapRef.current.scrollTop; // ์ธ๋ก ์คํฌ๋กค ์์น ์กฐํ
mapRef.current.scrollTop = 100; // ์ธ๋ก ์คํฌ๋กค ์์น๋ฅผ 100์ผ๋ก ์ด๋(์คํฌ๋กค์ ์ํด ๊ฐ๋ ค์ง ์ฝํ
์ธ ๋์ด๊ฐ 100px์ด ๋๊ฒ๋ ์์ )
mapRef.current.scrollBy(x, y); // ํ์ฌ ์คํฌ๋กค ์์น์์ x(๊ฐ๋ก), y(์ธ๋ก)๋งํผ ์ด๋
mapRef.current.scrollTo(pageX, pageY); // ์คํฌ๋กค์ x(๊ฐ๋ก), y(์ธ๋ก) ์์น๋ก ์ด๋
`img` ํ๊ทธ๋ ๋๋๊ทธํ ํ์๊ฐ ์์ผ๋ฏ๋ก `draggable` ์์ฑ์ `false` ๊ฐ์ ์ค๋ค.
// Map.js
import React, { useState, useRef, useCallback } from 'react';
import styled, { css } from 'styled-components/macro';
const Map = () => {
const mapRef = useRef(null);
return (
<S.Map ref={mapRef}>
<img draggable={false} alt="map" src={'...'} />
</S.Map>
);
};
์์์ ๋ง์ฐ์ค๋ฅผ ์ฌ๋ฆฌ๋ฉด ์ ๋ชจ์์ผ๋ก ๋ฐ๋๋๋ก `cursor` ์์ฑ์ `grab`์ผ๋ก ์ค๋ค. ์ด๋ฏธ์ง๊ฐ ๋ถ๋ชจ ์์(S.Map)๋ณด๋ค ์ปค๋ ์คํฌ๋กค์ด ์๊ธฐ์ง ์๋๋ก `overflow`๋ `hidden`์ผ๋ก ๋ณ๊ฒฝํ๋ค.
// Map.js - CSS(Styled-Components)
const S = {};
S.Map = styled.div`
position: relative;
width: 100%;
height: 100%;
cursor: grab;
overflow: hidden;
`;
State
์คํฌ๋กค ์ํ
๋ง์ฐ์ค ์ด๋ฒคํธ๋ก ๋ฐ์ํ ์ขํ๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ์ํ ์ ์
import React, { useState, useRef, useCallback } from 'react';
import styled, { css } from 'styled-components/macro';
const Map = () => {
const [pos, setPos] = useState({ top: 0, left: 0, x: 0, y: 0 });
// ...
};
- `top` : div(S.Map) ์๋ฆฌ๋จผํธ์ y(์ธ๋ก) ์คํฌ๋กค ์์น — ์คํฌ๋กค์ ์ํด ๊ฐ๋ ค์ง ์์ญ์ ์ธ๋ก ๊ธธ์ด
- `left` : div(S.Map) ์๋ฆฌ๋จผํธ์ x(๊ฐ๋ก) ์คํฌ๋กค ์์น — ์คํฌ๋กค์ ์ํด ๊ฐ๋ ค์ง ์์ญ์ ๊ฐ๋ก ๊ธธ์ด
- `x` : ๋ง์ฐ์ค ํด๋ฆญ ์ด๋ฒคํธ์ event.clientX ์ขํ — ๋ธ๋ผ์ฐ์ ํ๋ฉด ๊ธฐ์ค์ X ์ขํ
- `y` : ๋ง์ฐ์ค ํด๋ฆญ ์ด๋ฒคํธ์ event.clientY ์ขํ — ๋ธ๋ผ์ฐ์ ํ๋ฉด ๊ธฐ์ค์ Y ์ขํ
ํด๋ฆญ ์ํ
ํด๋ฆญ ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ํ ์ํ ์ ์
// Map.js
import React, { useState, useRef, useCallback } from 'react';
import styled, { css } from 'styled-components/macro';
const Map = () => {
const [isMouseDown, seIsMouseDown] = useState(false);
// ...
};
๋ง์ฐ์ค ํธ๋ค๋ฌ
React๋ ์๋ก์ด props๋ฅผ ๋ฐ๊ฑฐ๋ ์ํ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์ปดํฌ๋ํธ ์์ ๋ชจ๋ ํจ์๋ฅผ ๋ค์ ์ ์ํ๋ฏ๋ก, `useCallback`์ ์ฌ์ฉํ์ฌ ๊ฐ์ด ๋ณ๊ฒฝ๋์๋๋ง ์ฌ์ ์ํ๋๋ก ํ๋ค.
onMouseDown (Tic)
๋ง์ฐ์ค๋ฅผ ํด๋ฆญํ์ ๋ ํธ์ถํ `startDrag()` ํธ๋ค๋ฌ๋ฅผ ์ ์ํ๋ค.
- ํด๋ฆญ ์ํ `true` ๋ก ๋ณ๊ฒฝ
- ํ์ฌ ์์(S.Map)์ ์คํฌ๋กค ์์น ์ ์ฅ
- ๋ง์ฐ์ค๋ฅผ ํด๋ฆญํ ์์น ์ ์ฅ
- (CSS) `cursor` ์์ฑ์ `grabbing`์ผ๋ก ๋ณ๊ฒฝํ๊ณ , `user-select`๋ `none`(ํ ์คํธ ์ ํ ๋ฐฉ์ง)์ผ๋ก ๋ณ๊ฒฝ
// Map.js
import React, { useState, useRef, useCallback } from 'react';
import styled, { css } from 'styled-components/macro';
const Map = () => {
const [isMouseDown, seIsMouseDown] = useState(false);
const startDrag = useCallback(({ clientX, clientY }) => {
seIsMouseDown(true);
// ํด๋ฆญ ์์ ์ clientX, clientY ์ขํ์ ์์์ ์คํฌ๋กค ์์น ์ ์ฅ
setPos({
left: mapRef.current.scrollLeft, // ์์์ left ์คํฌ๋กค ์์น(์คํฌ๋กค์ ์ํด ๊ฐ๋ ค์ง ์์ญ์ ๊ฐ๋ก ๊ธธ์ด)
top: mapRef.current.scrollTop, // ์์์ top ์คํฌ๋กค ์์น(์คํฌ๋กค์ ์ํด ๊ฐ๋ ค์ง ์์ญ์ ์ธ๋ก ๊ธธ์ด)
x: clientX, // ๋ธ๋ผ์ฐ์ ํ๋ฉด(๋ทฐํฌํธ) ๊ธฐ์ค์ X ์ขํ(left)
y: clientY, // ๋ธ๋ผ์ฐ์ ํ๋ฉด(๋ทฐํฌํธ) ๊ธฐ์ค์ Y ์ขํ(top)
});
}, []);
// ...
return (
<S.Map ref={mapRef} onMouseDown={startDrag} isMouseDown={isMouseDown}>
<img draggable={false} alt="map" src={'...'} />
</S.Map>
);
};
// CSS (styled-components)
S.Map = styled.div`
// ...
cursor: grab;
overflow: hidden;
${({ isMouseDown }) =>
isMouseDown &&
css`
cursor: grabbing;
user-select: none;
`}
`;
onMouseMove (๋๋๊ทธ) โญ๏ธ
๋ง์ฐ์ค๋ฅผ ๋๋๊ทธํ์ ๋ ํธ์ถํ `dragging` ํธ๋ค๋ฌ๋ฅผ ์ ์ํ๋ค. ํด๋ฆญํ์ง ์์ ์ํ๋ผ๋ฉด ์๋ฌด ์์ ๋ ํ์ง ์๊ณ , ๋ง์ฝ ํด๋ฆญํ ์ํ๋ผ๋ฉด...
โถ ๋ง์ฐ์ค ๋๋๊ทธ๋ฅผ ๋ฉ์ถ ์ง์ ๊ณผ, ๋ง์ฐ์ค๋ฅผ ํด๋ฆญํ ์ง์ ์ ๋บ ๊ฐ `dx`, `dy`๋ฅผ ๊ตฌํ๊ณ — ์คํฌ๋กคํ ๋ฒ์์ ๊ฐ
โท ํ์ฌ ์คํฌ๋กค ์์น์, ์คํฌ๋กคํ ๊ฐ์ ๋บ ์์น๋ก ์คํฌ๋กค ์ด๋
"์๋(ํด๋ฆญ) → ์" ๋ฐฉํฅ์ผ๋ก ๋๋๊ทธ๋ ์คํฌ๋กค `+`
ํ์ฌ `top`์ `0`, `clientY`(๋๋๊ทธ ๋ฉ์ถ ์ง์ ) `30`, `y`(ํด๋ฆญํ ์ง์ ) `60`์ด๋ผ๊ณ ๊ฐ์
- `30 - 60 = -30` → dy(clientY - y)
- `0 - (-30) = 30` → top - dy
- ์คํฌ๋กค top `30`์ผ๋ก ์ด๋ → ์คํฌ๋กค top์ด 0์์ 30์ผ๋ก ๋์ด๋จ
"์(ํด๋ฆญ) → ์๋" ๋ฐฉํฅ์ผ๋ก ๋๋๊ทธ๋ ์คํฌ๋กค `-`
ํ์ฌ `top`์ `30`, `clientY`(๋๋๊ทธ ๋ฉ์ถ ์ง์ ) `60`, `y`(ํด๋ฆญํ ์ง์ ) `30`์ด๋ผ๊ณ ๊ฐ์
- `60 - 30 = 30` → dy(clientY - y)
- `30 - 30 = 0` → top - dy
- ์คํฌ๋กค top `0`์ผ๋ก ์ด๋ → ์คํฌ๋กค top์ด 30์์ 0์ผ๋ก ์ค์ด๋ฌ
// Map.js
import React, { useState, useRef, useCallback } from 'react';
import styled, { css } from 'styled-components/macro';
const Map = () => {
const dragging = useCallback(
({ clientX, clientY }) => {
if (isMouseDown) {
const { x, y, left, top } = pos;
// (๋ง์ฐ์ค ์ด๋์ ๋ฉ์ถ ์ง์ )๊ณผ (๋ง์ฐ์ค๋ฅผ ํด๋ฆญํ ์ง์ )์ ๋บ ๊ฐ → ์คํฌ๋กคํ ๋ฒ์์ ๊ฐ
const dx = clientX - x;
const dy = clientY - y;
// (๋ง์ฐ์ค๋ฅผ ํด๋ฆญํ์ ๋์ ์์ scroll ์์น)์ (์คํฌ๋กคํ ๊ฐ)์ ๋บ ๋งํผ ์คํฌ๋กค ์ด๋
// dy๊ฐ -๋ฉด (์๋)์์ (์) ๋ฐฉํฅ ๋๋๊ทธ์ด๋ฉฐ, -(-dy) → +dy → ์คํฌ๋กค +
// dy๊ฐ +๋ฉด (์)์์ (์๋) ๋ฐฉํฅ ๋๋๊ทธ์ด๋ฉฐ, -(dy) → -dy → ์คํฌ๋กค -
mapRef.current.scrollTo(left - dx, top - dy);
}
},
[isMouseDown, pos],
);
// ...
return (
<S.Map ref={mapRef} onMouseMove={dragging}>
<img draggable={false} alt="map" src={'...'} />
</S.Map>
);
};
onMouseUp (Toc)
๋ง์ฐ์ค ํด๋ฆญ์ ํด์ ํ๊ฑฐ๋ ์ด๋ฒคํธ ์์ญ์ ๋ฒ์ด๋ฌ์ ๋, ๋ง์ฐ์ค ํด๋ฆญ ์ํ `isMouseDown`์ `false`๋ก ๋ฐ๊ฟ ํธ๋ค๋ฌ๋ฅผ ์ ์ํ๋ค. ๋ง์ฐ์ค ํด๋ฆญ ์ํ๊ฐ `false`๊ฐ ๋์ผ๋ CSS์ `cursor` ์์ฑ์ `grab`์ผ๋ก ๋์๊ฐ๋ค.
// Map.js
import React, { useState, useRef, useCallback } from 'react';
import styled, { css } from 'styled-components/macro';
const Map = () => {
const [isMouseDown, seIsMouseDown] = useState(false);
const finishDrag = useCallback(({ type }) => {
if (type === 'mouseup' || type === 'mouseleave') {
seIsMouseDown(false);
}
}, []);
// ...
return (
<S.Map ref={mapRef} onMouseUp={finishDrag} onMouseLeave={finishDrag}>
<img draggable={false} alt="map" src={''} />
</S.Map>
);
};
onContextMenu (๋ง์ฐ์ค ์ฐํด๋ฆญ)
๋ง์ฐ์ค๋ฅผ ์ฐํด๋ฆญํ์ ๋์ ์ขํ๋ฅผ ์ ์ฅํ๊ณ , ์ ์ฅํ ์ขํ ์ง์ ์ ๋ง์ปค(์ด๋ฏธ์ง) ๋ฑ์ ์ถ๊ฐํ๊ณ ์ถ๋ค๋ฉด, ๋ค์ดํฐ๋ธ ์ด๋ฒคํธ์ ์ ๊ทผํด์ `offsetX`, `offsetY` ๊ฐ์ ์ ์ฅํ๋ฉด ๋๋ค.
โก๏ธ `offsetX`, `offsetY`๋ DOM ์ด๋ฒคํธ๊ฐ ๊ฑธ๋ ค ์๋ ์๋ฆฌ๋จผํธ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๊ณณ์ ์ขํ๋ฅผ ์ถ๋ ฅํ๋ค. ํ๋ฉด์ ๋ณด์ด์ง ์๋ ์คํฌ๋กค ์์ญ๊น์ง ํฌํจํ๋ค.
๋ง์ฐ์ค ์ฐํด๋ฆญ์ ๋ํ ์ด๋ฒคํธ๋ `onContextMenu` ์์ฑ์ ์ด์ฉํ๋ค. ์ฐํด๋ฆญ ํ ์ปจํ ์คํธ ๋ฉ๋ด๊ฐ ์๋์ผ๋ก ๋ํ๋๋๋ฐ ์ด๋ฅผ ๋ฐฉ์งํ๋ ค๋ฉด ๊ณ ์ ๋์์ ๋ง์์ฃผ๋ `e.preventDefault()` ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
// Map.js
import React, { useState, useRef, useCallback } from 'react';
import styled, { css } from 'styled-components/macro';
import Marker from './Maker'; // ๋ง์ปค ์ด๋ฏธ์ง ์ปดํฌ๋ํธ import
const Map = () => {
const [markers, setMarkers] = useState([]);
const addMarker = useCallback(
(e) => {
setMarkers([
...markers,
[e.nativeEvent.offsetX, e.nativeEvent.offsetY],
// offsetX/Y: DOM ์ด๋ฒคํธ๊ฐ ๊ฑธ๋ ค์๋ ์์๋ฅผ ๊ธฐ์ค(ํ๋ฉด์ ๋ณด์ด์ง ์๋ ์คํฌ๋กค ์์ญ ํฌํจ)์ผ๋ก ์ขํ ์ถ๋ ฅ
]);
e.preventDefault(); // ์ฐํด๋ฆญ ๋ฉ๋ด ์ถ๋ ฅ ๋ฐฉ์ง
},
[markers, setMarkers],
);
// ...
return (
<S.Map ref={mapRef} onContextMenu={addMarker}>
<img draggable={false} alt="map" src={''} />
{markers.length >= 1
? markers.map((markerPos) => (
<Marker key={''} left={markerPos[0]} top={markerPos[1]} />
))
: null}
</S.Map>
);
};
์ ์ฅํ `offetX`, `offetY` ์ขํ๊ฐ์ ์์ ์ด๋ฏธ์ง ํ๊ทธ์ `left`, `top` ํฌ์ง์ ๊ฐ์ผ๋ก ์ค์ ํ๋ค.
// Marker.js
import React from 'react';
import styled from 'styled-components/macro';
import markerImg from '../assets/marker.png';
const Marker = ({ left, top }) => {
return (
<Image
draggable={false}
alt="marker"
src={markerImg}
left={left}
top={top}
/>
);
};
const Image = styled.img`
position: absolute;
width: 60px;
left: ${({ left }) => left}px;
top: ${({ top }) => top}px;
`;
export default Marker;
๋ ํผ๋ฐ์ค
- Drag to scroll - HTML DOM
- ์์ ์ฌ์ด์ฆ์ ์คํฌ๋กค
- ๋ง์ฐ์ค ์ด๋ฒคํธ
- ๋ธ๋ผ์ฐ์ ์ฐฝ ์ฌ์ด์ฆ์ ์คํฌ๋กค
- ์ขํ
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Algorithm] ํน์ ์ ๊น์ง์ ํฉ ๊ตฌํ๊ธฐ / ๋ฑ์ฐจ์์ด (๊ฐ์ฐ์ค ๊ณต์) (0) | 2024.04.28 |
---|---|
[CS] ์ง๋ฒ ๊ณ์ฐ ๋ฐฉ๋ฒ โ 10์ง์ โ 2์ง์ ๋ณํ (0) | 2024.04.28 |
[JS] ์๋ฐ์คํฌ๋ฆฝํธ URL ๊ฐ์ฒด / searchParams (0) | 2024.04.27 |
[JS] ์๋ฐ์คํฌ๋ฆฝํธ Map / Set ์๋ฃ๊ตฌ์กฐ (0) | 2024.04.27 |
[Web] ์ธ์ vs ์ฟ ํค vs ํ ํฐ (0) | 2024.04.27 |
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[Algorithm] ํน์ ์ ๊น์ง์ ํฉ ๊ตฌํ๊ธฐ / ๋ฑ์ฐจ์์ด (๊ฐ์ฐ์ค ๊ณต์)
[Algorithm] ํน์ ์ ๊น์ง์ ํฉ ๊ตฌํ๊ธฐ / ๋ฑ์ฐจ์์ด (๊ฐ์ฐ์ค ๊ณต์)
2024.04.28 -
[CS] ์ง๋ฒ ๊ณ์ฐ ๋ฐฉ๋ฒ — 10์ง์ โ 2์ง์ ๋ณํ
[CS] ์ง๋ฒ ๊ณ์ฐ ๋ฐฉ๋ฒ — 10์ง์ โ 2์ง์ ๋ณํ
2024.04.28 -
[JS] ์๋ฐ์คํฌ๋ฆฝํธ URL ๊ฐ์ฒด / searchParams
[JS] ์๋ฐ์คํฌ๋ฆฝํธ URL ๊ฐ์ฒด / searchParams
2024.04.27 -
[JS] ์๋ฐ์คํฌ๋ฆฝํธ Map / Set ์๋ฃ๊ตฌ์กฐ
[JS] ์๋ฐ์คํฌ๋ฆฝํธ Map / Set ์๋ฃ๊ตฌ์กฐ
2024.04.27