בפוסט הזה מוסברים העקרונות הבסיסיים של גרירה ושחרור.
יצירת תוכן שניתן לגרירה
ברוב הדפדפנים, כברירת מחדל ניתן לגרור בחירות טקסט, תמונות וקישורים. לדוגמה, אם תגרור קישור בדף אינטרנט, תופיע תיבה קטנה עם כותרת וכתובת אתר שאפשר לשחרר בסרגל הכתובות או בשולחן העבודה כדי ליצור או לנווט לקישור. כדי שתהיה אפשרות לגרור סוגי תוכן אחרים, עליך צריכים להשתמש בממשקי ה-API של HTML5 Drag and Drop.
כדי להפוך אובייקט שניתן לגרירה, מגדירים את draggable=true
ברכיב הזה. כמעט
כל דבר שניתן לגרור, כולל תמונות, קבצים, קישורים, קבצים או כל פריט אחר
תגי עיצוב בדף.
הדוגמה הבאה יוצרת ממשק לסידור מחדש של עמודות
בפריסה באמצעות רשת CSS. תגי העיצוב הבסיסיים של העמודות נראים כך, עם
המאפיין draggable
בכל עמודה שהוגדרה כ-true
:
<div class="container">
<div draggable="true" class="box">A</div>
<div draggable="true" class="box">B</div>
<div draggable="true" class="box">C</div>
</div>
זהו ה-CSS של רכיבי הקונטיינר והתיבה. שירות ה-CSS היחיד שקשור אל
תכונת הגרירה היא cursor: move
לנכס. שאר הקוד קובע את הפריסה והעיצוב של מאגר התגים
ורכיבי תיבה.
.container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
}
.box {
border: 3px solid #666;
background-color: #ddd;
border-radius: .5em;
padding: 10px;
cursor: move;
}
בשלב הזה אפשר לגרור את הפריטים, אבל לא יקרה כלום. כדי להוסיף התנהגות המשתמשים, עליכם להשתמש ב-JavaScript API.
האזנה לאירועים שגוררים
כדי לעקוב אחרי תהליך הגרירה, אפשר להאזין לכל אחד מהאירועים הבאים:
כדי לטפל בזרימת הגרירה, צריך סוג של רכיב מקור (כאשר הגרירה מתחיל), מטען הנתונים (payload) (הדבר שגורר) ויעד (אזור הבנת הירידה). רכיב המקור יכול להיות כמעט כל סוג של רכיב. היעד הוא אזור ירידה או קבוצה של אזורים עם ירידה בנתוני המשתמשים שיכולים לקבל את הנתונים מנסה לנטוש. לא כל הרכיבים יכולים לשמש לטירגוט. לדוגמה, היעד לא יכול להיות תמונה.
התחלה וסיום של רצף גרירה
אחרי שמגדירים מאפיינים של draggable="true"
בתוכן, צריך לצרף
handler של אירוע dragstart
כדי להתחיל את רצף הגרירה בכל עמודה.
הקוד הזה מגדיר את שקיפות העמודה ל-40% כשהמשתמש מתחיל לגרור אותה, ואז להחזיר אותו ל-100% כשאירוע הגרירה מסתיים.
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
}
let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
});
אפשר לראות את התוצאה בהדגמה הבאה של Glitch. גוררים פריט
שינויים באטימוּת. מכיוון שרכיב המקור מכיל את האירוע dragstart
, ההגדרה
מ-this.style.opacity
עד 40% נותן למשתמש משוב חזותי על כך שהאלמנט הזה
של הבחירה הנוכחית שמועברת. כשמשחררים את הפריט, רכיב המקור
חוזרים ל-100% אטימוּת, למרות שעדיין לא הגדרתם את התנהגות הירידה.
הוספת סימנים ויזואליים
כדי לעזור למשתמשים להבין איך לקיים אינטראקציה עם הממשק, השתמשו
גורמים מטפלים באירועים של dragenter
, dragover
ו-dragleave
. במשפט הזה,
עמודות הן יעדי שחרור בנוסף להיות ניתנים לגרירה. עוזרים למשתמש:
להבין זאת על ידי סימון הגבול כשהוא מחזיק פריט שגורר מעל
עמודה. לדוגמה, בשירות ה-CSS, אפשר ליצור מחלקה של over
עבור
רכיבים שמהווים ירידה ביעדים:
.box.over {
border: 3px dotted #666;
}
לאחר מכן, ב-JavaScript, מגדירים את הגורמים המטפלים באירועים, מוסיפים את המחלקה over
כאשר
העמודה גוררת מעל העמודה ומסירים אותה כשהרכיב שגורר עוזב. לחשבון
את ה-handler של dragend
אנחנו מוודאים גם להסיר את המחלקות בסוף
ולגרור.
document.addEventListener('DOMContentLoaded', (event) => {
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
items.forEach(function (item) {
item.classList.remove('over');
});
}
function handleDragOver(e) {
e.preventDefault();
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over');
}
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
});
יש בקוד הזה כמה נקודות שכדאי להתייחס אליהן:
פעולת ברירת המחדל עבור האירוע
dragover
הוא להגדיר את המאפייןdataTransfer.dropEffect
כ-"none"
המידע על הנכסdropEffect
מופיע בהמשך הדף. בינתיים, רק לדעת שהוא מונע את הפעלת האירועdrop
. כדי לשנות את ההגדרה הזו התנהגות, קוראים לפונקציהe.preventDefault()
. עוד שיטה טובה היא לחזורfalse
באותו handler.הגורם המטפל באירועים של
dragenter
משמש להחלפת מצב של המחלקהover
במקוםdragover
. אם משתמשים ב-dragover
, האירוע יופעל שוב ושוב בזמן שהמשתמש מחזיקה את הפריט שגורר מעל עמודה, וגורם למחלקה של ה-CSS להחליף מצב שוב ושוב. זה גורם לדפדפן לבצע הרבה עבודת עיבוד מיותרת, מה שעלול להשפיע על חוויית המשתמש. מומלץ מאוד לצמצם חוזרים, ואם צריך להשתמש ב-dragover
, כדאי ויסות נתונים (throttle) או ביטול הקפצה של ה-event listener.
השלמת ההשקה
כדי לעבד את הירידה, צריך להוסיף האזנה לאירוע עבור האירוע drop
. בתוך drop
handler, צריך למנוע את התנהגות ברירת המחדל של הדפדפן להשקות,
הוא בדרך כלל סוג של הפניה מחדש מעצבנת. כדי לעשות זאת, צריך להתקשר אל e.stopPropagation()
.
function handleDrop(e) {
e.stopPropagation(); // stops the browser from redirecting.
return false;
}
חשוב לרשום את ה-handler החדש לצד שאר רכיבי ה-handler:
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
אם תריצו את הקוד בשלב הזה, הפריט לא ישוחרר למיקום החדש. שפת תרגום
כדי לעשות את זה, השתמשו DataTransfer
לאובייקט.
המאפיין dataTransfer
מכיל את הנתונים שנשלחים בפעולת גרירה. dataTransfer
מוגדר באירוע dragstart
ונקרא או מטופל באירוע הירידה. ביצוע שיחה
באמצעות e.dataTransfer.setData(mimeType, dataPayload)
אפשר להגדיר את ה-MIME של האובייקט
הסוג והמטען הייעודי (payload) של הנתונים.
בדוגמה הזו נאפשר למשתמשים לשנות את הסדר של העמודות. לשם כך, צריך קודם לאחסן את ה-HTML של רכיב המקור בזמן הגרירה מתחיל:
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
באירוע drop
, עיבוד השחרור של העמודה מתבצע על ידי הגדרת הערכים של עמודת המקור
קוד ה-HTML של עמודת היעד שבה הורדתם את הנתונים. הזה
כוללת בדיקה של המשתמש לא לחזור לאותה עמודה
שגוררים מ-.
function handleDrop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
אפשר לראות את התוצאה בהדגמה הבאה. כדי שזה יעבוד, צריך בדפדפן מחשב. ממשק ה-API לגרירה ושחרור לא נתמך בנייד. גרירה ו- מפרסמים את העמודה A מעל העמודה B ושימו לב איך הם משנים מקומות:
עוד מאפייני גרירה
האובייקט dataTransfer
חושף מאפיינים כדי לספק משוב חזותי
למשתמש במהלך תהליך הגרירה ולשלוט באופן שבו כל יעד של שחרור מגיב
סוג מסוים של נתונים.
dataTransfer.effectAllowed
מגביל את 'סוג הגרירה' שהמשתמש יכול לבצע ברכיב. נעשה בו שימוש במודל העיבוד באמצעות גרירה ושחרור כדי לאתחל אתdropEffect
במהלך האירועיםdragenter
ו-dragover
. לנכס יכולים להיות הערכים הבאים:none
,copy
,copyLink
,copyMove
,link
,linkMove
,move
,all
,uninitialized
.dataTransfer.dropEffect
המדיניות הזו קובעת את המשוב שהמשתמש מקבל במהלךdragenter
ו-dragover
אירועים. כשהמשתמש מחזיק את הסמן שלו מעל לרכיב יעד, הרכיב של הדפדפן הסמן מציין איזה סוג פעולה תתבצע, למשל העתקה או תנועה. האפקט יכול לכלול אחד מהערכים הבאים:none
,copy
,link
,move
.e.dataTransfer.setDragImage(imgElement, x, y)
כלומר, במקום להשתמש בברירת המחדל של הדפדפן 'תמונת רפאים' משוב, אתה אתם יכולים להגדיר סמל גרירה.
העלאת קבצים
בדוגמה הפשוטה הזו, נעשה שימוש בעמודה גם כמקור הגרירה וגם כיעד הגרירה. הזה עשוי לקרות בממשק משתמש שמבקש מהמשתמש לסדר מחדש את הפריטים. במצבים מסוימים, יעד הגרירה והמקור עשויים להיות מסוגים שונים של רכיבים, כמו בממשק שבהם המשתמשים צריכים לבחור תמונה אחת כתמונה הראשית למוצר. גרירת התמונה שנבחרה ליעד.
השימוש ב'גרירה ושחרור' מתבצע לעיתים קרובות כדי לאפשר למשתמשים לגרור פריטים משולחן העבודה שלהם אל
באפליקציה. ההבדל העיקרי הוא ב-handler של drop
. במקום להשתמש
dataTransfer.getData()
כדי לגשת לקבצים, הנתונים שלהם נמצאים
מאפיין dataTransfer.files
:
function handleDrop(e) {
e.stopPropagation(); // Stops some browsers from redirecting.
e.preventDefault();
var files = e.dataTransfer.files;
for (var i = 0, f; (f = files[i]); i++) {
// Read the File objects in this FileList.
}
}
מידע נוסף בנושא זמין כאן גרירה ושחרור בהתאמה אישית.