Skip to main content
Back to examples
Popular features/Highlight position on drag
Loading live demo…from dhtmlxcode.com
Popular features

Highlight position on drag

Show a crosshair and live start and end dates alongside a task bar while it's dragged, so you can see exactly where it will land.

gantt.message({
	text: "<b>Drag</b> any blue task: its row, timeline range and intersected cells of the bottom scale are highlighted while live start/end dates update alongside.",
	expire: -1
});

gantt.config.date_format = "%Y-%m-%d";
gantt.config.row_height   = 34;
gantt.config.bar_height   = 22;
gantt.config.scale_height  = 52;

let draggedScaleRange = null;
let scaleRenderFrame = null;

function scale(unit, step, format, highlightOnDrag) {
	let config = {
		unit: unit,
		step: step,
		format: format
	};

	/* Only the bottom scale row receives the drag highlight callback. */
	if (highlightOnDrag) {
		config.css = function (date) {
			if (!draggedScaleRange) {
				return "";
			}

			let cellStart = new Date(date);
			let cellEnd = gantt.date.add(cellStart, step || 1, unit);

			return cellStart < draggedScaleRange.end && cellEnd > draggedScaleRange.start
				? "drag_scale_cell"
				: "";
		};
	}

	return config;
}

function setDraggedScaleRange(task) {
	if (!task || !task.start_date) {
		draggedScaleRange = null;
		return;
	}

	let start = new Date(task.start_date);
	let end = task.end_date ? new Date(task.end_date) : new Date(start);

	/* Milestones have no duration, but still belong to the cell containing them. */
	if (end <= start) {
		end = new Date(start.valueOf() + 1);
	}

	draggedScaleRange = { start: start, end: end };
}

function repaintScale() {
	if (scaleRenderFrame !== null) {
		return;
	}

	scaleRenderFrame = requestAnimationFrame(function () {
		scaleRenderFrame = null;
		gantt.render();
	});
}

let zoomNames = ["day", "week", "month", "quarter", "year"];

gantt.ext.zoom.init({
	levels: [
		{ name: "day", scale_height: 52, scales: [
			scale("month", 1, "%F %Y", false),
			scale("day", 1, "%j %D", true)
		] },
		{ name: "week", scale_height: 52, scales: [
			scale("month", 1, "%F %Y", false),
			scale("week", 1, "Week #%W", true)
		] },
		{ name: "month", scale_height: 52, scales: [
			scale("year", 1, "%Y", false),
			scale("month", 1, "%M", true)
		] },
		{ name: "quarter", scale_height: 52, scales: [
			scale("year", 1, "%Y", false),
			scale("quarter", 1, function (d) {
				return "Q" + (Math.floor(d.getMonth() / 3) + 1);
			}, true)
		] },
		{ name: "year", scale_height: 52, scales: [
			scale("year", 1, "%Y", true)
		] }
	],
	element: function () { return gantt.$root.querySelector(".gantt_task"); }
});

DHX.zoom.init(gantt, zoomNames, "zoom_seg");
function setZoom(name) { DHX.zoom.set(name); }

gantt.config.columns = [
	{ name: "text",     label: "Task", tree: true, width: 220, resize: true },
	{ name: "start_date", label: "Start", align: "center", width: 100, resize: true },
	{ name: "duration", label: "Days",  align: "center", width: 58, resize: true },
	{ name: "add",      width: 40, resize: true }
];

const formatDraggedTaskDate = gantt.date.date_to_str("%d %M");
gantt.templates.leftside_text = function (startDate, endDate, task) {
	if (task.id == gantt.getState().drag_id) {
		return '<span class="drag_date_label">' + formatDraggedTaskDate(startDate) + "</span>";
	}
};

gantt.templates.rightside_text = function (startDate, endDate, task) {
	if (task.id == gantt.getState().drag_id) {
		return '<span class="drag_date_label">' + formatDraggedTaskDate(endDate) + "</span>";
	}
};

gantt.attachEvent("onBeforeTaskDrag", function (id) {
	setDraggedScaleRange(gantt.getTask(id));
	repaintScale();
	return true;
});

gantt.attachEvent("onTaskDrag", function (id, mode, task) {
	setDraggedScaleRange(task);
	repaintScale();
});

gantt.attachEvent("onAfterTaskDrag", function () {
	draggedScaleRange = null;
	repaintScale();
});

gantt.attachEvent("onGanttReady", function () {
	function addElement(cfg) {
		let div = document.createElement("div");
		div.style.position = "absolute";
		div.className = cfg.css || "";
		div.style.left = cfg.left;
		div.style.width = cfg.width;
		div.style.height = cfg.height;
		div.style.lineHeight = cfg.height;
		div.style.top = cfg.top;
		if (cfg.html) div.innerHTML = cfg.html;
		if (cfg.wrapper) cfg.wrapper.appendChild(div);
		return div;
	}

	/* Cross-hair highlight. */
	gantt.addTaskLayer({
		renderer: function (task) {
			let s = gantt.getTaskPosition(task, task.start_date, task.end_date);
			let wrap = document.createElement("div");
			addElement({ css: "drag_move_vertical", left: s.left + "px", top: 0,
				width: s.width + "px", height: gantt.getVisibleTaskCount() * gantt.config.row_height + "px", wrapper: wrap });
			addElement({ css: "drag_move_horizontal", left: 0, top: s.top + "px",
				width: "100%", height: (gantt.config.row_height - 1) + "px", wrapper: wrap });
			return wrap;
		},
		filter: function (task) { return task.id == gantt.getState().drag_id; }
	});
});

/* --- data ---------------------------------------------------------------- */
let data = {
	tasks: [
		{ id: 1, text: "Nimbus 3.0 Launch", type: "project", open: true, progress: 0.35 },

		{ id: 100, text: "Discovery & Planning", type: "project", parent: 1, open: true, progress: 1 },
		{ id: 101, text: "Market research",        parent: 100, start_date: "2026-01-05", duration: 5, progress: 1 },
		{ id: 102, text: "Competitive analysis",   parent: 100, start_date: "2026-01-05", duration: 4, progress: 1 },
		{ id: 103, text: "Stakeholder interviews", parent: 100, start_date: "2026-01-12", duration: 4, progress: 1 },
		{ id: 104, text: "Requirements doc",       parent: 100, start_date: "2026-01-16", duration: 5, progress: 0.9 },
		{ id: 105, text: "Roadmap sign-off",       parent: 100, type: "milestone", start_date: "2026-01-23" },

		{ id: 200, text: "Design", type: "project", parent: 1, open: true, progress: 0.6 },
		{ id: 201, text: "UX wireframes",          parent: 200, start_date: "2026-01-12", duration: 6, progress: 0.8 },
		{ id: 202, text: "Information architecture", parent: 200, start_date: "2026-01-12", duration: 5, progress: 0.8 },
		{ id: 203, text: "Visual design system",   parent: 200, start_date: "2026-01-23", duration: 7, progress: 0.6 },
		{ id: 204, text: "Interactive prototype",  parent: 200, start_date: "2026-01-30", duration: 5, progress: 0.5 },
		{ id: 205, text: "Usability testing",      parent: 200, start_date: "2026-02-04", duration: 4, progress: 0.3 },
		{ id: 206, text: "Design review",          parent: 200, type: "milestone", start_date: "2026-02-10" },

		{ id: 300, text: "Development", type: "project", parent: 1, open: true, progress: 0.25 },
		{ id: 301, text: "Core API",               parent: 300, start_date: "2026-02-04", duration: 10, progress: 0.5 },
		{ id: 302, text: "Auth & billing",         parent: 300, start_date: "2026-02-04", duration: 8,  progress: 0.4 },
		{ id: 303, text: "Web app shell",          parent: 300, start_date: "2026-02-14", duration: 9,  progress: 0.3 },
		{ id: 304, text: "Mobile app",             parent: 300, start_date: "2026-02-23", duration: 10, progress: 0.2 },
		{ id: 305, text: "Third-party integrations", parent: 300, start_date: "2026-02-14", duration: 6, progress: 0.1 },
		{ id: 306, text: "Internal QA pass",       parent: 300, start_date: "2026-03-05", duration: 6,  progress: 0 },
		{ id: 307, text: "Code freeze",            parent: 300, type: "milestone", start_date: "2026-03-13" },

		{ id: 400, text: "Beta & Hardening", type: "project", parent: 1, open: true, progress: 0 },
		{ id: 401, text: "Closed beta rollout",    parent: 400, start_date: "2026-03-13", duration: 5, progress: 0 },
		{ id: 402, text: "Bug triage & fixes",     parent: 400, start_date: "2026-03-18", duration: 7, progress: 0 },
		{ id: 403, text: "Performance hardening",  parent: 400, start_date: "2026-03-25", duration: 5, progress: 0 },
		{ id: 404, text: "Accessibility audit",    parent: 400, start_date: "2026-03-27", duration: 4, progress: 0 },
		{ id: 405, text: "Beta sign-off",          parent: 400, type: "milestone", start_date: "2026-04-02" },

		{ id: 500, text: "Launch", type: "project", parent: 1, open: true, progress: 0 },
		{ id: 501, text: "Marketing assets",       parent: 500, start_date: "2026-02-14", duration: 5, progress: 0 },
		{ id: 502, text: "Press kit & outreach",   parent: 500, start_date: "2026-02-14", duration: 5, progress: 0 },
		{ id: 503, text: "Launch day runbook",     parent: 500, start_date: "2026-04-02", duration: 4, progress: 0 },
		{ id: 504, text: "Post-launch monitoring", parent: 500, start_date: "2026-04-06", duration: 4, progress: 0 },
		{ id: 505, text: "Public launch",          parent: 500, type: "milestone", start_date: "2026-04-12" }
	],
	links: [
		{ id: 1,  source: 101, target: 103, type: "0" },
		{ id: 2,  source: 102, target: 104, type: "0" },
		{ id: 3,  source: 103, target: 104, type: "0" },
		{ id: 4,  source: 104, target: 105, type: "0" },
		{ id: 5,  source: 101, target: 201, type: "0" },
		{ id: 6,  source: 102, target: 202, type: "0" },
		{ id: 7,  source: 201, target: 203, type: "0" },
		{ id: 8,  source: 202, target: 203, type: "0" },
		{ id: 9,  source: 105, target: 203, type: "0" },
		{ id: 10, source: 203, target: 204, type: "0" },
		{ id: 11, source: 204, target: 205, type: "0" },
		{ id: 12, source: 205, target: 206, type: "0" },
		{ id: 13, source: 203, target: 301, type: "0" },
		{ id: 14, source: 203, target: 302, type: "0" },
		{ id: 15, source: 301, target: 303, type: "0" },
		{ id: 16, source: 302, target: 303, type: "0" },
		{ id: 17, source: 303, target: 304, type: "0" },
		{ id: 18, source: 301, target: 305, type: "0" },
		{ id: 19, source: 304, target: 306, type: "0" },
		{ id: 20, source: 305, target: 306, type: "0" },
		{ id: 21, source: 306, target: 307, type: "0" },
		{ id: 22, source: 307, target: 401, type: "0" },
		{ id: 23, source: 401, target: 402, type: "0" },
		{ id: 24, source: 402, target: 403, type: "0" },
		{ id: 25, source: 402, target: 404, type: "0" },
		{ id: 26, source: 403, target: 405, type: "0" },
		{ id: 27, source: 404, target: 405, type: "0" },
		{ id: 28, source: 206, target: 501, type: "0" },
		{ id: 29, source: 206, target: 502, type: "0" },
		{ id: 30, source: 405, target: 503, type: "0" },
		{ id: 31, source: 502, target: 503, type: "0" },
		{ id: 32, source: 503, target: 504, type: "0" },
		{ id: 33, source: 504, target: 505, type: "0" }
	]
};

gantt.init("gantt_here");
gantt.parse(data);

gantt.ext.zoom.setLevel("week");
DHX.zoom.syncUI();
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Highlight position on drag: dhtmlxGantt</title>
	<script src="../../codebase/dhtmlxgantt.js?v=10.0.0"></script>
	<link rel="stylesheet" href="../../codebase/dhtmlxgantt.css?v=10.0.0">
	<link rel="stylesheet" href="../common/demo-controls/dhx_controls.css">
	<script src="../common/demo-controls/dhx_controls.js"></script>
	<link rel="preconnect" href="https://fonts.googleapis.com">
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
	<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
	<style>
		body { display: flex; flex-direction: column; }
		#gantt_here { flex: 1 1 auto; min-height: 0; width: 100%; }

		.gantt_task_line { border: none; border-radius: 7px; box-shadow: 0 1px 2px rgba(16,24,40,.18); }
		.gantt_task_line .gantt_task_progress { border-radius: 7px 0 0 7px; }
		.gantt_task_line.gantt_project { border-radius: 5px; }

		.drag_move_vertical, .drag_move_horizontal {
			background-color: rgba(37,99,235,.12);
			box-sizing: border-box;
		}
		.drag_move_vertical  { border-left: 1px solid rgba(37,99,235,.55); border-right: 1px solid rgba(37,99,235,.55); }
		.drag_move_horizontal { border-top: 1px solid rgba(37,99,235,.55); border-bottom: 1px solid rgba(37,99,235,.55); }

		.drag_date_label {
			position: relative;
			z-index: 2;
			display: inline-flex;
			align-items: center;
			justify-content: center;
			min-width: 72px;
			margin: 0 8px;
			padding: 0 8px;
			box-sizing: border-box;
			border-radius: 999px;
			background: var(--dhx-panel);
			box-shadow: 0 1px 2px rgba(16,24,40,.18);
			color: var(--dhx-info-ink);
			font: 600 12px var(--dhx-font);
			line-height: 18px;
			white-space: nowrap;
			pointer-events: none;
		}

		:root[data-gantt-theme="dark"] .drag_date_label { box-shadow: 0 1px 2px rgba(0,0,0,.5); }

		.gantt_side_content.gantt_link_crossing {
			margin-top: 0px;
		}

		.gantt_scale_cell.drag_scale_cell {
			background-color: rgba(37,99,235,.18);
			box-shadow: inset 0 -2px 0 rgba(37,99,235,.55);
		}

	</style>
</head>
<body>

<div class="dhx_header" style="justify-content:center">
	<div class="dhx_seg" id="zoom_seg">
		<button class="dhx_btn" data-zoom="day"     onclick="setZoom('day')">Day</button>
		<button class="dhx_btn" data-zoom="week"    onclick="setZoom('week')">Week</button>
		<button class="dhx_btn" data-zoom="month"   onclick="setZoom('month')">Month</button>
		<button class="dhx_btn" data-zoom="quarter" onclick="setZoom('quarter')">Quarter</button>
		<button class="dhx_btn" data-zoom="year"    onclick="setZoom('year')">Year</button>
	</div>
</div>

<div id="gantt_here"></div>


</body>
</html>