iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Modern Web

Modern Web × AI《拖延怪日記》:語錄陪伴擺脫拖延系列 第 17

【Day 17】- 入門 JavaScript 網頁架設:Drag & Drop API(拖曳互動)

  • 分享至 

  • xImage
  •  

摘要
把 Day 16 的「點擊把任務卡放進時段格」升級為「拖曳(Drag & Drop)」,並在放下(drop)後同步寫入 localStorage。同時保留點擊作為鍵盤與行動裝置的回退方案(a11y & fallback)。

為什麼要加入拖曳?

  • 更直覺:把任務「拖進」早/午/晚格子,比按鈕更貼近使用者心智模型。
  • 學習重點:實作原生 Drag & Drop API(dragstart / dragover / drop),理解為何 event.preventDefault() 在 dragover 階段是必要的。

學習重點

  • Drag & Drop 事件:dragstart(拖曳開始)→ dragover(允許放下)→ drop(處理落點)
  • 在 dragover 內呼叫 e.preventDefault(),否則 drop 事件不會觸發
  • 落點後呼叫既有 placeTaskTo(period),並將狀態寫入 localStorage

實作

  1. HTML:為三個時段格子加上 data-period 與 dropeffect(微調)
    找到 Day 16 的三個格子按鈕,用相同位置替換為下列三行:
<button id="morningSlot"   class="time-slot" type="button" data-period="morning"   aria-dropeffect="move" aria-label="早上格子(☀️)">☀️ 早上</button>
<button id="afternoonSlot" class="time-slot" type="button" data-period="afternoon" aria-dropeffect="move" aria-label="下午格子(🧠)">🧠 下午</button>
<button id="eveningSlot"   class="time-slot" type="button" data-period="evening"   aria-dropeffect="move" aria-label="晚上格子(🌃)">🌃 晚上</button>
  1. JS:讓任務卡支援拖曳(在 renderTaskCard() 內新增)
    在建立 card 後、taskCardWrap.appendChild(card); 之前或之後加入下列設定:
// [Day17-NEW] 任務卡可被拖曳
card.setAttribute('draggable', 'true');
card.addEventListener('dragstart', (e) => {
  // 以目前任務 id 當作拖曳資料(文字即可)
  e.dataTransfer.setData('text/plain', currentTaskId || '');
  // 可選:提供移動效果
  e.dataTransfer.effectAllowed = 'move';
});
  1. JS:為三個時段格子綁定 DnD 事件(新增)
    放在三個格子的「點擊版 MVP 綁定」附近,新增下列拖曳處理:
// [Day17-NEW] 允許把卡片拖進三個時段格子
[morningSlotEl, afternoonSlotEl, eveningSlotEl].forEach(slot => {
  if (!slot) return;

  // 關鍵:dragover 必須 preventDefault,否則 drop 不會觸發
  slot.addEventListener('dragover', (e) => {
    e.preventDefault();              // ★★ 必要點!否則無法觸發 drop
    e.dataTransfer.dropEffect = 'move';
    slot.classList.add('dropping');  // 可選:加上暫時樣式(若有 CSS)
  });

  slot.addEventListener('dragleave', () => {
    slot.classList.remove('dropping');
  });

  slot.addEventListener('drop', (e) => {
    e.preventDefault();
    slot.classList.remove('dropping');

    // 從拖曳資料取回 recordId(此範例實際用不到,示範完整流程)
    const draggedId = e.dataTransfer.getData('text/plain');

    // 用 data-period 取得目標時段,沿用 Day16 的存檔流程
    const period = slot.dataset.period;
    if (period) {
      placeTaskTo(period);           // 內部已 appendChild + writeScheduleMap
      // a11y 文字回饋
      scheduleFeedback.textContent = `已安排到:${PERIOD_LABEL[period] || period}`;
    }
  });
});
  1. JS:在顯示安排頁時,確保任務卡與 DnD 已就緒(微調)
    在 showPage('taskSchedule') 分支內 renderTaskCard() 後面補充一行(若你想更保險):
// [Day17-NEW] 顯示時再保險地掛上 drag 事件(renderTaskCard 也會做一次)
const cardEl = document.getElementById('taskCard');
if (cardEl && !cardEl.hasAttribute('draggable')) {
  cardEl.setAttribute('draggable', 'true');
  cardEl.addEventListener('dragstart', (e) => {
    e.dataTransfer.setData('text/plain', currentTaskId || '');
    e.dataTransfer.effectAllowed = 'move';
  });
}

保留 Day 16 的點擊綁定(鍵盤操作/行動裝置回退),不需要刪除:

// 仍保留點擊版(a11y 與觸控回退)
morningSlotEl?.addEventListener('click',   () => placeTaskTo('morning'));
afternoonSlotEl?.addEventListener('click', () => placeTaskTo('afternoon'));
eveningSlotEl?.addEventListener('click',   () => placeTaskTo('evening'));

驗證

  1. 拖曳:把任務卡拖到「🧠 下午」→ 卡片進入該格,底部顯示「已安排到:下午」。
  2. 重整:重新整理 → 開啟安排頁 → 任務卡仍在剛剛放下的格子(localStorage 成功保存)。
  3. 回退:不方便拖曳時,點擊任一格子仍可安排(保留 Day 16 行為)。
  4. 鍵盤:聚焦任務卡可用 Enter/Space 觸發拖曳替代流程(若有額外實作);至少可以用 點擊完成放入。

常見錯誤 & 排查

  1. drop 沒反應
    八成是 dragover 沒呼叫 e.preventDefault();補上後 drop 才會觸發。
  2. 看不到拖曳效果
    確認卡片元素有 draggable="true",且 dragstart 有被觸發。
  3. 已放入但重整後消失
    檢查 SCHEDULE_KEY 的內容是否更新({ [recordId]: 'morning'|'afternoon'|'evening' }),以及目前畫面上的 currentTaskId 是否與最新紀錄一致。
  4. 讀屏沒有回饋
    確定有更新 #scheduleFeedback(aria-live="polite")的文字。

補充說明:為什麼 event.preventDefault() 必要?

在原生 Drag & Drop 中,瀏覽器預設不允許在某元素上放下拖曳資料。
只有在 dragover 事件中呼叫 e.preventDefault(),瀏覽器才會把該元素視為「可放置目標」,進而觸發後續的 drop。少了這一步,drop 完全不會被觸發。


上一篇
【Day 16】- 入門 JavaScript 網頁架設:appendChild 移動 DOM(任務卡 × 時段格子)
下一篇
【Day 18】— 入門 JavaScript 網頁架設:navigator.share(溫暖總結與分享引導)
系列文
Modern Web × AI《拖延怪日記》:語錄陪伴擺脫拖延19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言