खींचने और छोड़ने लायक एलिमेंट बनाना

वेब की सुविधाएं: पॉइंटर इवेंट टच-ऐक्शन user-select

किसी मौजूदा फ़्लोटिंग एलिमेंट में खींचने और छोड़ने की सुविधा जोड़ें. जैसे, पैनल, पैलेट, टूलबार, इंस्पेक्टर या डायलॉग. खींचने और छोड़ने से यह तय नहीं होता कि एलिमेंट को कैसे दिखाया जाए, खारिज किया जाए, स्टैक किया जाए या उस पर फ़ोकस किया जाए.

एक खास हैंडल का इस्तेमाल करना

जब एलिमेंट में चुने जा सकने वाले टेक्स्ट, बटन, इनपुट या लिंक शामिल हों, तब किसी खास हैंडल से खींचना और छोड़ना शुरू करें. इससे, इसके सामान्य कॉन्टेंट का इस्तेमाल किया जा सकता है. हैंडल को कीबोर्ड से मूव करने लायक बनाएं. साथ ही, उसे ऐक्सेस किया जा सकने वाला ऐसा नाम दें जिसमें ऐरो-की इंटरैक्शन का ज़िक्र हो.

हैंडल में role="application" का इस्तेमाल न करें. इससे सहायक टेक्नोलॉजी के इंटरैक्शन में बेवजह बदलाव होता है.

cursor: grab/grabbing, touch-action: none, और user-select: none और -webkit-user-select: none, दोनों को हैंडल पर लागू करें, न कि खींचने और छोड़ने लायक पूरे एलिमेंट पर. इससे, हैंडल के बाहर मौजूद टेक्स्ट को चुना जा सकता है और टच जेस्चर सामान्य तरीके से काम करते हैं:

.drag-handle {
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.drag-handle.dragging {
  cursor: grabbing;
}

पॉइंटर की पोज़िशन और पहुंच को बनाए रखना

मूवमेंट को व्यूपोर्ट तक सीमित करते समय, व्यूपोर्ट के हिसाब से पोज़िशनिंग मॉडल का इस्तेमाल करें, जैसे कि position: fixed.

  • सिर्फ़ प्राइमरी पॉइंटर बटन (event.button === 0) के लिए, खींचें और छोड़ें.
  • pointerdown पर पॉइंटर ऑफ़सेट को रिकॉर्ड करें और हर मूव पर इसका इस्तेमाल करें, ताकि एलिमेंट को उसके कोने से दूर ले जाने पर वह जंप न करे.
  • पॉइंटर के हैंडल से हटने के बाद भी, खींचने और छोड़ने की सुविधा को चालू रखें. setPointerCapture() पर डिफ़ॉल्ट रूप से सेट करें और lostpointercapture पर ड्रैग करने की स्थिति को हटाएं. अगर हैंडल को खींचकर छोड़ने के दौरान हटाया या फिर से रेंडर किया जा सकता है, तो इसके बजाय दस्तावेज़-लेवल के pointermove, pointerup, और pointercancel लिसनर का इस्तेमाल करें. ऐसा इसलिए, क्योंकि उस मामले में कैप्चर नहीं किया जा सकता.
  • इसके अलावा, खींचने और छोड़ने की स्थिति में जाने से पहले, करीब 4 पिक्सल के मूवमेंट का इंतज़ार करें. इससे हैंडल पर क्लिक करने से, खींचने और छोड़ने की स्टाइल ट्रिगर नहीं होती.

पॉइंटर कैप्चर की मदद से, मूवमेंट लिसनर हैंडल पर बने रह सकते हैं:

handle.addEventListener('pointerdown', (event) => {
  if (event.button !== 0 || event.target.closest('button, input, select, textarea, a')) return;

  const rect = draggable.getBoundingClientRect();
  offsetX = event.clientX - rect.left;
  offsetY = event.clientY - rect.top;
  handle.setPointerCapture(event.pointerId);
});

handle.addEventListener('pointermove', (event) => {
  if (!handle.hasPointerCapture(event.pointerId)) return;
  handle.classList.add('dragging');
  moveTo(event.clientX - offsetX, event.clientY - offsetY);
});

handle.addEventListener('lostpointercapture', () => {
  handle.classList.remove('dragging');
});

function moveTo(left, top) {
  const maxLeft = Math.max(0, window.innerWidth - draggable.offsetWidth);
  const maxTop = Math.max(0, window.innerHeight - draggable.offsetHeight);

  draggable.style.left = `${Math.max(0, Math.min(left, maxLeft))}px`;
  draggable.style.top = `${Math.max(0, Math.min(top, maxTop))}px`;
}

हर पॉइंटर और कीबोर्ड की पोज़िशन को एक ही हेल्पर से क्लैंप करें, ताकि एलिमेंट को ऐक्सेस किया जा सके. व्यूपोर्ट या एलिमेंट के साइज़ में बदलाव होने के बाद, इसकी मौजूदा पोज़िशन को फिर से क्लैंप करें. ज़्यादा लेआउट वाले एलिमेंट के लिए, खींचने और छोड़ने के दौरान ट्रांसलेट करने की सुविधा का इस्तेमाल करें. इससे हर फ़्रेम के लेआउट से बचा जा सकता है. इसके बाद, रिलीज़ होने पर फ़ाइनल पोज़िशन को left और top पर सेट करें.

कीबोर्ड की मदद से कर्सर को मूव करने की सुविधा दें

जब हैंडल पर फ़ोकस हो, तब ऐरो बटन की मदद से एलिमेंट को मूव करें और उन बटन के लिए preventDefault() को कॉल करें. पॉइंटर मूवमेंट की तरह ही, क्लैंप किए गए पोज़िशनिंग हेल्पर का इस्तेमाल करें. शुरुआत में, 15 पिक्सल की बढ़ोतरी करना सही रहता है. इसे कॉम्पोनेंट के साइज़ और सटीक होने की ज़रूरतों के हिसाब से अडजस्ट करें.

डायलॉग के लिए खास नोट

<dialog> या [popover], इस तरीके का इस्तेमाल कर सकता है. इसके लिए, उसे हेडर को हैंडल के तौर पर इस्तेमाल करना होगा. left और top असाइन करने से पहले, margin: 0 सेट करें. ऐसा इसलिए, क्योंकि ब्राउज़र के डिफ़ॉल्ट मार्जिन, मैन्युअल तरीके से पोज़िशनिंग करने में रुकावट डालते हैं. डायलॉग और पॉपओवर को खोलने, बंद करने, फ़ोकस करने, और खारिज करने के तरीके के बारे में जानने के लिए, डिक्लेरेटिव डायलॉग और पॉपओवर कंट्रोल और प्लैटफ़ॉर्म कंट्रोल डिसमिस डायलॉग देखें.

फ़ॉलबैक रणनीतियां

जब किसी कॉम्पोनेंट को सिर्फ़ खींचकर छोड़ने से उसकी जगह बदलती है, तो इसे परतदार वृद्धि के तौर पर मानें: JavaScript के बिना, एलिमेंट, उसके कॉन्टेंट, और उसके कंट्रोल को डिफ़ॉल्ट पोज़िशन में दिखने लायक, पढ़ने लायक, और काम करने लायक बनाए रखें. काम न करने वाले ड्रैग हैंडल को न दिखाएं: अगर हैंडल एक स्टैंडअलोन ग्रिप कंट्रोल है, तो उसे डिफ़ॉल्ट रूप से छिपाएं और JavaScript के शुरू होने पर दिखाएं; अगर हेडर, हैंडल के तौर पर भी काम करता है, तो JavaScript के शुरू होने पर उसके ड्रैग अफ़ोर्डेंस अटैच करें.

user-select: none से पहले -webkit-user-select: none शामिल करें, ताकि Safari में खींचने और छोड़ने के दौरान टेक्स्ट चुनने की सुविधा बंद रहे.