Dokumentation/Leitfäden

Achsen, Grenzen und Raster

Sperre Drags auf eine Achse, halte sie in einer Box, lass sie an einem Raster oder an eigenen Hilfslinien einrasten – für ein Element, eine Liste oder einen ganzen Teil der Seite.

Diese Seite wurde von KI übersetzt und kann Ungenauigkeiten enthalten. Englisches Original lesen

Ein Drag lässt sich auf einer Achse halten, in einer Box halten, an einem Raster einrasten oder durch eine eigene Regel beugen. Die Regeln sind Modifier: Sie passen in jedem Frame den Versatz des Drags an. Setz sie an einem ziehbaren Element, an einer Liste oder an einem Teil der Seite, und das Overlay, die Drop-Ziele und der Drop folgen ihnen.

Eine Achse sperren

Clips auf einer Zeitleiste gleiten entlang ihrer Spur. Schalte die Sperre aus, und sie können auch die Spur wechseln.

Live

Demo wird geladen…

<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Timeline from './Timeline.vue';
</script>

<template>
  <DnDProvider>
    <Timeline />
  </DnDProvider>
</template>
TimelineClip.vue
const { isDragging } = makeDraggable(el, {
  id: () => props.clip.id,
  axis: () => (props.locked ? 'x' : undefined),
});

axis: 'x' bewegt nur horizontal, 'y' nur vertikal. makeDraggable, makeSortableList, makeSortableTree, makeSortableItem und makeDragArea nehmen es alle:

makeSortableList(listRef, { items: tracks, axis: 'y' });
  • Die Sperre läuft nach jedem Modifier, sie hält also, was auch immer diese tun: Ein Einrasten oder eine Grenze kann das Element nicht von seiner Linie schieben.
  • Die Linie liegt im Inhalt, nicht auf dem Bildschirm: Sie bewegt sich mit der Liste (oder dem Elternelement), in der der Drag gestartet ist. Scrollst du die Seite mitten im Drag, bleibt das Element in seiner Zeile.
  • Die eigene axis eines Elements gewinnt gegen die seiner Liste oder seines Baums, und die eines ziehbaren Elements (oder seiner Liste) gegen die seines Drag-Bereichs.
  • Sie wird beim Start eines Drags gelesen, ein Getter kann sie also umschalten, wie oben.
  • Ein auf 'y' gesperrter Baum verschachtelt trotzdem: Die Tiefe kommt dann vom Halten über der Mitte einer Zeile.
Nimm lieber axis als die Modifier restrictToVerticalAxis und restrictToHorizontalAxis. Sie tun dasselbe, laufen aber als gewöhnliche Modifier mitten in der Kette, und ein Modifier danach (ein Raster, das auf beiden Achsen einrastet) kann das Element wieder von der Achse bewegen.

Modifier

Ein Modifier passt in jedem Frame den Versatz des Drags an. Sein Ergebnis bewegt das Overlay und ist der Punkt, an dem Ziele gesucht werden, also sortiert eine Liste nach der gesperrten, begrenzten oder eingerasteten Position, und die position des Drops ist dort, wo die Box gelandet ist.

ModifierWas er tut
restrictToParentHält das Overlay in dem Container, in dem der Drag gestartet ist: der sortierbaren Liste oder dem Elternelement eines einfachen ziehbaren Elements.
restrictToElement(target)Hält das Overlay in einem Element: einem Template-Ref, einem Getter oder dem Element selbst.
restrictToWindowEdgesHält das Overlay im Fenster.
restrictToFirstScrollableAncestorHält das Overlay im ersten scrollbaren Vorfahren des gezogenen Elements.
snapToGrid(size)Lässt das Overlay an einem Raster einrasten (unten).
snapCenterToCursorZentriert das Overlay unter dem Zeiger, statt den Greifversatz zu behalten.
restrictToVerticalAxisBewegt nur vertikal. Nimm lieber axis: 'y'.
restrictToHorizontalAxisBewegt nur horizontal. Nimm lieber axis: 'x'.

modifiers ist eine Option des Providers, der Drag-Bereiche, der Listen und Bäume sowie der ziehbaren und sortierbaren Elemente. Alle außer der des Providers nehmen auch ein Ref oder einen Getter, gelesen beim Start des Drags.

Drinnen halten

Die eigene Ansicht in einem Videocall bleibt auf dem Bildschirm. Schalte die Grenze um und ziehe sie weit hinaus:

Live

Demo wird geladen…

<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import CallScreen from './CallScreen.vue';
</script>

<template>
  <DnDProvider>
    <CallScreen />
  </DnDProvider>
</template>
SelfView.vue
const { isDragging } = makeDraggable(el, {
  id: 'self-view',
  modifiers: () => props.modifiers, // [restrictToParent], [restrictToWindowEdges] or []
});
  • restrictToParent hält sie auf dem Bildschirm, ihrem Elternelement. Bei einem sortierbaren Element ist das „Elternelement“ seine Liste, wo auch immer die Liste im DOM sitzt.
  • restrictToWindowEdges lässt sie den Bildschirm verlassen, aber nicht das Fenster. Außerhalb des Bildschirms abgelegt, ist sie auf keinem Drop-Ziel, also fliegt sie zurück.
  • Ohne Grenze geht sie überallhin.

Eine Grenze hält das Overlay drinnen, in der Größe, die es hat: Mit overlayFit: 'slot' wird eine Box, die über einer Liste breiterer Elemente wächst, in ihrer neuen Größe drinnen gehalten.

Am Raster einrasten

Die Knoten eines Flussdiagramms rasten am Raster der Zeichenfläche ein. Schalte die Zellgröße und das Gleiten um:

Live

Demo wird geladen…

<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Flowchart from './Flowchart.vue';
</script>

<template>
  <DnDProvider>
    <Flowchart />
  </DnDProvider>
</template>
FlowNode.vue
const { isDragging } = makeDraggable(el, {
  id: () => props.node.id,
  modifiers: () => [snapToGrid({ x: props.grid, y: props.grid, smooth: props.smooth })],
});

snapToGrid(24) rastet beide Achsen auf 24-px-Zellen ein. Ein Objekt legt mehr fest:

OptionBedeutung
x, yDie Zellgröße in Pixeln, pro Achse. Lass eine weg, um nur die andere einrasten zu lassen: { y: 15 } rastet einen Termin auf Viertelstunden ein und lässt ihn frei über die Tage wandern.
origin'container' (Standard): Die linke obere Ecke des Overlays landet auf Rasterlinien, die von der linken oberen Ecke des Containers aus gezogen sind – der sortierbaren Liste oder dem Elternelement eines einfachen ziehbaren Elements. 'delta': Der Versatz selbst wird gerundet, ein Element, das neben dem Raster gestartet ist, bleibt also neben dem Raster und bewegt sich um ganze Zellen von dort, wo es war. Nimm es für frei platzierte Elemente (eine freie Leinwand), die ihre eigene Ausrichtung behalten sollen.
smoothtrue oder { duration, easing }: Das Overlay gleitet zwischen den Zellen (standardmäßig 80 ms). Ziele nutzen die eingerastete Position sofort.

Zeichne bei origin: 'container' das Raster ab der Border-Box des Containers: Die Zeichenfläche oben hat ihren Rahmen als box-shadow, nicht als border, also beginnen ihre Linien direkt an ihrem Rand.

Ein eigener Modifier

Boxen auf einer Folie springen auf die Kanten und Mitten der anderen, wenn sie nahe kommen, und pinkfarbene Hilfslinien zeigen die Linien, auf denen sie sitzen:

Live

Demo wird geladen…

<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Slide from './Slide.vue';
</script>

<template>
  <DnDProvider>
    <Slide />
  </DnDProvider>
</template>
Slide.vue
import type { Modifier } from '@vue-dnd-kit/core';

/** Near a line, the box jumps onto it. */
const magnet: Modifier = ({ transform, overlayRect, containerRect, active }) => {
  if (!containerRect) return transform;
  const id = String(active.id);
  const left = overlayRect.left - containerRect.left;
  const top = overlayRect.top - containerRect.top;
  return {
    x: transform.x + pull(left, overlayRect.width, lines(id, 'x')),
    y: transform.y + pull(top, overlayRect.height, lines(id, 'y')),
  };
};

// In order: the magnet pulls, then the slide's edges hold.
const modifiers = [magnet, restrictToParent];

Ein Modifier bekommt transform, den Versatz zum Start des Drags, so wie ihn die Modifier davor hinterlassen haben, und gibt einen neuen zurück. Er bekommt außerdem, was er zur Berechnung brauchen kann:

ArgumentWas es ist
transformDer bisherige Versatz.
overlayRectDie Overlay-Box, um transform verschoben, in der Größe, die sie jetzt hat.
activeRectDas gezogene Element beim Start des Drags.
containerRectDer Container, in dem der Drag gestartet ist (die sortierbare Liste oder das Elternelement), dort, wo er jetzt ist: Er folgt dem Scrollen.
windowRectDas Fenster.
originDer Zeiger beim Start des Drags.
active, overDer Drag und das Ziel darunter.

Modifier laufen in jedem Frame: Halte sie schnell und frei von Seiteneffekten. Die Hilfslinien oben werden aus dem delta des Drags im onDragMove eines Monitors gezeichnet, das der Versatz nach allen Modifiern ist. Typen unter Modifier.

Die Reihenfolge

Modifier von mehreren Stellen laufen nacheinander, jeder auf dem Ergebnis des vorherigen:

  1. die modifiers des Providers;
  2. die modifiers des Drag-Bereichs, in dem der Drag gestartet ist;
  3. die eigenen modifiers des gezogenen Elements oder, wenn es keine hat, die seiner Liste oder seines Baums;
  4. restrictToArea des Drag-Bereichs;
  5. die axis-Sperre.

Innerhalb eines Arrays laufen sie der Reihe nach, setz also Einrasten und Magnete vor Grenzen: Eine Grenze am Schluss hält die Box drinnen, was auch immer die anderen getan haben. Die Folie oben führt [magnet, restrictToParent] aus; andersherum könnte der Magnet eine Box, die am Rand der Folie gehalten wird, ein paar Pixel darüber hinausziehen.

Die modifiers eines Elements ersetzen die seiner Liste; sie kommen nicht hinzu. Die Grenzen des Bereichs kommen nach dem Einrasten der Schritte 1–3, ein eingerastetes Element bleibt also trotzdem drinnen, und die Achsensperre kommt ganz zum Schluss.

Begrenzte Listen behalten ihre Enden

Ein Ebenen-Panel: Zeilen bewegen sich nur hoch und runter und verlassen das Panel nie. Ziehe die oberste Zeile ganz nach unten:

Live

Demo wird geladen…

<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import Layers from './Layers.vue';
</script>

<template>
  <DnDProvider>
    <Layers />
  </DnDProvider>
</template>
Layers.vue
const { items } = makeSortableList(list, { items: layers, axis: 'y', modifiers: [restrictToParent] });

Eine Grenze hält das Overlay am Rand der Box, während der Zeiger weitergeht. Sobald der Zeiger das Overlay entlang der Achse der Liste verlassen hat, sortiert die Liste stattdessen nach dem Zeiger, also bleiben der erste und der letzte Platz erreichbar, wie auch immer du das Element hältst. Raster sortieren weiter nach dem Overlay: Am Rand gehalten, deckt es die Randzelle schon ab.

Regeln für einen Teil der Seite

Zwei Bereiche, zwei Regelsätze. Sticker in Layout rasten an dessen Raster ein und können es nicht verlassen; Sticker in Sketch gehen überallhin, auch in Layout. Ein Drag behält die Regeln des Bereichs, in dem er gestartet ist, und der Bereich, dessen Regeln gelten, leuchtet auf.

Live

Demo wird geladen…

<script setup lang="ts">
import { ref } from 'vue';
import { DnDProvider, type Coordinates, type Key } from '@vue-dnd-kit/core';
import DragArea from './DragArea.vue';

const stickers = ref([
  { id: 'logo', text: 'Logo', color: '#42b883', area: 'sketch', x: 20, y: 24 },
  { id: 'photo', text: 'Photo', color: '#3b82f6', area: 'sketch', x: 110, y: 96 },
  { id: 'quote', text: 'Quote', color: '#ec4899', area: 'sketch', x: 36, y: 150 },
  { id: 'title', text: 'Title', color: '#f59e0b', area: 'layout', x: 24, y: 24 },
  { id: 'button', text: 'Button', color: '#a855f7', area: 'layout', x: 120, y: 144 },
]);

const place = (area: string, id: Key, at: Coordinates) => {
  const sticker = stickers.value.find((item) => item.id === id);
  if (!sticker) return;
  sticker.area = area;
  sticker.x = Math.round(at.x);
  sticker.y = Math.round(at.y);
};
</script>

<template>
  <DnDProvider>
    <div class="studio">
      <DragArea
        id="sketch"
        title="Sketch"
        rules="no rules"
        :stickers="stickers.filter((sticker) => sticker.area === 'sketch')"
        @place="place"
      />
      <DragArea
        id="layout"
        title="Layout"
        rules="snapToGrid(24) · restrictToArea"
        strict
        :stickers="stickers.filter((sticker) => sticker.area === 'layout')"
        @place="place"
      />
    </div>
  </DnDProvider>
</template>

<style scoped>
.studio {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
}
</style>
DragArea.vue
<script setup lang="ts">
const area = useTemplateRef<HTMLElement>('area');
const { isDragging } = makeDragArea(area, {
  modifiers: () => (props.strict ? [snapToGrid(24)] : []),
  restrictToArea: () => props.strict,
});
</script>

<template>
  <div ref="area" class="area" :class="{ busy: isDragging }">
    <Sticker v-for="sticker in stickers" :key="sticker.id" :sticker="sticker" />
  </div>
</template>

makeDragArea legt Regeln für die Drags fest, die in seinem Element starten – einfache ziehbare Elemente und sortierbare Elemente gleichermaßen, die dann keine eigenen Regeln brauchen. Behalte einen DnDProvider für die ganze App und gib einem Board, einer Zeichenfläche oder einem Feed einen eigenen Bereich.

OptionWas sie tut
axisSperrt die Drags auf eine Achse. Die eigene axis eines ziehbaren Elements oder die seiner Liste gewinnt.
modifiersLaufen nach denen des Providers und vor den eigenen des ziehbaren Elements.
restrictToAreatrue: Die Drags bleiben im Element des Bereichs.
autoScrollAuto-Scroll des Scroll-Containers des Bereichs (das Element oder sein nächster scrollbarer Vorfahr). false schaltet ihn dort ab.
activationAktivierung je nach Zeigerart, etwa ein längeres Touch-Drücken in einem scrollenden Feed. Die eigene eines ziehbaren Elements gewinnt; die des Providers kommt zuletzt.
disabledIm Bereich startet kein Drag, auch nicht in den Bereichen darin. Seine ziehbaren Elemente melden aria-disabled.

Jede Option nimmt einen Wert, ein Ref oder einen Getter, gelesen, wenn ein Drücken oder ein Drag startet. Der Bereich gibt isDragging zurück (ein Drag, der hier gestartet ist, läuft, wie der leuchtende Rahmen oben zeigt) und isAutoScrolling (Auto-Scroll bewegt seinen Container). Vollständige Referenz: makeDragArea.

Wie Bereiche zusammenspielen

  • Ein Drag folgt dem Bereich, in dem er gestartet ist, auch nachdem er ihn verlassen hat. Über einen Bereich zu fahren ändert nichts, außer an seinem autoScroll: Das gilt für jeden Drag, der seinen Container scrollt.
  • Verschachtelte Bereiche verschmelzen nicht: Der nächstgelegene gewinnt. disabled ist die Ausnahme und wird vererbt: Ein abgeschalteter Bereich schaltet auch die Bereiche darin ab.
  • Kollisionserkennung, Tastaturtasten und Ansagen bleiben providerweit.
Warum kein zweiter DnDProvider um das Board? Provider sind unabhängig: Ein Drag wechselt nie von einem zum anderen, die Elemente des Boards könnten es also nie verlassen. Verschachtle einen Provider nur, wenn ein Teil der Seite eigene providerweite Einstellungen braucht und seine Elemente darin bleiben. Siehe Der Provider.

Dort lassen, wo es abgelegt wird

Die Zeitleiste, der Call, das Flussdiagramm, die Folie und die Bereiche oben lassen jedes Element dort, wo es abgelegt wurde. Jedes Drag-Event trägt position: die linke obere Ecke der gezogenen Box im Ziel, ab dessen Padding-Kante und mit dessen Scroll – das left und top, das ein absolut positioniertes Element genau dorthin setzt. Es kommt nach den Modifiern, also landet ein eingerasteter Knoten auf dem Raster.

Flowchart.vue
makeDroppable(canvas, { id: 'canvas' });
useDnDMonitor({
  onDragEnd({ active, over, position }) {
    const node = nodes.value.find((item) => item.id === active.id);
    if (!node || over?.id !== 'canvas' || !position) return;
    node.x = Math.round(position.x);
    node.y = Math.round(position.y);
  },
});

Mehr zu freiem Ziehen und Drop-Zielen unter Drag and Drop.