InteraktionGilt fürSuperScheduler Pro
Stunden, Minuten, Tage und Zoom
Wählen Sie die Zellgröße mit scale ('Hour', 'Day', 'Week', 'Month', 'Year' oder 'CellDuration' mit cellDuration in Minuten), legen Sie mit cellWidth die Pixel pro Zelle und mit days die Länge fest, und beschreiben Sie die Kopfzeilen mit timeHeaders. Nächte und Wochenenden blenden Sie mit businessBeginsHour, businessEndsHour und showNonBusiness={false} aus. Für den Zoom listen Sie zoomLevels auf und wechseln mit control.zoom.setActive, animateTo oder step zwischen ihnen; Pinch-Geste und Strg/Cmd + Mausrad sind standardmäßig aktiv.
Die Zeitachse von SuperScheduler Pro wird durch eine Handvoll Optionen definiert: was eine Zelle darstellt (scale), wie breit sie ist (cellWidth), wo die Zeitleiste beginnt und wie lang sie ist (startDate, days) und wie die Kopfzeilen sie beschriften (timeHeaders). Zoom ist eine Liste solcher Konfigurationen, zoomLevels, die Nutzer per Geste und Ihr Code über control.zoom erreichen.
Dieser Leitfaden führt von festen Skalen bis zum stufenlosen Zoom. Lite hat eine feste Tagesachse; alles andere hier erfordert Pro.
Skala, Zelldauer und Breite
scale | Eine Zelle ist | Typische Verwendung |
|---|---|---|
'Minute' | 1 Minute | Sendeabläufe, Laborläufe |
'CellDuration' | cellDuration Minuten (Standard 60) | Slots zu 5, 15 oder 30 Minuten; Schichten zu 240 Minuten |
'Hour' | 1 Stunde | Werkstätten, Besprechungsräume, Teams |
'Day' | 1 Kalendertag | Hotels, Vermietung, Personalplanung |
'Week' | 1 Kalenderwoche, beginnend mit weekStarts | Projekte, Kampagnen |
'Month' | 1 Kalendermonat | Lange Einsätze, Kapazitätspläne |
'Year' | 1 Kalenderjahr | Mehrjährige Übersichten |
'Manual' | Die Zellen, die Sie in timeline auflisten | Unregelmäßige Zeiträume |
cellWidth ist in Pixeln pro Zelle der aktuellen Skala angegeben (Standard 40): 44 bedeutet auf einer Tagesachse 44 Pixel pro Tag, auf einer Stundenachse aber 44 Pixel pro Stunde. startDate (Standard heute, auf Mitternacht gekürzt) und days legen die Länge der Zeitleiste fest.
Einige typische Konfigurationen:
import type { SchedulerProps } from 'super-scheduler'
// A month of day cells: the classic booking chart.
export const monthOfDays = {
scale: 'Day',
startDate: '2026-10-01',
days: 31,
cellWidth: 44,
timeHeaders: [{ groupBy: 'Month' }, { groupBy: 'Day', format: 'ddd d' }],
} satisfies SchedulerProps
// One working day in 15-minute cells; nights are removed from the axis.
export const quarterHours = {
scale: 'CellDuration',
cellDuration: 15,
startDate: '2026-10-12',
days: 1,
cellWidth: 36,
businessBeginsHour: 7,
businessEndsHour: 19,
showNonBusiness: false,
timeHeaders: [
{ groupBy: 'Hour', format: 'HH:mm' },
{ groupBy: 'Cell', format: 'mm' },
],
} satisfies SchedulerProps
// A work week of hours, Monday to Friday, with 12-hour labels.
export const workWeekOfHours = {
scale: 'Hour',
startDate: '2026-10-12',
days: 5,
cellWidth: 48,
timeFormat: 'Clock12Hours',
businessBeginsHour: 8,
businessEndsHour: 18,
showNonBusiness: false,
timeHeaders: [{ groupBy: 'Day', format: 'dddd d MMMM' }, { groupBy: 'Hour' }],
} satisfies SchedulerProps
// A year in month cells, for long-running assignments.
export const yearOfMonths = {
scale: 'Month',
startDate: '2026-01-01',
days: 365,
cellWidth: 90,
timeHeaders: [{ groupBy: 'Year' }, { groupBy: 'Month', format: 'MMM' }],
} satisfies SchedulerPropscellDuration legt auch das Standard-Einrasten fest: Bei 15-Minuten-Zellen rasten Verschiebungen, Dauer-Änderungen und Auswahlen auf Viertelstunden ein. Die Optionsfamilie snapToGrid schaltet das Einrasten pro Geste ab. Auf einer Tagesachse werden Ereignisse standardmäßig als ganze Zellen gezeichnet (useEventBoxes: 'Always'); setzen Sie useEventBoxes="Never", um sie zu ihren exakten Zeiten zu zeichnen, sodass ein Aufenthalt von 14:00 bis 11:00 Uhr innerhalb seiner Tageszellen beginnt und endet.
Zeitköpfe
timeHeaders listet die Kopfzeilen von oben nach unten. Jede Zeile gruppiert die Zeit nach einer Einheit und kann ein Beschriftungsformat (format) und eine Höhe (height) festlegen:
groupBy | Gruppiert nach |
|---|---|
'Year', 'Quarter', 'Month', 'Week', 'Day', 'Hour', 'Minute' | Dieser Kalendereinheit |
'Cell' | Eine Beschriftung pro Zelle |
'Default' | cellGroupBy (Standard 'Day') |
'None' | Eine Beschriftung für die ganze Zeile |
Der Standard ist [{ groupBy: 'Default' }, { groupBy: 'Cell' }]: Tage über Zellen. Jede Kopfzeile ist headerHeight Pixel hoch (Standard 30), sofern sie keine eigene height festlegt.
Format-Tokens
Formate verwenden diese Tokens; jedes andere Zeichen wird unverändert ausgegeben. Die Beispiele formatieren 2026-10-05T14:30:00 mit der Locale en-us.
| Token | Ausgabe | Token | Ausgabe |
|---|---|---|---|
yyyy | 2026 | HH | 14 |
yy | 26 | H | 14 |
MMMM | October | hh | 02 |
MMM | Oct | h | 2 |
MM | 10 | mm | 30 |
M | 10 | m | 30 |
dddd | Monday | ss, s | 00, 0 |
ddd | Mo | tt | PM |
dd, d | 05, 5 | %d | 5 |
Namen folgen der locale des Planers (Standard 'en-us'): 'dddd d MMMM' ergibt mit locale="es-es" „lunes 5 octubre“. In mehreren Locales ist ddd eine Abkürzung aus einem oder zwei Buchstaben („Mo“, „L“); verwenden Sie dddd oder schreiben Sie Ihre eigene Beschriftung in onBeforeTimeHeaderRender, wenn Sie drei Buchstaben wollen. Beschriftungen, Styles, Tooltips und Areas der Köpfe lassen sich alle in diesem Hook anpassen.
12- oder 24-Stunden-Beschriftungen
timeFormat steuert die Standardbeschriftung der Stunden: 'Auto' (Standard) folgt der Locale (12 Stunden für en-us, 24 Stunden für die meisten europäischen Locales), 'Clock12Hours' und 'Clock24Hours' erzwingen eines davon. Ein explizites format an einer Kopfzeile hat immer Vorrang: 'h:mm tt' für 12-Stunden-Beschriftungen, 'HH:mm' für 24-Stunden-Beschriftungen. Ein anderes Uhrzeitformat ändert nur die Beschriftungen; die Zeiten der Ereignisse verschieben sich nie.
Geschäftszeiten und ausgeblendete Zeit
Die Geschäftszeit wird durch businessBeginsHour (Standard 9), businessEndsHour (Standard 18; 0 bedeutet Mitternacht am Ende des Tages) und businessWeekends (Standard false) definiert. Mit showNonBusiness auf dem Standardwert true werden Zellen der Nicht-Geschäftszeit schattiert. Mit showNonBusiness={false} werden sie aus der Achse entfernt:
- Auf einer Tagesachse verschwinden die Wochenendtage (aus 14 Tagen werden 10 Spalten).
- Auf einer Achse innerhalb des Tages verschwinden die Stunden außerhalb der Geschäftszeit; eine Arbeitswoche in Stunden zeigt dann pro Tag nur 08:00 bis 18:00 Uhr.
Für alles Speziellere wird onIncludeTimeCell beim Aufbau der Zeitleiste für jede Kandidatenzelle aufgerufen: Setzen Sie args.cell.visible = false, um eine Zelle wegzulassen, oder args.cell.width, um ihre Breite zu ändern. scale: 'Manual' mit einem timeline-Array aus { start, end, width }-Zellen gibt Ihnen die volle Kontrolle.
Zoomstufen
Eine Zoomstufe ist ein benannter Satz von Optionen, die gemeinsam angewendet werden: typischerweise scale, cellDuration, cellWidth und timeHeaders. Definieren Sie die Stufenleiter einmal, auf Modulebene:
import type { SuperScheduler } from 'super-scheduler'
/**
* From the most detailed view to the widest. Each level is a set of options applied together;
* cellWidth is in px per cell of that level (per 15 minutes, per hour, per day, per week).
*/
export const ZOOM_LEVELS: SuperScheduler.ZoomLevel[] = [
{
id: 'quarter-hours',
properties: {
scale: 'CellDuration',
cellDuration: 15,
cellWidth: 40,
timeHeaders: [
{ groupBy: 'Day', format: 'dddd d MMMM' },
{ groupBy: 'Cell', format: 'HH:mm' },
],
},
},
{
id: 'hours',
properties: {
scale: 'Hour',
cellWidth: 56,
timeHeaders: [
{ groupBy: 'Day', format: 'dddd d MMMM' },
{ groupBy: 'Hour', format: 'HH:mm' },
],
},
},
{
id: 'days',
properties: {
scale: 'Day',
cellWidth: 80,
timeHeaders: [{ groupBy: 'Month' }, { groupBy: 'Day', format: 'ddd d' }],
},
},
{
id: 'weeks',
properties: {
scale: 'Week',
cellWidth: 120,
timeHeaders: [{ groupBy: 'Month' }, { groupBy: 'Week' }],
},
},
]
export type ZoomLevelId = 'quarter-hours' | 'hours' | 'days' | 'weeks'
export const ZOOM_LEVEL_IDS: readonly ZoomLevelId[] = ['quarter-hours', 'hours', 'days', 'weeks']Übergeben Sie sie als zoomLevels und wählen Sie die Anfangsstufe mit zoom (ein Index oder eine id). zoomPosition (standardmäßig 'left', sonst 'middle', 'right') bestimmt, welcher Teil des Viewports beim Wechsel der Stufe an seinem Platz bleibt.
Eine Eigenschaft kann auch eine Funktion des Ankerdatums sein, ({ date, level }) => value, zum Beispiel um das Jahr im Monatskopf nur rund um den Jahreswechsel anzuzeigen.
Während einer stufenlosen Geste wählt der Planer die Stufe, die der aktuellen Zeit pro Pixel am nächsten liegt, und wendet ihre Achse und ihre Köpfe an, sobald der Nutzer in sie hineinzoomt. Eigenschaften, die das Zeitfenster zurücksetzen würden, etwa days und startDate, gelten nur, wenn Ihr Code eine Stufe explizit auswählt. Für Gesten spielt die Reihenfolge des Arrays keine Rolle, denn sie messen jede Stufe.
Den Zoom aus Code ändern
control.zoom hat drei Methoden und eine Eigenschaft:
| Member | Was es tut |
|---|---|
setActive(level, position?, anchorDate?) | Wendet eine Stufe (Index oder ID) sofort an, einschließlich days und startDate |
animateTo(target, options?) | Animiert zu { level } oder zu einer freien { cellWidth }; gibt ein Promise zurück, das sich erfüllt, wenn die Animation zur Ruhe kommt |
step(delta, options?) | Bewegt sich um delta Positionen durch zoomLevels, in der Reihenfolge des Arrays und begrenzt auf dessen Enden; ohne zoomLevels multipliziert es die Zellbreite pro Schritt mit 1,6 |
active | Index der aktiven Stufe, -1, bevor eine Stufe angewendet wurde |
animateTo und step akzeptieren { duration, position, anchorDate }: duration in Millisekunden (Standard 300, 0 für keine Animation) und anchorDate als Datum, 'center' oder 'today', um diesen Moment an seinem Platz zu halten. Animationen erfolgen sofort, wenn der Nutzer reduzierte Bewegung bevorzugt, und bewirken nichts, während eine Zoom-Geste läuft. Eine unbekannte Stufen-ID wirft einen Fehler.
import { useCallback, useMemo, useState } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import { ZOOM_LEVELS, ZOOM_LEVEL_IDS, type ZoomLevelId } from './zoom-levels'
// Stable objects: a new one per render would be re-applied on every render.
const ZOOM_GESTURE: SuperScheduler.ZoomGestureOptions = {
// The default maximum (400 px per cell) is too narrow to cross from days into hours.
max: 1024,
}
interface Props {
readonly rooms: SuperScheduler.ResourceData[]
readonly bookings: SuperScheduler.EventData[]
}
export function ZoomablePlanning({ rooms, bookings }: Props) {
const { controlRef, control } = useSchedulerControl()
const [level, setLevel] = useState<ZoomLevelId>('days')
const owned = useMemo(() => bookings.slice(), [bookings])
// One React update when a gesture or an animation settles, never one per frame.
const onZoom = useCallback((args: SuperScheduler.ZoomArgs) => {
if (args.phase !== 'end') return
const id = ZOOM_LEVEL_IDS[args.level]
if (id !== undefined) setLevel(id)
}, [])
const show = (id: ZoomLevelId) =>
void control?.zoom.animateTo({ level: id }, { anchorDate: 'center' })
// step() walks the zoomLevels array in its order: here -1 is more detail, +1 a wider view.
const zoomIn = () => void control?.zoom.step(-1)
const zoomOut = () => void control?.zoom.step(1)
return (
<>
<div role="toolbar" aria-label="Zoom">
{ZOOM_LEVEL_IDS.map((id) => (
<button key={id} type="button" aria-pressed={level === id} onClick={() => show(id)}>
{id}
</button>
))}
<button type="button" aria-label="Zoom in" onClick={zoomIn}>
+
</button>
<button type="button" aria-label="Zoom out" onClick={zoomOut}>
−
</button>
</div>
<SuperSchedulerComponent
controlRef={controlRef}
startDate="2026-10-12"
days={14}
zoomLevels={ZOOM_LEVELS}
zoom="days"
zoomPosition="middle"
zoomGesture={ZOOM_GESTURE}
onZoom={onZoom}
resources={rooms}
events={owned}
/>
</>
)
}Sie sollten über der Planung vier Stufen-Buttons sowie Plus- und Minus-Buttons sehen. Ein Druck auf „hours“ animiert die Achse von Tagen zu Stunden um die Mitte der Ansicht, und der gedrückte Zustand folgt auch Pinch-Gesten, weil onZoom am Ende jedes Zoomvorgangs die Stufe meldet.
onZoom erhält phase ('start', 'change', 'end'), origin ('gesture' oder 'api'), level, cellWidth, scale, cellDuration, das Ankerdatum, den Beginn des Viewports und die Detailstufe. Gesten und animateTo melden jeden Frame; reagieren Sie auf phase === 'end', es sei denn, Sie aktualisieren pro Frame direkt das DOM. Den React-State aktualisieren Sie nie pro Frame.
Gesten
Zoom-Gesten sind standardmäßig aktiv: Strg oder Cmd mit dem Mausrad (was in Chrome, Edge und Firefox auch die Pinch-Geste auf dem Trackpad abdeckt), die Pinch-Geste auf dem Trackpad in Safari und das Zwei-Finger-Pinch auf Touchscreens. Der Zoom ist stufenlos und unter dem Zeiger verankert. Feinjustieren Sie ihn mit zoomGesture:
| Option | Standard | Bedeutung |
|---|---|---|
min | cellWidthMin (mindestens 1) | Kleinste Zellbreite in Pixeln |
max | 400 | Größte Zellbreite in Pixeln |
wheel | 'ctrl' | 'always' zoomt bei jeder vertikalen Mausradbewegung (Umschalt + Mausrad scrollt); false zoomt nie mit dem Mausrad |
pinch | true | Pinch-Geste auf dem Trackpad in Safari und auf Touchscreens |
sensitivity | 1 | Geschwindigkeitsfaktor |
scales | 'zoomLevels' | Wechselt zwischen Ihren Stufen; 'auto' verwendet eine Leiter aus Stunde, Tag, Woche, Monat; false behält die aktuelle Skala |
link | keiner | Planer mit derselben Link-ID zoomen gemeinsam |
zoomGesture={false} entfernt alle Gesten-Listener. Weil cellWidth pro Zelle gilt, braucht der Übergang von einer Tagesstufe zu einer Stundenstufe Platz: Ein Tag mit 400 Pixeln sind nur etwa 17 Pixel pro Stunde. Erhöhen Sie also max (das Beispiel verwendet 1024), wenn Ihre Leiter von Tagen bis in Stunden reicht.
keyboardOptions={{ zoomKeys: true }} ergänzt zusammen mit keyboardEnabled Strg/Cmd mit = oder + (step(1)), - (step(-1)) und 0 (zurück zur Anfangsstufe), solange der Fokus im Planer liegt.
Zoom-Widgets
super-scheduler/zoom-ui bietet drei optionale DOM-Widgets, die sich ohne React-Renderings aktualisieren:
createZoomHud(control, options): eine Anzeige im Raster, sichtbar während Gesten und 700 ms lang nach einem Zoom über die API;formatlegt ihren Text fest.createZoomSlider(control, container, options): ein natives Range-Input mit Tastaturunterstützung, logarithmischer oder linearer Skala und Rastpunkten an Ihren Zoomstufen oder an expliziten Breiten. Sein Wert ist in Pixeln pro Zelle angegeben; am besten passt er daher zu einer Achse mit einer einzigen Skala.createLodBadge(control, container, labels): zeigt an, ob sich die Ansicht im Detail-, Kompakt- oder Übersichtsmodus befindet.
import { useEffect, useRef } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SuperScheduler } from 'super-scheduler'
import { createLodBadge, createZoomHud, createZoomSlider } from 'super-scheduler/zoom-ui'
export function PlanningWithZoomWidgets({ rooms }: { rooms: SuperScheduler.ResourceData[] }) {
const { controlRef, control } = useSchedulerControl()
const toolbar = useRef<HTMLDivElement>(null)
// Widgets need a mounted control. The cleanup matters: Strict Mode mounts twice in development.
useEffect(() => {
const host = toolbar.current
if (control === null || host === null) return
// A pill inside the grid while zooming (no container needed).
const hud = createZoomHud(control, {
format: ({ cellWidth }) => `${Math.round(cellWidth)} px per day`,
})
// A native range input; its value is px per cell, so it suits a single-scale axis like this one.
const slider = createZoomSlider(control, host, {
min: 4,
max: 160,
scale: 'log',
label: 'Day width',
})
// Detail, Compact or Overview, following the level of detail.
const badge = createLodBadge(control, host)
return () => {
hud.dispose()
slider.dispose()
badge.dispose()
}
}, [control])
return (
<>
<div ref={toolbar} role="toolbar" aria-label="Zoom" />
<SuperSchedulerComponent
controlRef={controlRef}
startDate="2026-10-01"
days={120}
scale="Day"
cellWidth={44}
resources={rooms}
/>
</>
)
}Jedes Widget hat element und dispose(). Erzeugen Sie sie in einem Effect, der vom Control abhängt, und geben Sie sie in dessen Cleanup frei; das Freigeben des Controls entfernt sie ebenfalls.
Detailstufe
Wenn Nutzer weit herauszoomen, wäre es unlesbar und langsam, jede Beschriftung in voller Größe zu zeichnen. Die Detailstufe (lod, standardmäßig aktiv) passt das Rendering an den Platz auf dem Bildschirm an und ändert ab 40 Pixeln pro Tag nichts:
- Ereignisse passen sich unter 40 Pixeln pro Tag ihrer eigenen Breite an: voller Inhalt ab 80 Pixeln, eine Textzeile ab 66 Pixeln, darunter ein einfacher Block. Unter 8 Pixeln pro Tag werden Blöcke zu vollflächigen Füllungen und schmale Ereignisse zu dünnen Balken.
- Zellen zeigen ihren Inhalt (HTML, Text, Areas) ab 24 Pixeln pro Zelle. Unter 2 Pixeln pro Zelle gibt es überhaupt keine Zellelemente, und
onBeforeCellRenderwird nicht aufgerufen. - Rasterlinien halten mindestens 8 Pixel Abstand, die Schattierung von Wochenenden und Nicht-Geschäftszeit braucht 6 Pixel pro Tag, und Beschriftungen der Köpfe werden kürzer oder gröber, wenn sie nicht mehr passen.
Jeder Schwellenwert lässt sich über lod={{ ... }} ändern (zoomedOut, eventFull, eventText, eventSolid, cellContent, cellBackground, gridLines, shading, dayLabel, dayNumber, weekLabel, hysteresis), und lod={false} rendert auf jeder Zoomstufe wörtlich. Der aktuelle Zustand steht in control.levelOfDetail (level ist 'full', 'compact' oder 'overview') und wird als data-lod-Attribute an die Wurzel geschrieben, für Ihr CSS.
Termine in der PhysiotherapiepraxisEin Patient kann um 10 Uhr nicht. Finden Sie den nächsten Slot, der Pausen und Reinigung respektiert. Bühnenplanung für ein FestivalEin Linecheck ragt in den Puffer vor einem Auftritt. Auf fünf Minuten zoomen, kürzen und sehen, welche Räume und Crews am Act hängen. Planung einer MietwagenflotteEin Kleinwagen fällt am Abholtag aus. Die Miete auf ein anderes Auto legen, die Aufbereitung einhalten und sehen, wo die Flotte knapp wird.
Nächste Schritte
- Auf einer langen Zeitleiste zeigen, wo der Viewport steht: Minimap und Kennzahlen.
- Zoom und Scrollposition pro Nutzer speichern: Bereiche und gespeicherte Ansichten.
- Scrollen und Zoomen auch bei großen Datenmengen schnell halten: Performance und Virtualisierung.
Verwandte Beispiele
- FormaTermine in der PhysiotherapiepraxisEin Patient kann um 10 Uhr nicht. Finden Sie den nächsten Slot, der Pausen und Reinigung respektiert.
- Aurora LiveBühnenplanung für ein FestivalEin Linecheck ragt in den Puffer vor einem Auftritt. Auf fünf Minuten zoomen, kürzen und sehen, welche Räume und Crews am Act hängen.
- FleetlinePlanung einer MietwagenflotteEin Kleinwagen fällt am Abholtag aus. Die Miete auf ein anderes Auto legen, die Aufbereitung einhalten und sehen, wo die Flotte knapp wird.
- Court ClubPlatzbuchung im SportclubAm Vormittag reißt ein Padel-Netz. Kurs verschieben, Platz sperren und jeden Coach in seinem Dienst lassen.