Drag'n'Drop (sichqoncha bilan)
Drag'n'Drop (sudrab tashlash) — foydalanuvchiga qulay interfeys usuli. Faylni papkaga sudrash, savatga mahsulot tashlash, elementlar tartibini o'zgartirish — bularning barchasi shu texnikaga asoslanadi. Bu darsda sof sichqoncha hodisalari asosidagi Drag'n'Drop'ni chuqur ko'rib chiqamiz.
dragstart, dragend va h.k.). U fayllarni tashqi ilovalardan sudrab kirtish uchun qulay, lekin cheklovlari bor: gorizontal/vertikal harakatni cheklash yoki maxsus animatsiyalar qilish qiyin. Shuning uchun ko'p hollarda oddiy sichqoncha hodisalari afzal ko'riladi — biz shuni o'rganamiz.Asosiy algoritm
Drag'n'Drop algoritmi juda sodda ko'rinadi — u uch hodisaga asoslanadi:
mousedown— elementni bosganda sudrashni boshlaymiz.mousemove— kursor harakatlanganda elementni ko'chiramiz (uniposition: absoluteqilib, koordinatalarini yangilaymiz).mouseup— tugma qo'yib yuborilganda sudrashni to'xtatamiz.
Muhim nuqta: mousemoveni elementga emas, balki butun hujjatga (documentga) bog'lash kerak. Chunki kursor tez harakatlanganda element kursordan orqada qolishi va mousemove element ustida bo'lmasligi mumkin. Agar hodisani faqat elementga bog'lasak, sudrash "uzilib" qoladi.
let ball = document.getElementById('ball');
ball.onmousedown = function(event) {
// Elementni body'ga to'g'ridan-to'g'ri joylashtiramiz
ball.style.position = 'absolute';
ball.style.zIndex = 1000;
document.body.append(ball);
function moveAt(pageX, pageY) {
ball.style.left = pageX - ball.offsetWidth / 2 + 'px';
ball.style.top = pageY - ball.offsetHeight / 2 + 'px';
}
// Elementni kursor markaziga joylashtiramiz
moveAt(event.pageX, event.pageY);
function onMouseMove(event) {
moveAt(event.pageX, event.pageY);
}
// mousemove'ni HUJJATGA bog'laymiz
document.addEventListener('mousemove', onMouseMove);
// Qo'yib yuborilganda tozalaymiz
ball.onmouseup = function() {
document.removeEventListener('mousemove', onMouseMove);
ball.onmouseup = null;
};
};
Standart ondragstart harakatini o'chirish
Agar yuqoridagi kodni sinasangiz, tuval (canvas) yoki rasm (image) elementlarida g'alati holat kuzatiladi: sudrash o'rniga brauzer o'zining "native" drag-and-drop harakatini ishga tushiradi. Bu bizga xalaqit beradi.
Yechim — brauzerning standart dragstart harakatini bekor qilish:
ball.ondragstart = function() {
return false;
};
Endi brauzer o'zining drag harakatini boshlamaydi va bizning kodimiz to'sqinliksiz ishlaydi.
Sudrash markazini to'g'rilash
Yuqorida biz elementni kursor markaziga joylashtirdik. Lekin haqiqiy interfeyslarda foydalanuvchi elementning istalgan joyidan ushlashi mumkin. Agar shunda elementni har doim markazga ko'chirsak, u "sakrab" ketadi.
To'g'ri yechim: mousedown paytida kursor bilan element chekkasi orasidagi masofani (siljish, shift) hisoblab, keyingi harakatlarda uni saqlab qolish kerak:
ball.onmousedown = function(event) {
// Kursor bilan element chap-yuqori burchagi orasidagi masofa
let shiftX = event.clientX - ball.getBoundingClientRect().left;
let shiftY = event.clientY - ball.getBoundingClientRect().top;
function moveAt(pageX, pageY) {
ball.style.left = pageX - shiftX + 'px';
ball.style.top = pageY - shiftY + 'px';
}
// ...qolgan qismi yuqoridagidek
};
getBoundingClientRect() elementning oynaga nisbatan koordinatalarini beradi. Shu sabab bu yerda clientX/Y ishlatiladi (ikkalasi ham oynaga nisbatan). Keyin joylashtirishda esa hujjatga nisbatan pageX/Y va left/top (absolute) ishlatiladi.pointer-events bilan nishonni aniqlash muammosi
Sudralayotgan element kursor tagida turadi. Bu bir muammo tug'diradi: kursor ostidagi haqiqiy nishon (masalan, savat yoki papka) qaysi element ekanini bilmoqchi bo'lsak, event.target har doim sudralayotgan elementning o'zini qaytaradi — chunki u kursor tagida yotibdi!
Yechim ikkita. Birinchisi — CSS pointer-events: none. Bu xossa elementni sichqoncha hodisalari uchun "ko'rinmas" qiladi — hodisalar undan o'tib, ostidagi elementga yetadi:
// Sudrayotgan element sichqoncha hodisalarini o'tkazib yuboradi:
// ball.style.pointerEvents = 'none';
// Endi event.target ostidagi haqiqiy elementni qaytaradi.
pointer-events: none oddiy va tez, lekin cheklovi bor: agar sudralayotgan elementda ham sichqoncha hodisalari kerak bo'lsa, bu ishlamaydi. Bunday holatda quyidagi elementFromPoint usuli afzal.document.elementFromPoint bilan nishonni aniqlash
Ikkinchi, universal yechim — document.elementFromPoint(clientX, clientY) metodi. U berilgan oyna koordinatalaridagi eng ustki (topmost) elementni qaytaradi. Sudrash paytida sudralayotgan elementni vaqtincha yashirib, ostidagi nishonni topamiz:
// mousemove ichida:
function onMouseMove(event) {
moveAt(event.pageX, event.pageY);
// Sudralayotgan elementni yashiramiz
ball.hidden = true;
let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
ball.hidden = false;
// elemBelow null bo'lishi mumkin (kursor oyna tashqarisida bo'lsa)
if (!elemBelow) return;
// Nishon (droppable) elementni topamiz
let droppableBelow = elemBelow.closest('.droppable');
// ...droppableBelow ustida ekanmizmi, tekshiramiz
}
elementFromPoint(clientX, clientY) aynan oyna koordinatalarini (client), hujjat koordinatalarini emas (page) kutadi. Agar adashib pageX/pageY bersangiz va sahifa skroll qilingan bo'lsa — noto'g'ri element qaytadi. Shuningdek, agar koordinatalar oyna tashqarisida bo'lsa, metod null qaytaradi.ball.hidden = true bilan bir lahzaga yashirib, so'ng darhol ball.hidden = false qilish miltillash chaqirmaydi, chunki brauzer bu ikki o'zgarish orasida ekranni qayta chizmaydi (repaint).Nishonlarni kuzatish: onDragEnter/onDragLeave
Yuqoridagi elemBelowni har mousemoveda hisoblab, oldingi qiymat bilan solishtirib, o'zimizning "nishonga kirdi / nishondan chiqdi" mantig'ini yozamiz. Bu bizga nishonni yoritish (highlight) yoki boshqa vizual javob berish imkonini beradi:
let currentDroppable = null;
function onMouseMove(event) {
// ...moveAt va elemBelow ni hisoblaymiz...
let droppableBelow = elemBelow ? elemBelow.closest('.droppable') : null;
if (currentDroppable !== droppableBelow) {
if (currentDroppable) {
// Oldingi nishondan chiqdik
leaveDroppable(currentDroppable);
}
currentDroppable = droppableBelow;
if (currentDroppable) {
// Yangi nishonga kirdik
enterDroppable(currentDroppable);
}
}
}
Xulosa
Sichqoncha asosidagi Drag'n'Drop algoritmi:
mousedown: sudrashni boshlash,shiftX/shiftYni hisoblash, standartondragstartnireturn falsebilan o'chirish.mousemove(hujjatga bog'langan): elementni ko'chirish vaelementFromPointyokipointer-events: noneorqali nishonni aniqlash.mouseup: sudrashni to'xtatish va ishlov beruvchilarni tozalash (removeEventListener).