๊ด€๋ฆฌ ๋ฉ”๋‰ด

UI Code Lab

๋ฐ”๋‹๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ UI ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„ํ•˜๊ธฐ – ์‹ค์ „ ์‚ฌ๋ก€ ์ค‘์‹ฌ ๋ณธ๋ฌธ

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธUI

๋ฐ”๋‹๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ UI ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„ํ•˜๊ธฐ – ์‹ค์ „ ์‚ฌ๋ก€ ์ค‘์‹ฌ

๐Ÿฏ๊ฟ€์ƒ์ด๐Ÿ 2025. 7. 11. 14:39

๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ UI ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„ํ•˜๊ธฐ

 

 

์›น UI์—์„œ ์‚ฌ์šฉ์ž์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๊ฐ€

๋ฐ”๋กœ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ์ธํ„ฐํŽ˜์ด์Šค(Drag and Drop UI) ์ž…๋‹ˆ๋‹ค.

 

ํŒŒ์ผ ์ฒจ๋ถ€, ๋ฆฌ์ŠคํŠธ ์ •๋ ฌ, ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์„ฑ ๋“ฑ ๋‹ค์–‘ํ•œ ์‹ค๋ฌด ์‚ฌ๋ก€์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ด ๊ธฐ๋Šฅ์€

์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ด ๊ธ€์—์„œ๋Š” ๋ฐ”๋‹๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ(Vanilla JavaScript)๋ฅผ ํ™œ์šฉํ•˜์—ฌ

๋‹ค์Œ ์„ธ ๊ฐ€์ง€ ์‹ค์ „ ์‚ฌ๋ก€๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ UI๋ฅผ ๊ตฌํ˜„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

  • โœ… Gmail ์Šคํƒ€์ผ์˜ ํŒŒ์ผ ์ฒจ๋ถ€ ๋“œ๋กญ์กด
  • โœ… TODO ์•ฑ์—์„œ ํ•  ์ผ ์ˆœ์„œ ๋ณ€๊ฒฝ ๊ธฐ๋Šฅ
  • โœ… ๋Œ€์‹œ๋ณด๋“œ ์œ„์ ฏ ์œ„์น˜ ์ด๋™ UI

 

๐Ÿ“‚ 1. ํŒŒ์ผ ์ฒจ๋ถ€ ๋“œ๋กญ์กด UI – Gmail ์Šคํƒ€์ผ ๊ตฌํ˜„

 

๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ๋ฐฉ์‹์œผ๋กœ ํŒŒ์ผ์„ ์ฒจ๋ถ€ํ•  ์ˆ˜ ์žˆ๋Š” UI๋Š”

์ด๋ฉ”์ผ, ๊ฒŒ์‹œํŒ, ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€ ๋“ฑ์—์„œ ๋งŽ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

๐Ÿ”ง ์ฃผ์š” ๊ธฐ๋Šฅ

  • dragenter, dragover, drop ์ด๋ฒคํŠธ๋ฅผ ํ™œ์šฉํ•œ ๋“œ๋กญ์กด ๊ตฌํ˜„
  • ์—…๋กœ๋“œ๋œ ํŒŒ์ผ ๋ชฉ๋ก ์‹ค์‹œ๊ฐ„ ์‹œ๊ฐํ™”
  • ํŒŒ์ผ ํ˜•์‹ ํ•„ํ„ฐ๋ง ๋ฐ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ

 

๐Ÿ’ก UX ๊ฐ•ํ™” ํŒ

  • ์‚ฌ์šฉ์ž๊ฐ€ ํŒŒ์ผ์„ ๋“œ๋กญํ•  ๋•Œ ๋ฐฐ๊ฒฝ์ƒ‰ ๋ณ€๊ฒฝ ๋“ฑ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต
  • DataTransfer.items๋ฅผ ํ™œ์šฉํ•ด ํด๋” ๋“œ๋ž˜๊ทธ ๋ฐฉ์ง€ ์ฒ˜๋ฆฌ
 
dropZone.addEventListener('drop', (e) => {
  e.preventDefault();
  const files = [...e.dataTransfer.files];
  renderFileList(files);
});

 

๐Ÿ”ƒ 2. ๋ฆฌ์ŠคํŠธ ์ •๋ ฌ ๊ธฐ๋Šฅ – TODO ๋ฆฌ์ŠคํŠธ ํ•ญ๋ชฉ ์ˆœ์„œ ๋ณ€๊ฒฝ

 

๋“œ๋ž˜๊ทธ๋ฅผ ํ†ตํ•ด ํ•  ์ผ ๋ชฉ๋ก์˜ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด

์‚ฌ์šฉ์ž๋Š” ๋”์šฑ ์ง๊ด€์ ์œผ๋กœ ๋ฆฌ์ŠคํŠธ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ”ง ์ฃผ์š” ๊ธฐ๋Šฅ

  • ๊ฐ ํ•ญ๋ชฉ์— draggable="true" ์†์„ฑ ๋ถ€์—ฌ
  • dragstart, dragover, drop ์ด๋ฒคํŠธ๋กœ DOM ์š”์†Œ ์ถ”์  ๋ฐ ์ •๋ ฌ
  • insertBefore ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•ด ๋ฆฌ์ŠคํŠธ ์ˆœ์„œ ๋ณ€๊ฒฝ

 

๐Ÿ’ก UX ๊ฐ•ํ™” ํŒ

  • ๋“œ๋ž˜๊ทธ ์ค‘์ธ ํ•ญ๋ชฉ์— dragging ํด๋ž˜์Šค ์ถ”๊ฐ€ํ•˜์—ฌ ์Šคํƒ€์ผ ๊ฐ•ํ™”
  • ๋“œ๋กญ ์œ„์น˜์— ์‹œ๊ฐ์  ํžŒํŠธ(placeholder DOM) ์ถ”๊ฐ€
 
list.addEventListener('drop', (e) => {
  const dragged = document.querySelector('.dragging');
  const target = e.target.closest('li');
  list.insertBefore(dragged, target);
});

 

๐Ÿ“ฆ 3. ๋Œ€์‹œ๋ณด๋“œ ์œ„์ ฏ ์ด๋™ – ๋ฐ•์Šค ๊ธฐ๋ฐ˜ ๋“œ๋ž˜๊ทธ ๊ตฌํ˜„

 

HTML Drag API ์™ธ์—๋„ mousedown, mousemove, mouseup๋ฅผ ํ™œ์šฉํ•œ

์ปค์Šคํ…€ ๋ฐฉ์‹์˜ ๋“œ๋ž˜๊ทธ UI๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

ํŠนํžˆ ๋Œ€์‹œ๋ณด๋“œ์˜ ์นด๋“œ๋‚˜ ์œ„์ ฏ ๋ฐฐ์น˜๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์กฐ์ •ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ”ง ์ฃผ์š” ๊ธฐ๋Šฅ

  • ์ปค์Šคํ…€ ์ด๋ฒคํŠธ ์ฒด์ธ์œผ๋กœ ์œ„์น˜ ์ œ์–ด
  • position: absolute์™€ left/top ์†์„ฑ ๋ณ€๊ฒฝ์„ ํ†ตํ•œ ์ด๋™
  • ์ปค์„œ ์Šคํƒ€์ผ ๋ณ€๊ฒฝ ๋ฐ ๊ฒฝ๊ณ„์„  ํžŒํŠธ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
 
document.addEventListener('mousemove', (e) => {
  if (isDragging) {
    box.style.left = `${e.pageX - offsetX}px`;
    box.style.top = `${e.pageY - offsetY}px`;
  }
});

 

๐Ÿ“Œ ๋ฐ˜์‘ํ˜• & ์ ‘๊ทผ์„ฑ ๊ณ ๋ ค

  • ๋ชจ๋ฐ”์ผ ๋Œ€์‘์„ ์œ„ํ•ด touchstart, touchmove ์ด๋ฒคํŠธ๋„ ๋ณ‘ํ–‰ ์ฒ˜๋ฆฌ
  • ํ‚ค๋ณด๋“œ ํƒ์ƒ‰ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ์™€ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ์„ ํ†ตํ•ด ์ ‘๊ทผ์„ฑ ๊ฐ•ํ™”
  • ์ƒํƒœ ์ €์žฅ์„ ์œ„ํ•ด localStorage์™€ ์—ฐ๋™ํ•˜์—ฌ UX ์ง€์†์„ฑ ํ™•๋ณด

 

๐Ÿ ๊ฒฐ๋ก 

 

๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ UI๋Š” ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜์„ ๊ฐ•ํ™”ํ•˜๋ฉด์„œ๋„

UX๋ฅผ ๋งค๋„๋Ÿฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ์ค‘์š”ํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ์ˆœ์ˆ˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค๋ฉด

์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ํ™•์žฅ์„ฑ๊นŒ์ง€ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ด ๊ธ€์˜ ์‚ฌ๋ก€๋“ค์„ ํ™œ์šฉํ•ด

์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ์—๋„

์ž์—ฐ์Šค๋Ÿฌ์šด ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ UI๋ฅผ ์ ์šฉํ•ด ๋ณด์„ธ์š”.