Calendar Gantt
by Rolf Friedrich
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kalender mit Events</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="calendar">
<!-- Hintergrund-Grid -->
<div class="background-grid">
<!-- 31 Tage für den Hintergrund -->
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<!-- Event-Container -->
<!-- Event-Container -->
<div class="event-container">
<!-- Mehrtägiges Event -->
<div
class="event event-overlap"
style="grid-column: 5 / span 5; grid-row: 1;"
data-start="5"
data-end="9"
data-id="5_9"
draggable="true"
>
<div class="event__title">Mehrtägiges Event</div>
<div class="event__time">Beginn: 8:00 Uhr</div>
<div class="event__description">Dies ist ein Büroevent für die Abteilung IT</div>
</div>
<!-- Einzeltermin -->
<div
class="event event-single"
style="grid-column: 10 / span 1; grid-row: 1;"
data-start="10"
data-end="10"
data-id="10_10"
draggable="true"
>
<div class="event__title">Einzeltermin</div>
<div class="event__time">10:00 - 10:30 Uhr</div>
<div class="event__description">Kurzmeeting mit der Geschäftsleitung</div>
</div>
</div>
<!-- Tage -->
<!-- 31 Tage für den Kalender -->
<div class="day" data-day="1">1</div>
<div class="day" data-day="2">2</div>
<div class="day"...
CSS
/* Verhindert Textmarkierung */
body {
user-select: none;
}
/* Dragging-Effekt */
.dragging {
opacity: 0.7; /* Leicht transparent */
cursor: grabbing;
position: absolute; /* Schwebt über dem Grid */
z-index: 10; /* Höher als andere Elemente */
}
/* Kalender-Container mit Scrollbar */
.calendar-container {
overflow-x: auto; /* Horizontal scrollbar */
overflow-y: hidden; /* Kein vertikales Scrollen */
width: 100%; /* Passt sich an den Bildschirm an */
}
/* Gesamtes Kalender-Grid */
.calendar {
display: grid;
grid-template-columns: repeat(31, minmax(3rem, 1fr)); /* 31 Tage in einer Reihe */
grid-auto-rows: minmax(3rem, auto); /* Mindesthöhe pro Zeile */
gap: 0; /* Abstand zwischen den Zellen */
position: relative;
width: max-content; /* Passt sich der Breite an */
}
/* Einzelne Tage */
.day {
background: #f0f0f0;
border-left: 1px solid #ccc;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
text-align: center;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
position: relative;
min-width: 3rem;
min-height: 3rem; /* Mindesthöhe */
z-index: 2;
pointer-events: auto; /* Aktiviert Mausereignisse */
}
/* Hintergrund-Grid */
.background-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(31, minmax(3rem, 1fr)); /* 31 Tage */
grid-auto-rows: minmax(3rem, auto);
gap: 0;
z-index: 0;
pointer-events: none; /* Hintergrund ignoriert Mausereignisse */
}
.background-grid div {
background: #f9f9f9;
border: 1px dashed #ddd;
}
/* Container für Events */
.event-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(31, minmax(3rem, 1fr)); /* 31 Tage */
grid-auto-rows: 3rem; /* Dynamische Zeilenhöhe */
gap: 0;
z-index: 3;
pointer-events: none; /* Events-Container ignoriert Mausereignisse */
}
/* Mehrtägige...
JavaScript
document.addEventListener('DOMContentLoaded', () => {
const calendar = document.querySelector('.calendar');
const eventContainer = document.querySelector('.event-container');
const days = [...calendar.querySelectorAll('.day')];
let isSelecting = false;
let startDay = null;
let endDay = null;
let draggedEvent = null;
let offsetX = 0;
let originalStart = 0;
let originalEnd = 0;
let totalDays = days.length;
/**
* Passt die Höhe der gesamten Kalenderzeile und der Tage an die maximale Anzahl von Terminen an.
*/
function adjustRowHeight() {
const maxRows = [...eventContainer.children].reduce((max, event) => {
const row = parseInt(event.style.gridRow) || 1;
return Math.max(max, row);
}, 1);
calendar.style.gridAutoRows = `${maxRows * 3}rem`;
days.forEach(day => {
day.style.minHeight = `${maxRows * 3}rem`;
});
}
/**
* Initialisiert Drag-and-Drop für alle Events.
*/
function initializeEvents() {
const events = document.querySelectorAll('.event');
events.forEach(event => {
event.setAttribute('draggable', 'true');
if (!event.dataset.start || !event.dataset.end) {
const gridColumn = event.style.gridColumn.split('/');
event.dataset.start = parseInt(gridColumn[0]) || 1;
event.dataset.end = (parseInt(gridColumn[1]) || 1) + parseInt(gridColumn[0]) - 1;
}
event.addEventListener('dragstart', (e) => {
draggedEvent = e.target;
const rect = draggedEvent.getBoundingClientRect();
offsetX = e.clientX - rect.left;
originalStart = parseInt(draggedEvent.dataset.start, 10);
originalEnd = parseInt(draggedEvent.dataset.end, 10);
e.dataTransfer.setDragImage(new Image(), 0, 0);
draggedEvent.classList.add('dragging');
});
event.addEventListener('dragend', () => {
if (draggedEvent) {
...