Documentation/Guides

Axes, limites et grilles

Verrouillez les glissements sur un axe, gardez-les dans une boîte, alignez-les sur une grille ou sur vos propres repères — pour un item, une liste ou toute une partie de la page.

Modifier cette page

Cette page a été traduite par IA et peut contenir des inexactitudes. Lire l'original en anglais

Un glissement peut être tenu sur un axe, gardé dans une boîte, aligné sur une grille, ou infléchi par une règle à vous. Ces règles sont des modificateurs : ils ajustent le décalage du glissement à chaque frame. Posez-les sur un élément déplaçable, sur une liste ou sur une partie de la page, et l'overlay, les cibles de dépôt et le dépôt les suivent tous.

Verrouiller un axe

Les clips d'une timeline glissent le long de leur piste. Désactivez le verrouillage, et ils peuvent aussi changer de piste.

Live

Chargement de la démo…

<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' ne déplace qu'horizontalement, 'y' que verticalement. makeDraggable, makeSortableList, makeSortableTree, makeSortableItem et makeDragArea l'acceptent tous :

makeSortableList(listRef, { items: tracks, axis: 'y' });
  • Le verrouillage s'exécute après tous les modificateurs : il tient donc quoi qu'ils fassent. Un alignement ou une limite ne peut pas faire sortir l'item de sa ligne.
  • La ligne est dans le contenu, pas à l'écran : elle se déplace avec la liste (ou le parent) où le glissement a commencé. Faites défiler la page en plein glissement et l'item reste sur sa ligne.
  • L'axis propre à un item l'emporte sur celui de sa liste ou de son arbre, et celui d'un élément déplaçable (ou de sa liste) l'emporte sur celui de sa zone de glissement.
  • Il est lu quand un glissement commence : un getter peut donc le basculer, comme ci-dessus.
  • Un arbre verrouillé sur 'y' s'imbrique toujours : la profondeur vient alors du survol du milieu d'une ligne.
Préférez axis aux modificateurs restrictToVerticalAxis et restrictToHorizontalAxis. Ils font la même chose, mais en tant que modificateurs ordinaires, ils s'exécutent au milieu de la chaîne, et un modificateur placé après eux (une grille qui aligne les deux axes) peut de nouveau faire sortir l'item de l'axe.

Modificateurs

Un modificateur ajuste le décalage du glissement à chaque frame. Son résultat déplace l'overlay et constitue le point où les cibles sont cherchées : une liste se trie donc selon la position verrouillée, limitée ou alignée, et la position du dépôt est l'endroit où la boîte a fini.

ModificateurCe qu'il fait
restrictToParentGarde l'overlay dans le conteneur où le glissement a commencé : la liste triable, ou l'élément parent d'un élément déplaçable simple.
restrictToElement(target)Garde l'overlay dans un élément : une ref de template, un getter ou l'élément lui-même.
restrictToWindowEdgesGarde l'overlay dans la fenêtre.
restrictToFirstScrollableAncestorGarde l'overlay dans le premier ancêtre défilant de l'élément glissé.
snapToGrid(size)Aligne l'overlay sur une grille (voir plus bas).
snapCenterToCursorCentre l'overlay sous le pointeur au lieu de garder le décalage de saisie.
restrictToVerticalAxisNe se déplace que verticalement. Préférez axis: 'y'.
restrictToHorizontalAxisNe se déplace qu'horizontalement. Préférez axis: 'x'.

modifiers est une option du provider, des zones de glissement, des listes et des arbres, ainsi que des éléments déplaçables et des items triables. Toutes, sauf celle du provider, acceptent aussi une ref ou un getter, lus quand le glissement commence.

Le garder à l'intérieur

La vignette de votre propre caméra dans un appel vidéo reste sur l'écran. Changez de limite et faites-la glisser loin au-dehors :

Live

Chargement de la démo…

<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 la retient sur l'écran, son élément parent. Pour un item triable, le « parent » est sa liste, où que la liste se trouve dans le DOM.
  • restrictToWindowEdges la laisse quitter l'écran, mais pas la fenêtre. Déposée hors de l'écran, elle n'est sur aucune cible de dépôt : elle revient donc à sa place.
  • Sans limite, elle va n'importe où.

Une limite garde l'overlay à l'intérieur, à la taille qu'il a : avec overlayFit: 'slot', une boîte qui grandit au-dessus d'une liste d'items plus larges est gardée à l'intérieur à sa nouvelle taille.

Aligner sur une grille

Les nœuds d'un diagramme de flux s'alignent sur la grille du canevas. Changez la taille des cellules et le glissement en douceur :

Live

Chargement de la démo…

<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) aligne les deux axes sur des cellules de 24 px. Un objet permet d'en régler davantage :

OptionSignification
x, yLa taille de la cellule en pixels, par axe. Omettez-en un pour n'aligner que l'autre : { y: 15 } aligne un événement sur les quarts d'heure et le laisse se déplacer librement d'un jour à l'autre.
origin'container' (par défaut) : le coin supérieur gauche de l'overlay se pose sur des lignes de grille tracées depuis le coin supérieur gauche du conteneur — la liste triable, ou le parent d'un élément déplaçable simple. 'delta' : c'est le décalage lui-même qui est arrondi, si bien qu'un item parti hors de la grille y reste et se déplace par cellules entières depuis l'endroit où il était. Choisissez-le pour des items placés n'importe où (un canevas libre) qui doivent garder leur propre alignement.
smoothtrue ou { duration, easing } : l'overlay glisse en douceur d'une cellule à l'autre (80 ms par défaut). Les cibles utilisent tout de suite la position alignée.

Avec origin: 'container', tracez la grille depuis la border box du conteneur : le canevas ci-dessus a son cadre en box-shadow, pas en border, si bien que ses lignes commencent tout au bord.

Votre propre modificateur

Les boîtes d'une diapositive sautent sur les bords et les centres des autres quand elles s'en approchent, et des repères roses montrent les lignes sur lesquelles elles se posent :

Live

Chargement de la démo…

<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];

Un modificateur reçoit transform, le décalage depuis le début du glissement tel que l'ont laissé les modificateurs précédents, et en renvoie un nouveau. Il reçoit aussi ce dont il peut avoir besoin pour le calculer :

ArgumentCe que c'est
transformLe décalage jusqu'ici.
overlayRectLa boîte de l'overlay déplacée de transform, à la taille qu'elle a maintenant.
activeRectL'élément glissé au début du glissement.
containerRectLe conteneur où le glissement a commencé (la liste triable, ou l'élément parent), là où il est maintenant : il suit le défilement.
windowRectLa fenêtre.
originLe pointeur au début du glissement.
active, overLe glissement et la cible en dessous.

Les modificateurs s'exécutent à chaque frame : gardez-les rapides et sans effets de bord. Les repères ci-dessus sont dessinés à partir du delta du glissement dans l'onDragMove d'un moniteur, qui est le décalage après tous les modificateurs. Les types se trouvent dans Modificateurs.

L'ordre

Les modificateurs venant de plusieurs endroits s'exécutent l'un après l'autre, chacun à partir du résultat du précédent :

  1. les modifiers du provider ;
  2. les modifiers de la zone de glissement où le glissement a commencé ;
  3. les modifiers propres à l'item glissé ou, s'il n'en a pas, ceux de sa liste ou de son arbre ;
  4. le restrictToArea de la zone de glissement ;
  5. le verrouillage d'axis.

Dans un même tableau, ils s'exécutent dans l'ordre : placez donc les alignements et les aimants avant les limites. Une limite en dernier garde la boîte à l'intérieur quoi qu'aient fait les autres. La diapositive ci-dessus exécute [magnet, restrictToParent] ; dans l'autre sens, l'aimant pourrait tirer une boîte retenue au bord de la diapositive de quelques pixels au-delà.

Les modifiers d'un item remplacent ceux de sa liste ; ils ne s'y ajoutent pas. Les limites de la zone viennent après les alignements des étapes 1 à 3 : un item aligné reste donc à l'intérieur, et le verrouillage d'axe vient tout à la fin.

Les listes limitées gardent leurs extrémités

Un panneau de calques : les lignes ne se déplacent que vers le haut et le bas, et ne quittent jamais le panneau. Faites glisser la ligne du haut tout en bas :

Live

Chargement de la démo…

<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] });

Une limite retient l'overlay au bord de la boîte pendant que le pointeur continue. Dès que le pointeur a quitté l'overlay le long de l'axe de la liste, la liste se trie plutôt selon le pointeur : la première et la dernière place restent ainsi accessibles, quelle que soit la façon dont vous tenez l'item. Les grilles continuent de se trier selon l'overlay : retenu au bord, il couvre déjà la cellule du bord.

Des règles pour une partie de la page

Deux zones, deux jeux de règles. Les stickers de Layout s'alignent sur sa grille et ne peuvent pas en sortir ; les stickers de Sketch vont n'importe où, y compris dans Layout. Un glissement garde les règles de la zone où il a commencé, et la zone dont les règles s'appliquent s'éclaire.

Live

Chargement de la démo…

<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 fixe des règles pour les glissements qui commencent à l'intérieur de son élément — éléments déplaçables simples comme items triables, qui n'ont alors besoin d'aucune règle à eux. Gardez un seul DnDProvider pour toute l'application, et donnez à un tableau, un canevas ou un fil sa propre zone.

OptionCe qu'elle fait
axisVerrouille les glissements sur un axe. L'axis propre à un élément déplaçable, ou celui de sa liste, l'emporte.
modifiersS'exécutent après ceux du provider et avant ceux de l'élément déplaçable.
restrictToAreatrue : les glissements restent dans l'élément de la zone.
autoScrollLe défilement automatique du conteneur défilant de la zone (l'élément, ou son ancêtre défilant le plus proche). false le désactive à cet endroit.
activationL'activation par type de pointeur, comme un appui tactile plus long dans un fil qui défile. Celle de l'élément déplaçable l'emporte ; celle du provider vient en dernier.
disabledAucun glissement ne démarre dans la zone, ni dans les zones qu'elle contient. Ses éléments déplaçables indiquent aria-disabled.

Chaque option accepte une valeur, une ref ou un getter, lus quand un appui ou un glissement commence. La zone renvoie isDragging (un glissement qui a commencé ici est en cours, comme le montre le cadre éclairé ci-dessus) et isAutoScrolling (le défilement automatique fait défiler son conteneur). Référence complète : makeDragArea.

Comment les zones se combinent

  • Un glissement suit la zone où il a commencé, même après en être sorti. Passer au-dessus d'une zone ne change rien, sauf son autoScroll : celui-ci s'applique à chaque glissement qui fait défiler son conteneur.
  • Les zones imbriquées ne fusionnent pas : la plus proche l'emporte. disabled est l'exception, et il est hérité : une zone désactivée désactive aussi les zones qu'elle contient.
  • La détection de collision, les touches du clavier et les annonces restent à l'échelle du provider.
Pourquoi pas un second DnDProvider autour du tableau ? Les providers sont indépendants : un glissement ne passe jamais de l'un à l'autre, et les items du tableau ne pourraient donc jamais en sortir. N'imbriquez un provider que quand une partie de la page a besoin de ses propres réglages à l'échelle du provider et que ses items y restent. Voir Le provider.

Le laisser là où il est déposé

La timeline, l'appel, l'diagramme de flux, la diapositive et les zones ci-dessus gardent tous chaque item là où il a été déposé. Chaque événement de glissement transporte position : le coin supérieur gauche de la boîte glissée à l'intérieur de la cible, depuis le bord de son padding et défilement compris — le left et le top qui placent un item positionné en absolu exactement à cet endroit. Elle est calculée après les modificateurs : un nœud aligné atterrit donc sur la grille.

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);
  },
});

Plus de détails sur le glissement libre et les cibles de dépôt dans Glisser-déposer.