Documentação/Guias

Eixos, limites e grades

Trave arrastes em um eixo, mantenha-os dentro de uma caixa, encaixe-os em uma grade ou em guias suas — para um item, uma lista ou uma parte inteira da página.

Editar esta página

Esta página foi traduzida por IA e pode conter imprecisões. Ler o original em inglês

Um arraste pode ser preso a um eixo, mantido dentro de uma caixa, encaixado em uma grade ou desviado por uma regra sua. As regras são modificadores: eles ajustam o deslocamento do arraste a cada quadro. Defina-os em um draggable, em uma lista ou em uma parte da página, e o overlay, os alvos de drop e o drop seguem todos eles.

Travar um eixo

Os clipes de uma linha do tempo deslizam pela sua faixa. Desligue a trava e eles também podem trocar de faixa.

Live

Carregando a 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' move só na horizontal, 'y' só na vertical. makeDraggable, makeSortableList, makeSortableTree, makeSortableItem e makeDragArea aceitam essa opção:

makeSortableList(listRef, { items: tracks, axis: 'y' });
  • A trava roda depois de todos os modificadores, então ela se mantém, não importa o que eles façam: um encaixe ou um limite não consegue tirar o item da sua linha.
  • A linha está no conteúdo, não na tela: ela se move com a lista (ou o elemento pai) onde o arraste começou. Role a página no meio do arraste e o item continua na mesma linha.
  • O axis do próprio item vence o da lista ou da árvore, e o de um draggable (ou da lista dele) vence o da área de arraste.
  • Ele é lido quando um arraste começa, então um getter pode alterná-lo, como acima.
  • Uma árvore travada em 'y' ainda aninha: a profundidade passa a vir de parar sobre o meio de uma linha.
Prefira axis aos modificadores restrictToVerticalAxis e restrictToHorizontalAxis. Eles fazem a mesma coisa, mas, como modificadores comuns, rodam no meio da cadeia, e um modificador depois deles (uma grade que encaixa os dois eixos) pode tirar o item do eixo de novo.

Modificadores

Um modificador ajusta o deslocamento do arraste a cada quadro. O resultado dele move o overlay e é o ponto onde os alvos são procurados, então uma lista ordena pela posição travada, limitada ou encaixada, e a position do drop é onde a caixa terminou.

ModificadorO que faz
restrictToParentMantém o overlay dentro do contêiner em que o arraste começou: a lista ordenável, ou o elemento pai de um draggable simples.
restrictToElement(target)Mantém o overlay dentro de um elemento: um template ref, um getter ou o próprio elemento.
restrictToWindowEdgesMantém o overlay dentro da janela.
restrictToFirstScrollableAncestorMantém o overlay dentro do primeiro ancestral rolável do elemento arrastado.
snapToGrid(size)Encaixa o overlay em uma grade (abaixo).
snapCenterToCursorCentraliza o overlay sob o ponteiro em vez de manter o deslocamento de onde ele foi pego.
restrictToVerticalAxisMove só na vertical. Prefira axis: 'y'.
restrictToHorizontalAxisMove só na horizontal. Prefira axis: 'x'.

modifiers é uma opção do provider, das áreas de arraste, das listas e árvores, e dos draggables e itens ordenáveis. Todas, menos a do provider, também aceitam um ref ou um getter, lido quando o arraste começa.

Manter dentro

A própria imagem em uma videochamada fica na tela. Troque o limite e arraste-a para bem longe:

Live

Carregando a 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 a mantém na tela, o elemento pai dela. Para um item ordenável, o "pai" é a lista dele, onde quer que a lista esteja no DOM.
  • restrictToWindowEdges deixa que ela saia da tela, mas não da janela. Solta fora da tela, ela não está sobre um alvo de drop, então volta ao lugar.
  • Sem limite, ela vai para qualquer lugar.

Um limite mantém o overlay dentro, no tamanho que ele tem: com overlayFit: 'slot', uma caixa que cresce sobre uma lista de itens mais largos é mantida dentro no novo tamanho.

Encaixar em uma grade

Os nós de um fluxograma se encaixam na grade da tela de desenho. Troque o tamanho da célula e o deslizamento:

Live

Carregando a 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) encaixa os dois eixos em células de 24 px. Um objeto define mais:

OpçãoSignificado
x, yO tamanho da célula em pixels, por eixo. Deixe um de fora para encaixar só o outro: { y: 15 } encaixa um evento em quartos de hora e o deixa se mover livremente entre os dias.
origin'container' (padrão): o canto superior esquerdo do overlay pousa em linhas de grade traçadas a partir do canto superior esquerdo do contêiner — a lista ordenável, ou o pai de um draggable simples. 'delta': o próprio deslocamento é arredondado, então um item que começou fora da grade continua fora dela e se move por células inteiras a partir de onde estava. Escolha isso para itens colocados em qualquer lugar (um canvas livre) que devem manter o próprio alinhamento.
smoothtrue ou { duration, easing }: o overlay desliza entre as células (80 ms por padrão). Os alvos usam a posição encaixada na hora.

Com origin: 'container', desenhe a grade a partir da border box do contêiner: a tela de desenho acima tem a moldura como box-shadow, não como border, então as linhas dela começam bem na borda.

Um modificador seu

As caixas de um slide pulam para as bordas e os centros das outras quando chegam perto, e guias rosa mostram as linhas em que elas ficam:

Live

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

Um modificador recebe transform, o deslocamento desde o início do arraste como os modificadores anteriores o deixaram, e retorna um novo. Ele também recebe o que pode precisar para calculá-lo:

ArgumentoO que é
transformO deslocamento até agora.
overlayRectA caixa do overlay movida por transform, no tamanho que ela tem agora.
activeRectO elemento arrastado quando o arraste começou.
containerRectO contêiner em que o arraste começou (a lista ordenável, ou o elemento pai), onde ele está agora: acompanha a rolagem.
windowRectA janela.
originO ponteiro quando o arraste começou.
active, overO arraste e o alvo sob ele.

Os modificadores rodam a cada quadro: mantenha-os rápidos e sem efeitos colaterais. As guias acima são desenhadas a partir do delta do arraste no onDragMove de um monitor, que é o deslocamento depois de todos os modificadores. Tipos em Modificadores.

A ordem

Modificadores de vários lugares rodam um depois do outro, cada um a partir do resultado do anterior:

  1. os modifiers do provider;
  2. os modifiers da área de arraste em que o arraste começou;
  3. os modifiers do próprio item arrastado ou, quando ele não tem, os da lista ou da árvore dele;
  4. o restrictToArea da área de arraste;
  5. a trava de axis.

Dentro de um array, eles rodam em ordem, então coloque encaixes e ímãs antes dos limites: um limite por último mantém a caixa dentro, não importa o que os outros fizeram. O slide acima roda [magnet, restrictToParent]; na ordem inversa, o ímã poderia puxar uma caixa presa na borda do slide alguns pixels para além dela.

Os modifiers de um item substituem os da lista; eles não se somam a eles. Os limites da área vêm depois do encaixe das etapas 1–3, então um item encaixado ainda fica dentro, e a trava de eixo vem por último de todos.

Listas limitadas mantêm as pontas

Um painel de camadas: as linhas só se movem para cima e para baixo, e nunca saem do painel. Arraste a linha de cima até o fim:

Live

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

Um limite segura o overlay na borda da caixa enquanto o ponteiro continua. Assim que o ponteiro sai do overlay ao longo do eixo da lista, a lista passa a ordenar pelo ponteiro, então o primeiro e o último lugar continuam ao alcance, não importa como você segura o item. Grades continuam ordenando pelo overlay: segurado na borda, ele já cobre a célula da borda.

Regras para uma parte da página

Duas áreas, dois conjuntos de regras. Os adesivos em Layout se encaixam na grade dela e não podem sair; os adesivos em Sketch vão para qualquer lugar, para Layout também. Um arraste mantém as regras da área em que começou, e a área cujas regras estão valendo se acende.

Live

Carregando a 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 define regras para os arrastes que começam dentro do seu elemento — draggables simples e itens ordenáveis igualmente, que então não precisam de regras próprias. Mantenha um DnDProvider para o app inteiro, e dê a um quadro, a uma tela de desenho ou a um feed uma área própria.

OpçãoO que faz
axisTrava os arrastes em um eixo. O axis do próprio draggable, ou o da lista dele, vence.
modifiersRodam depois dos do provider e antes dos do próprio draggable.
restrictToAreatrue: os arrastes ficam dentro do elemento da área.
autoScrollRolagem automática do contêiner de rolagem da área (o elemento, ou o ancestral rolável mais próximo dele). false a desliga ali.
activationAtivação por tipo de ponteiro, como um toque mais longo em um feed que rola. A do próprio draggable vence; a do provider vem por último.
disabledNenhum arraste começa na área, nem nas áreas dentro dela. Os draggables dela informam aria-disabled.

Toda opção aceita um valor, um ref ou um getter, lido quando uma pressão ou um arraste começa. A área retorna isDragging (um arraste que começou aqui está em andamento, como mostra a moldura acesa acima) e isAutoScrolling (a rolagem automática está movendo o contêiner dela). Referência completa: makeDragArea.

Como as áreas se combinam

  • Um arraste segue a área em que começou, mesmo depois de sair dela. Passar por cima de uma área não muda nada, exceto o autoScroll dela: esse vale para todo arraste que rola o contêiner dela.
  • Áreas aninhadas não se mesclam: a mais próxima vence. disabled é a exceção, e é herdado: uma área desligada desliga também as áreas dentro dela.
  • A detecção de colisão, as teclas do teclado e os anúncios continuam valendo para o provider inteiro.
Por que não um segundo DnDProvider em volta do quadro? Os providers são independentes: um arraste nunca passa de um para outro, então os itens do quadro nunca poderiam sair dele. Aninhe um provider só quando uma parte da página precisa de configurações próprias de provider e os itens dela ficam dentro dela. Veja O provider.

Deixar onde foi solto

A linha do tempo, a chamada, o fluxograma, o slide e as áreas acima mantêm cada item onde ele foi solto. Todo evento de arraste traz position: o canto superior esquerdo da caixa arrastada dentro do alvo, a partir da borda do padding dele e com a rolagem dele — o left e o top que colocam um item com posicionamento absoluto bem ali. Ela vem depois dos modificadores, então um nó encaixado pousa na grade.

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

Mais sobre arraste livre e alvos de drop em Arrastar e soltar.