분류 javascript

끌어서 놓기 목록 작성

컨텐츠 정보

  • 조회 356 (작성일 )

본문

2019 년 드래그 앤 드롭 구성 요소를 만들려면 어떻게 해야 합니까?


https://baseweb.design/blog/drag-and-drop-list/ 


Base Web DnD List component 


드래그 앤 드롭 상호 작용은 1970 년대에 Jef Raskin이 Macintosh 프로젝트의 일부로 발명했습니다. 40 년 전입니다! 그렇다면 웹에서 드래그 할 수 있게 만드는 것이 여전히 고통스러운 이유는 무엇입니까? 컴포넌트 라이브러리가 정렬 가능한 목록과 슬라이더를 지원해야 하므로 최근에 답을 찾고 있었습니다.


정렬 가능한 목록 구성 요소 


HTML 드래그 앤 드롭 API 


공식 HTML 드래그 앤 드롭 API가 있으며 HTML5 표준에 도입되었으며 8 가지 이벤트가 제공됩니다.


  • drag
  • dragend
  • dragenter
  • dragexit
  • dragleave
  • dragover
  • dragstart
  • drop

여기 당신을 위한 작은 퀴즈가 있습니다. dragexit와 dragleave의 차이점은 무엇입니까? 확실하지 않다? 세 가지 사용자 동작으로 설명 할 수 있는 여덟 가지 이벤트를 구현하려면 약간의 상상력이 필요합니다.


  • pressing the mouse button
  • moving the mouse
  • releasing the mouse button

이것이 이 API에 대한 유일한 이상한 것은 아닙니다. 다른 "glitches":


  • no support for touch devices (ugh)
  • limited styling options for dragged items
  • the API is wildly inconsistent across browsers

이 비교적 새로운 API가 왜 그렇게 나쁜지 궁금 할 것입니다. 대답은 당신을 놀라게 할 것입니다 :


그렇습니다. Internet Explorer 6이 여기에 범인입니다. 모바일 지원이나 다른 결점으로 해결하고 싶지 않다면 어떻게 해야합니까?


요소가 더 이상 드래그 작업의 즉시 선택 대상이 아닌 경우 dragexit 이벤트가 시작됩니다. 드래그 된 요소 또는 텍스트 선택이 유효한 놓기 대상을 벗어나면 dragleave 이벤트가 시작됩니다.


기본 동작 추적 


다행히도 여전히 좋은 마우스 및 터치 이벤트가 있습니다.


  • mousedown (touchstart)
  • mousemove (touchmove)
  • mouseup (touchend, touchcancel)

또한 세 가지 드래그 앤 드롭 동작으로 잘 변환됩니다.


Mouse/Finger Coordinates 


우리가 필요로 하는 또 다른 정보는 주어진 지점에서 마우스 / 손가락의 좌표입니다. 모든 마우스 / 터치 이벤트가 통과하기 때문에 매우 간단합니다. 마우스 이벤트 :

  • event.clientX
  • event.clientY

터치 이벤트 :

  • event.touches[0].clientX
  • event.touches[0].clientY

측정 요소 


우리는 사물을 측정해야 합니다. 끔찍한 이름을 가진 훌륭한 API가 있습니다. Element.getBoundingClientRect를 소개하겠습니다. 모든 요소에서 호출하면 치수와 위치가 반환 됩니다.

{
"width": 500,
"height": 100,
"top": 674,
"right": 800,
"bottom": 774,
"left": 1300
}


Positioning 

사용자가 항목을 "이동"할 때 실제로 움직이는 것처럼 보이도록 위치를 계속 변경해야 합니다. 첫 번째 순진한 솔루션은 다음과 같습니다.


position: fixed;
top: 100px;
left: 200px;


사용자가 항목을 이동함에 따라 위치를 고정하고 상단 및 왼쪽 값을 계속 업데이트합니다. 이것은 합리적으로 잘 작동합니다. 그러나 더 나은 솔루션은 CSS 변환을 사용하는 것입니다.

transform: translate(10px, 20px);
transition-duration: 1s;


GPU가 가속화되어 브라우저에서 더 적은 작업이 필요하며 전환 기간과 결합하여 애니메이션을 적용 할 수도 있습니다.


Other DOM APIs 


스크롤, 컨테이너 조작 또는 브라우저 워크로드 최적화를 처리하는 유용한 DOM API가 많이 있습니다.

window.getComputedStyle(ELement);
window.scrollTo(X, Y);
window.pageXOffset;
window.pageYOffset;
window.innerHeight;
Element.contains();
Element.scrollTop;
requestAnimationFrame(() => {});


ReactDOM.createPortal 


또한 매우 유용한 React API 인 포털이 있습니다.


포털은 하위 컴포넌트를 상위 컴포넌트의 DOM 계층 외부에 있는 DOM 노드로 렌더링 하는 일류 방법을 제공합니다.


왜 그런 일이 필요할까요? 드래그 한 항목을 안정적으로 배치 할 수 있는 유일한 방법입니다.


render() {
return ReactDOM.createPortal(
<li>Dragged</li>,
document.body
);
}


이 예에서는 드래그 한 목록 항목을 문서 본문의 맨 아래로 "포털"합니다. 이렇게 하면 항목에 부모가 없고 CSS 변환과 같이 해당 위치에 영향을 줄 수 있는 상속 된 CSS 속성이 없습니다. 이 기술이 모달 또는 알림에 사용되는 것을 종종 볼 수 있습니다.


The Algorithm 


필요한 모든 도구와 API가 있습니다. 이제 모든 것을 하나의 알고리즘에 넣을 차례입니다.


User starts dragging the second item 


사용자가 두 번째 항목을 클릭하고 드래그 하기 시작합니다. 가시성을 숨김으로 설정하여 항목이 차지하는 현재 공간을 유지하고 동시에 루트로 포탈합니다. 초기 상단과 왼쪽 위치를 지정하고 사용자가 계속 움직일 때 변환 값을 업데이트합니다.


User reaches the boundary of the third item


사용자가 세 번째 항목의 경계에 도달 했으므로 세 번째 항목을 두 번째 위치로 이동하려고 합니다. 마이너스 변환 Y 값을 적용하기 만하면 됩니다. 또한 애니메이션을 적용하려면 전환 속성을 추가해야 합니다.


지금까지는 항목의 DOM 순서를 변경하지 않았습니다. 사용자가 드롭을 마친 후에 만 ​​발생합니다.


당신이 그것에 대해 갈 수 있는 방법은 여러 가지가 있지만 이 알고리즘은 매우 간단하고 작동합니다.


Accessibility 


무슨 뜻인가요? 키보드와 스크린 리더를 통해서만 구성 요소를 제어 할 수 있어야 합니다. 우리가 구현하는 일련의 키보드 단축키가 있습니다 :


  • tab and shift+tab to focus a item
  • space to lift or drop the item
  • j or arrow down to move the lifted item down
  • k or arrow up to move the lifted item up
  • escape to cancel the lift and return the item to its initial position

또한 스크린 리더를 통해 들을 수 있는 신호를 제공해야 합니다. 먼저 각 항목을 설명해야 항목을 이동할 수 있습니다.

<li aria-roledescription="This is a draggable item. Press space to lift.">
Item 1
</li>


그런 다음 사용자가 상호 작용을 하면서 추가 메시지를 제공합니다.


<div aria-live="assertive" role="log" aria-atomic="true">
You have lifted item at position 5. Press j to move it down...
</div>


이것을 ARIA 라이브 지역이라고 합니다. 내용을 설정 한 내용이 무엇이든 스크린 리더가 발표합니다. 자세한 내용은 이 기사를 확인하십시오.


Little Things 


이제 눈치 채지 못할 작은 것들이 있지만 전체 상호 작용을 더 좋게 만듭니다. 예를 들어, 드롭 애니메이션입니다. 여기에는 아무것도 없습니다 :

No drop animation. 


반면에 우리는


The drop is animated. 


그리고 이와 같이 고려해야 할 다른 많은 작은 상호 작용이 있습니다.


Testing 


드래그 앤 드롭 라이브러리는 여러 DOM API에 의존해야 합니다. 단위 테스트를 작성하려면 모든 테스트를 조롱해야 합니다. 그 시점에서 무엇을 테스트하고 있습니까? 엔드 투 엔드 테스트에 중점을 두는 것이 더 나은 솔루션 일 수 있습니다. Puppeteer를 사용하면 짧고 설명적인 테스트를 작성할 수 있습니다. 마우스를 사용하여 전체 상호 작용을 테스트합니다.


test('dnd the first item to second position', async () => {
await page.mouse.move(190, 111);
await page.mouse.down();
await page.mouse.move(190, 190);
await page.mouse.up();
expect(await getListItems(page)).toEqual([
'Item 2',
'Item 1',
'Item 3',
'Item 4',
'Item 5',
'Item 6',
]);
});


마우스를 움직여서 마지막에 DOM 요소의 순서를 확인합니다. 키보드 테스트는 비슷합니다.


test('move the first item to second position', async () => {
await page.keyboard.press('Tab');
await page.keyboard.press('Space');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('Space');
expect(await getListItems(page)).toEqual([
'Item 2',
'Item 1',
'Item 3',
'Item 4',
'Item 5',
'Item 6',
]);
});


Gotchas 


정기 테스트에서 다루지 않는 버그가 있습니다. 우리는 많은 위치 지정을 처리하기 때문에 일부 항목을 몇 픽셀 씩 잘못 배치하는 것은 매우 쉽습니다. jest-image-snapshot으로 시각적 회귀 테스트를 활용할 수 있습니다. 그것은 우리의 기능적 구성 요소의 이미지 (스냅 샷)를 취하고 후속 실행에서 다른 이미지를 가져 와서 픽셀 단위로 비교합니다. 차이가 있으면 테스트가 실패합니다. 각 OS는 글꼴을 다르게 렌더링 합니다. 테스트를 안정적으로 유지하려면 도커를 사용해야 할 수도 있습니다.


Internet Explorer 


솔직히 IE를 처음 열고 모든 것이 작동하는 것을 보는 것은 놀라운 일입니다. 다행히도 문제는 비교적 사소합니다.


  • transform: translate doesn't work for table rows
  • window.scrollBy doesn't exist
  • window.scrollY needs to be replaced by window.pageYOffset
  • window.scroll needs to be replaced window.scrollTo

Safari and Scrolling 


터치 스크롤과 드래그 앤 드롭의 동작은 완전히 동일하므로 터치 스크롤을 비활성화 해야 합니다. 운 좋게도 우리를 위해 CSS 속성이 있습니다.


touch-action: none; 


모든 브라우저에서 작동합니다. 사파리 제외. CSS가 실패하면 어떻게 합니까? 더 많은 JavaScript를 사용하십시오! 모든 터치 이벤트를 기본값으로 e.prevent 방지 할 수 있습니다. 그러나 이벤트 리스너를 수동적이지 않게 하는 것을 잊지 마십시오.


document.addEventListener('touchstart', _, {passive: false}); 


그렇지 않으면 e.preventDefault() 호출은 기본적으로 무시됩니다. 이것은 또한 React 이벤트 시스템을 전혀 사용할 수 없다는 것을 의미합니다. 기본 이벤트 만 사용하십시오.


결론 


드래그 앤 드롭 웹 경험을 구축하는 것은 참여하지만, 불가능하지 않다. 당신은 모바일 지원 및 접근성에 타협해서는 안됩니다. 이를 위해 HTML5 API를 피하고 마우스 / 터치 이벤트를 수행하십시오. 위치를 들어, CSS의 변환은 올바른 선택입니다. 브라우저는 물건을 측정하기 위해 getBoundingClientRect와 같은 많은 훌륭한 API를 제공합니다. 당신은 아무 것도를 해제하기 전에 그리고, 테스트를 작성하는 것을 잊지 마세요. 인형은 쉽게!


React Advanced London 2019에서 이 주제에 대해 이야기했습니다. 우리는 React 가능한 별도의 오픈 소스 라이브러리를 공개했습니다. Base DnD List는 이를 내부적으로 사용합니다.