Documentación/Guías

Ejes, límites y cuadrículas

Bloquea los arrastres en un eje, mantenlos dentro de una caja, ajústalos a una cuadrícula o a guías propias — para un ítem, una lista o toda una parte de la página.

Editar esta página

Esta página la ha traducido una IA y puede contener imprecisiones. Leer el original en inglés

Un arrastre puede sujetarse a un eje, mantenerse dentro de una caja, ajustarse a una cuadrícula o doblegarse a una regla tuya. Esas reglas son modificadores: ajustan el desplazamiento del arrastre en cada frame. Ponlos en un elemento arrastrable, en una lista o en una parte de la página, y el overlay, los destinos de drop y el drop los siguen.

Bloquear un eje

Los clips de una línea de tiempo se deslizan a lo largo de su pista. Quita el bloqueo y también podrán cambiar de pista.

Live

Cargando la demo…

<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' solo mueve en horizontal, 'y' solo en vertical. makeDraggable, makeSortableList, makeSortableTree, makeSortableItem y makeDragArea lo aceptan todos:

makeSortableList(listRef, { items: tracks, axis: 'y' });
  • El bloqueo se ejecuta después de todos los modificadores, así que se mantiene hagan lo que hagan: un ajuste o un límite no puede sacar el ítem de su línea.
  • La línea está en el contenido, no en la pantalla: se mueve con la lista (o el elemento padre) donde empezó el arrastre. Desplaza la página a mitad del arrastre y el ítem se queda en su fila.
  • El axis propio de un ítem gana al de su lista o su árbol, y el de un elemento arrastrable (o el de su lista) gana al de su área de arrastre.
  • Se lee cuando empieza un arrastre, así que un getter puede activarlo y desactivarlo, como arriba.
  • Un árbol bloqueado en 'y' sigue anidando: la profundidad sale entonces de pasar sobre el centro de una fila.
Prefiere axis a los modificadores restrictToVerticalAxis y restrictToHorizontalAxis. Hacen lo mismo, pero como modificadores normales se ejecutan en medio de la cadena, y un modificador posterior (una cuadrícula que ajusta los dos ejes) puede volver a sacar el ítem del eje.

Modificadores

Un modificador ajusta el desplazamiento del arrastre en cada frame. Su resultado mueve el overlay y es el punto donde se buscan los destinos, así que una lista ordena según la posición bloqueada, limitada o ajustada, y la position del drop es donde acabó la caja.

ModificadorQué hace
restrictToParentMantiene el overlay dentro del contenedor donde empezó el arrastre: la lista ordenable, o el elemento padre de un elemento arrastrable simple.
restrictToElement(target)Mantiene el overlay dentro de un elemento: una ref de plantilla, un getter o el propio elemento.
restrictToWindowEdgesMantiene el overlay dentro de la ventana.
restrictToFirstScrollableAncestorMantiene el overlay dentro del primer ancestro desplazable del elemento arrastrado.
snapToGrid(size)Ajusta el overlay a una cuadrícula (más abajo).
snapCenterToCursorCentra el overlay bajo el puntero en lugar de mantener el desplazamiento del agarre.
restrictToVerticalAxisSolo se mueve en vertical. Prefiere axis: 'y'.
restrictToHorizontalAxisSolo se mueve en horizontal. Prefiere axis: 'x'.

modifiers es una opción del provider, de las áreas de arrastre, de las listas y los árboles, y de los elementos arrastrables y los ítems ordenables. Todas salvo la del provider aceptan además una ref o un getter, que se leen cuando empieza el arrastre.

Mantenerlo dentro

Tu propia imagen en una videollamada se queda en la pantalla. Cambia el límite y arrástrala bien lejos:

Live

Cargando la demo…

<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 sujeta en la pantalla, su elemento padre. Para un ítem ordenable, el «padre» es su lista, esté donde esté la lista en el DOM.
  • restrictToWindowEdges la deja salir de la pantalla, pero no de la ventana. Si se suelta fuera de la pantalla, no está sobre ningún destino de drop, así que vuelve a su sitio.
  • Sin límite, va a cualquier parte.

Un límite mantiene dentro el overlay, con el tamaño que tenga: con overlayFit: 'slot', una caja que crece sobre una lista de ítems más anchos se mantiene dentro con su nuevo tamaño.

Ajustar a una cuadrícula

Los nodos de un diagrama de flujo se ajustan a la cuadrícula del lienzo. Cambia el tamaño de la celda y el deslizamiento:

Live

Cargando la demo…

<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) ajusta los dos ejes a celdas de 24 px. Un objeto permite ajustar más cosas:

OpciónSignificado
x, yEl tamaño de la celda en píxeles, por eje. Omite uno para ajustar solo el otro: { y: 15 } ajusta un evento a cuartos de hora y lo deja moverse libremente entre los días.
origin'container' (por defecto): la esquina superior izquierda del overlay cae en líneas de cuadrícula trazadas desde la esquina superior izquierda del contenedor — la lista ordenable, o el padre de un elemento arrastrable simple. 'delta': se redondea el propio desplazamiento, así que un ítem que empezó fuera de la cuadrícula se queda fuera de ella y se mueve por celdas enteras desde donde estaba. Elígelo para ítems colocados en cualquier sitio (un lienzo libre) que deben conservar su propia alineación.
smoothtrue o { duration, easing }: el overlay se desliza entre celdas (80 ms por defecto). Los destinos usan la posición ajustada de inmediato.

Con origin: 'container', dibuja la cuadrícula desde la border box del contenedor: el lienzo de arriba tiene su marco como box-shadow, no como border, así que sus líneas empiezan justo en el borde.

Un modificador propio

Las cajas de una diapositiva saltan a los bordes y centros de las demás cuando se acercan, y unas guías rosas muestran las líneas sobre las que se colocan:

Live

Cargando la demo…

<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 modificador recibe transform, el desplazamiento desde el inicio del arrastre tal como lo dejaron los modificadores anteriores, y devuelve uno nuevo. También recibe lo que pueda necesitar para calcularlo:

ArgumentoQué es
transformEl desplazamiento hasta ahora.
overlayRectLa caja del overlay movida por transform, con el tamaño que tiene ahora.
activeRectEl elemento arrastrado cuando empezó el arrastre.
containerRectEl contenedor donde empezó el arrastre (la lista ordenable, o el elemento padre), donde está ahora: sigue al scroll.
windowRectLa ventana.
originEl puntero cuando empezó el arrastre.
active, overEl arrastre y el destino que tiene debajo.

Los modificadores se ejecutan en cada frame: mantenlos rápidos y sin efectos secundarios. Las guías de arriba se dibujan a partir del delta del arrastre en el onDragMove de un monitor, que es el desplazamiento después de todos los modificadores. Los tipos están en Modificadores.

El orden

Los modificadores de varios sitios se ejecutan uno tras otro, cada uno sobre el resultado del anterior:

  1. los modifiers del provider;
  2. los modifiers del área de arrastre donde empezó el arrastre;
  3. los modifiers propios del ítem arrastrado o, si no tiene, los de su lista o su árbol;
  4. el restrictToArea del área de arrastre;
  5. el bloqueo de axis.

Dentro de un mismo array se ejecutan en orden, así que pon los ajustes y los imanes antes de los límites: un límite al final mantiene la caja dentro hagan lo que hagan los demás. La diapositiva de arriba ejecuta [magnet, restrictToParent]; al revés, el imán podría arrastrar unos píxeles más allá del borde una caja sujeta en el borde de la diapositiva.

Los modifiers de un ítem sustituyen a los de su lista; no se suman. Los límites del área vienen después de los ajustes de los pasos 1–3, así que un ítem ajustado sigue dentro, y el bloqueo de eje va el último de todos.

Las listas limitadas conservan sus extremos

Un panel de capas: las filas solo se mueven arriba y abajo, y nunca salen del panel. Arrastra la fila de arriba hasta el final:

Live

Cargando la demo…

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

Un límite sujeta el overlay en el borde de la caja mientras el puntero sigue avanzando. En cuanto el puntero ha salido del overlay a lo largo del eje de la lista, la lista ordena según el puntero, así que el primer y el último puesto siguen a tu alcance sujetes como sujetes el ítem. Las cuadrículas siguen ordenando según el overlay: sujeto en el borde, ya cubre la celda del borde.

Reglas para una parte de la página

Dos áreas, dos conjuntos de reglas. Las pegatinas de Layout se ajustan a su cuadrícula y no pueden salir de ella; las de Sketch van a cualquier parte, también a Layout. Un arrastre conserva las reglas del área donde empezó, y se ilumina el área cuyas reglas están en vigor.

Live

Cargando la demo…

<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 fija reglas para los arrastres que empiezan dentro de su elemento — tanto elementos arrastrables simples como ítems ordenables, que entonces no necesitan reglas propias. Mantén un único DnDProvider para toda la app, y dale a un tablero, un lienzo o un feed su propia área.

OpciónQué hace
axisBloquea los arrastres en un eje. Gana el axis propio de un elemento arrastrable, o el de su lista.
modifiersSe ejecutan después de los del provider y antes de los propios del elemento arrastrable.
restrictToAreatrue: los arrastres se quedan dentro del elemento del área.
autoScrollEl desplazamiento automático del contenedor desplazable del área (el elemento, o su ancestro desplazable más cercano). false lo desactiva ahí.
activationLa activación por tipo de puntero, como una pulsación táctil más larga en un feed que se desplaza. Gana la propia del elemento arrastrable; la del provider va la última.
disabledNo empieza ningún arrastre en el área, ni en las áreas que contiene. Sus elementos arrastrables indican aria-disabled.

Cada opción acepta un valor, una ref o un getter, que se leen cuando empieza una pulsación o un arrastre. El área devuelve isDragging (hay en curso un arrastre que empezó aquí, como muestra el marco iluminado de arriba) e isAutoScrolling (el desplazamiento automático está moviendo su contenedor). Referencia completa: makeDragArea.

Cómo se combinan las áreas

  • Un arrastre sigue al área donde empezó, incluso después de salir de ella. Pasar sobre un área no cambia nada, salvo su autoScroll: ese se aplica a cualquier arrastre que desplace su contenedor.
  • Las áreas anidadas no se fusionan: gana la más cercana. disabled es la excepción, y se hereda: un área desactivada desactiva también las áreas que contiene.
  • La detección de colisiones, las teclas del teclado y los anuncios siguen siendo de todo el provider.
¿Por qué no un segundo DnDProvider alrededor del tablero? Los providers son independientes: un arrastre nunca pasa de uno a otro, así que los ítems del tablero nunca podrían salir de él. Anida un provider solo cuando una parte de la página necesita sus propios ajustes a nivel de provider y sus ítems se quedan dentro. Consulta El provider.

Dejarlo donde se suelta

La línea de tiempo, la llamada, el diagrama de flujo, la diapositiva y las áreas de arriba mantienen todos cada ítem donde se soltó. Cada evento de arrastre lleva position: la esquina superior izquierda de la caja arrastrada dentro del destino, desde el borde de su padding y con su scroll — el left y el top que colocan justo ahí un ítem con posición absoluta. Se calcula después de los modificadores, así que un nodo ajustado aterriza en la cuadrícula.

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

Más sobre el arrastre libre y los destinos de drop en Arrastrar y soltar.