Frontend Development 16 min read

Implementing a Resizable, Drag‑Drop Gantt Chart Component with Vue 3

This article explains how to build a full‑featured Gantt chart in Vue 3, covering the UI layout with left‑hand tree panels and right‑hand timeline, synchronization of scrolling and state, draggable and resizable task bars, date‑generation utilities for various time scales, and performance considerations for large data sets.

360 Quality & Efficiency
360 Quality & Efficiency
360 Quality & Efficiency
Implementing a Resizable, Drag‑Drop Gantt Chart Component with Vue 3

The article originates from a product request to create a Gantt chart, a visual time‑management tool that displays tasks as horizontal bars across a timeline, allowing intuitive date adjustments and avoiding overlaps.

Product requirements include a split view with a left tree‑structured table for IDs and names, a right‑hand Gantt chart, adjustable panel widths, draggable horizontal bars that snap to grid cells, and multiple time‑scale views (day, week, month, quarter, year).

Tool comparison notes that no ready‑made library fits perfectly, but the v-gantt UI and functionality are close enough to serve as a reference implementation.

Implementation idea – the layout consists of three containers: .container-left , .container-right , and a draggable .resize element positioned between them. The left panel shows a collapsible tree, while the right panel renders the Gantt bars.

<div class="container">
  <div class="container-left">...</div>
  <div class="resize"></div>
  <div class="container-right">...</div>
</div>

Synchronizing the two panels requires keeping their scroll positions and hover states in sync, which is handled by listening to scroll events on both sides and applying the same offset.

Drag‑and‑drop logic is encapsulated in a Vue component that tracks dragging , resizing , offsetX , and calculates the moved days based on a configurable step (the pixel width of one day). When dragging ends, the component updates the task’s left , start_time , and end_time properties and emits a change event.

function dragResize () {
  const resizeEl = document.querySelector('.resize');
  const leftEl   = document.querySelector('.container-left');
  const rightEl  = document.querySelector('.container-right');
  const containerEl = document.querySelector('.container');

  resizeEl.onmousedown = function (e) {
    resizeEl.classList.add('active');
    const startX = e.clientX;
    resizeEl.left = resizeEl.offsetLeft;
    document.onmousemove = function (e) {
      const endX = e.clientX;
      let moveLen = resizeEl.left + (endX - startX);
      if (moveLen < 380) moveLen = 380; // min width
      if (moveLen > 908) moveLen = 908; // max width
      resizeEl.style.left = moveLen + 'px';
      leftEl.style.width = moveLen + 'px';
      rightEl.style.width = (containerEl.clientWidth - moveLen - 10) + 'px';
    };
    document.onmouseup = function () {
      resizeEl.classList.remove('active');
      document.onmousemove = null;
      document.onmouseup = null;
    };
    return false;
  };
}

The date‑generation utilities use dayjs to build hierarchical structures for years, quarters, months, weeks, and days. Functions such as yearTitleDate , quarterTitleDate , monthTitleDate , weekTitleDate , and dayTitleDate return arrays of objects containing name , range , and unique IDs.

export function monthTitleDate(start, end) {
  const startYear = dayjs(start).year();
  const startMonth = dayjs(start).month() + 1;
  const endYear = dayjs(end).year();
  const endMonth = dayjs(end).month() + 1;
  // build month list with children days
  // ...
  return dates;
}

Helper functions generationMonths , generationDays , generationQuarters , isLeap , and timeInWeek create the low‑level date nodes, optionally inserting day or week granularity.

function generationMonths(year, start_num = 1, end_num = 13, isLeap = false, insert_days = true, week = false) {
  const months = [];
  for (let i = start_num; i < end_num; i++) {
    const obj = {
      name: `${i}月`,
      fullname: `${year}年${i}月`,
      range: [dayjs(`${year}-${i}-01`).format('YYYY-MM-DD'), dayjs(`${year}-${i}`).endOf('month').format('YYYY-MM-DD')],
      id: generateUUID()
    };
    if (insert_days) obj.children = generationDays(year, i, isLeap, week);
    months.push(obj);
  }
  return months;
}

Performance considerations for large data sets suggest using virtual scrolling to render only visible rows, reducing DOM size and improving responsiveness.

Finally, the article mentions possible extensions such as drawing dependency lines with vue3-leaderline and handling massive datasets via virtual lists, concluding that the presented approach offers a practical way to build a customizable Gantt chart from scratch.

frontendJavaScriptVuedrag and dropGantt ChartResizable Panels
360 Quality & Efficiency
Written by

360 Quality & Efficiency

360 Quality & Efficiency focuses on seamlessly integrating quality and efficiency in R&D, sharing 360’s internal best practices with industry peers to foster collaboration among Chinese enterprises and drive greater efficiency value.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.