---
title: "Оси, границы и сетки"
description: "Фиксируйте драги по одной оси, удерживайте их внутри блока, привязывайте к сетке или к собственным направляющим — для одного элемента, списка или целой части страницы."
canonical_url: "https://zizigy.github.io/vue-dnd-kit/ru/docs/guides/constraints"
---

> Index of every page on this site: [llms.txt](https://zizigy.github.io/vue-dnd-kit/llms.txt)

Драг можно удержать на одной оси, внутри блока, привязать к сетке или изменить собственным
правилом. Правила — это **модификаторы**: они корректируют смещение драга на каждом кадре.
Задайте их на одном перетаскиваемом элементе, на списке или на части страницы, и оверлей, цели
и бросок будут им следовать.

## Фиксация оси

Клипы на таймлайне скользят по своей дорожке. Выключите фиксацию, и они смогут менять и
дорожку.

<example height="22rem" hint="Перетащите клип; выключите фиксацию и перетащите его на другую дорожку" name="axis-timeline">



</example>

```ts [TimelineClip.vue]
const { isDragging } = makeDraggable(el, {
  id: () => props.clip.id,
  axis: () => (props.locked ? 'x' : undefined),
});
```

`axis: 'x'` двигает только по горизонтали, `'y'` — только по вертикали. Её принимают
`makeDraggable`, `makeSortableList`, `makeSortableTree`, `makeSortableItem` и `makeDragArea`:

```ts
makeSortableList(listRef, { items: tracks, axis: 'y' });
```

- Фиксация срабатывает **после всех модификаторов**, поэтому держит, что бы они ни делали:
привязка или граница не могут сдвинуть элемент с его линии.
- Линия задана в содержимом, а не на экране: она движется вместе со списком (или родителем), в
котором начался драг. Прокрутите страницу посреди драга — и элемент останется на своей строке.
- Собственный `axis` элемента важнее, чем у его списка или дерева, а `axis` перетаскиваемого
элемента (или его списка) важнее, чем у его области перетаскивания.
- Он читается в начале драга, поэтому геттер может его переключать, как выше.
- Дерево, зафиксированное по `'y'`, всё равно вкладывает: глубина тогда берётся из наведения
на середину строки.

<tip>

Предпочитайте `axis` модификаторам `restrictToVerticalAxis` и `restrictToHorizontalAxis`. Они
делают то же самое, но как обычные модификаторы работают в середине цепочки, и модификатор
после них (сетка, привязывающая обе оси) может снова сдвинуть элемент с оси.

</tip>

## Модификаторы

Модификатор корректирует смещение драга на каждом кадре. Его результат двигает оверлей **и**
служит точкой, в которой ищутся цели, поэтому список сортирует по зафиксированной,
ограниченной или привязанной позиции, а `position` броска — там, где оказался блок.

<table>
<thead>
  <tr>
    <th>
      Модификатор
    </th>
    
    <th>
      Что делает
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        restrictToParent
      </code>
    </td>
    
    <td>
      Удерживает оверлей внутри контейнера, в котором начался драг: сортируемого списка или родительского элемента обычного перетаскиваемого элемента.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        restrictToElement(target)
      </code>
    </td>
    
    <td>
      Удерживает оверлей внутри элемента: template ref, геттера или самого элемента.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        restrictToWindowEdges
      </code>
    </td>
    
    <td>
      Удерживает оверлей внутри окна.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        restrictToFirstScrollableAncestor
      </code>
    </td>
    
    <td>
      Удерживает оверлей внутри первого прокручиваемого предка перетаскиваемого элемента.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        snapToGrid(size)
      </code>
    </td>
    
    <td>
      Привязывает оверлей к сетке (ниже).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        snapCenterToCursor
      </code>
    </td>
    
    <td>
      Центрирует оверлей под указателем вместо сохранения смещения захвата.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        restrictToVerticalAxis
      </code>
    </td>
    
    <td>
      Двигает только по вертикали. Лучше <code>
        axis: 'y'
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        restrictToHorizontalAxis
      </code>
    </td>
    
    <td>
      Двигает только по горизонтали. Лучше <code>
        axis: 'x'
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

`modifiers` — опция провайдера, областей перетаскивания, списков и деревьев, перетаскиваемых
и сортируемых элементов. Все, кроме провайдера, принимают также ref или геттер, который
читается в начале драга.

### Удержать внутри

Собственное видео в видеозвонке остаётся на экране. Переключите границу и утащите его
подальше:

<example height="29rem" hint="Утащите своё видео за пределы экрана с каждой из границ" name="bounds-pip">



</example>

```ts [SelfView.vue]
const { isDragging } = makeDraggable(el, {
  id: 'self-view',
  modifiers: () => props.modifiers, // [restrictToParent], [restrictToWindowEdges] or []
});
```

- `restrictToParent` держит его на экране, его родительском элементе. Для сортируемого
элемента «родитель» — его список, где бы список ни находился в DOM.
- `restrictToWindowEdges` позволяет ему покинуть экран, но не окно. Брошенное за пределами
экрана, оно не над целью, поэтому летит обратно.
- Без границы оно двигается куда угодно.

Граница удерживает внутри **оверлей**, в его текущем размере: с `overlayFit: 'slot'` блок,
который растёт над списком более широких элементов, удерживается внутри в новом размере.

### Привязка к сетке

Узлы блок-схемы привязываются к сетке холста. Переключите размер ячейки и плавность:

<example height="29rem" hint="Перетащите узел; переключите сетку и плавность" name="snap-canvas">



</example>

```ts [FlowNode.vue]
const { isDragging } = makeDraggable(el, {
  id: () => props.node.id,
  modifiers: () => [snapToGrid({ x: props.grid, y: props.grid, smooth: props.smooth })],
});
```

`snapToGrid(24)` привязывает обе оси к ячейкам 24 px. Объект задаёт больше:

<table>
<thead>
  <tr>
    <th>
      Опция
    </th>
    
    <th>
      Значение
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        x
      </code>
      
      , <code>
        y
      </code>
    </td>
    
    <td>
      Размер ячейки в пикселях, для каждой оси. Не задайте одну, чтобы привязывать только другую: <code>
        { y: 15 }
      </code>
      
       привязывает событие к четвертям часа и даёт ему свободно двигаться по дням.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        origin
      </code>
    </td>
    
    <td>
      <code>
        'container'
      </code>
      
       (по умолчанию): левый верхний угол оверлея попадает на линии сетки, проведённые от левого верхнего угла контейнера — сортируемого списка или родителя обычного перетаскиваемого элемента. <code>
        'delta'
      </code>
      
      : округляется само смещение, так что элемент, начавший вне сетки, вне неё и остаётся и двигается на целые ячейки от того места, где был. Выбирайте его для элементов, размещённых где угодно (свободный холст), которые должны сохранять собственное выравнивание.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        smooth
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
      
       или <code>
        { duration, easing }
      </code>
      
      : оверлей плавно скользит между ячейками (по умолчанию 80 ms). Цели сразу используют привязанную позицию.
    </td>
  </tr>
</tbody>
</table>

С `origin: 'container'` рисуйте сетку от border box контейнера: у холста выше рамка сделана
через `box-shadow`, а не `border`, поэтому его линии начинаются у самого края.

### Свой модификатор

Блоки на слайде прыгают на края и центры других, когда подходят близко, а розовые
направляющие показывают линии, на которых они стоят:

<example height="26rem" hint="Перетащите блок к другому или к середине слайда" name="magnet-guides">



</example>

```ts [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];
```

Модификатор получает `transform` — смещение от начала драга в том виде, в каком его оставили
предыдущие модификаторы, — и возвращает новое. Он также получает то, что может понадобиться
для вычисления:

<table>
<thead>
  <tr>
    <th>
      Аргумент
    </th>
    
    <th>
      Что это
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        transform
      </code>
    </td>
    
    <td>
      Смещение на данный момент.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        overlayRect
      </code>
    </td>
    
    <td>
      Блок оверлея, сдвинутый на <code>
        transform
      </code>
      
      , в его текущем размере.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        activeRect
      </code>
    </td>
    
    <td>
      Перетаскиваемый элемент в момент начала драга.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        containerRect
      </code>
    </td>
    
    <td>
      Контейнер, в котором начался драг (сортируемый список или родительский элемент), там, где он сейчас: он следует за прокруткой.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        windowRect
      </code>
    </td>
    
    <td>
      Окно.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        origin
      </code>
    </td>
    
    <td>
      Указатель в момент начала драга.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        active
      </code>
      
      , <code>
        over
      </code>
    </td>
    
    <td>
      Драг и цель под ним.
    </td>
  </tr>
</tbody>
</table>

Модификаторы выполняются на каждом кадре: держите их быстрыми и без побочных эффектов.
Направляющие выше рисуются по `delta` драга в `onDragMove` монитора — это смещение после всех
модификаторов. Типы — в [модификаторах](https://zizigy.github.io/docs/api/modifiers).

## Порядок

Модификаторы из нескольких мест выполняются один за другим, каждый от результата
предыдущего:

1. `modifiers` провайдера;
2. `modifiers` области перетаскивания, в которой начался драг;
3. собственные `modifiers` перетаскиваемого элемента или, если их нет, его списка или дерева;
4. `restrictToArea` области перетаскивания;
5. фиксация `axis`.

Внутри одного массива они выполняются по порядку, поэтому ставьте привязки и магниты перед
границами: граница в конце удерживает блок внутри, что бы ни сделали остальные. Слайд выше
выполняет `[magnet, restrictToParent]`; в обратном порядке магнит мог бы утянуть блок,
удерживаемый у края слайда, на несколько пикселей за край.

`modifiers` элемента **заменяют** модификаторы его списка, а не добавляются к ним. Границы
области идут после привязок шагов 1–3, поэтому привязанный элемент всё равно остаётся внутри,
а фиксация оси идёт самой последней.

## Ограниченные списки сохраняют свои края

Панель слоёв: строки двигаются только вверх и вниз и никогда не покидают панель. Перетащите
верхнюю строку в самый низ:

<example height="26rem" hint="Перетащите Heading вниз, за край панели" name="bounded-layers">



</example>

```ts [Layers.vue]
const { items } = makeSortableList(list, { items: layers, axis: 'y', modifiers: [restrictToParent] });
```

Граница удерживает оверлей у края блока, пока указатель движется дальше. Как только указатель
покинул оверлей вдоль оси списка, список сортирует уже по указателю, так что первое и
последнее место остаются достижимыми, как бы вы ни держали элемент. Сетки продолжают
сортировать по оверлею: удерживаемый у края, он уже накрывает крайнюю ячейку.

## Правила для части страницы

Две области, два набора правил. Стикеры в Layout привязываются к её сетке и не могут её
покинуть; стикеры в Sketch ходят куда угодно, в том числе в Layout. Драг сохраняет правила
области, в которой начался, а область, чьи правила действуют, подсвечивается.

<example height="25rem" hint="Перетащите стикер в Layout, затем стикер из Sketch в Layout" name="drag-areas">



</example>

```vue [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` задаёт правила для драгов, которые **начинаются внутри** её элемента, — и для
обычных перетаскиваемых элементов, и для сортируемых, которым тогда не нужны собственные
правила. Держите один `DnDProvider` на всё приложение, а доске, холсту или ленте дайте
собственную область.

<table>
<thead>
  <tr>
    <th>
      Опция
    </th>
    
    <th>
      Что делает
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        axis
      </code>
    </td>
    
    <td>
      Фиксирует драги по одной оси. Собственный <code>
        axis
      </code>
      
       перетаскиваемого элемента или его списка важнее.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        modifiers
      </code>
    </td>
    
    <td>
      Выполняются после модификаторов провайдера и до собственных модификаторов перетаскиваемого элемента.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        restrictToArea
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
      
      : драги остаются внутри элемента области.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        autoScroll
      </code>
    </td>
    
    <td>
      Автоскролл скролл-контейнера области (самого элемента или его ближайшего прокручиваемого предка). <code>
        false
      </code>
      
       отключает его там.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        activation
      </code>
    </td>
    
    <td>
      <a href="/docs/guides/sensors#activation-constraints">
        Активация
      </a>
      
       для каждого типа указателя, например более долгое нажатие касанием в прокручиваемой ленте. Собственная активация перетаскиваемого элемента важнее; активация провайдера — последняя.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        disabled
      </code>
    </td>
    
    <td>
      В области не начинается ни один драг, как и в областях внутри неё. Её перетаскиваемые элементы сообщают <code>
        aria-disabled
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

Каждая опция принимает значение, ref или геттер, который читается в начале нажатия или драга.
Область возвращает `isDragging` (идёт драг, начавшийся здесь, — это показывает подсвеченная
рамка выше) и `isAutoScrolling` (автоскролл прокручивает её контейнер). Полный справочник:
[makeDragArea](https://zizigy.github.io/docs/api/make-drag-area).

### Как сочетаются области

- Драг следует правилам области, в которой **начался**, даже после того как покинул её.
Проход над областью ничего не меняет, кроме её `autoScroll`: он применяется к каждому драгу,
который прокручивает её контейнер.
- Вложенные области не сливаются: побеждает ближайшая. Исключение — `disabled`, и оно
наследуется: выключенная область выключает и области внутри себя.
- Определение цели, клавиши клавиатуры и объявления остаются общими для провайдера.

<note>

Почему не второй `DnDProvider` вокруг доски? Провайдеры независимы: драг никогда не переходит
из одного в другой, так что элементы доски никогда не смогли бы её покинуть. Вкладывайте
провайдер только тогда, когда части страницы нужны собственные настройки на уровне провайдера
и её элементы остаются внутри неё. См. [Провайдер](https://zizigy.github.io/docs/guides/provider).

</note>

## Оставить там, где бросили

Таймлайн, звонок, блок-схема, слайд и области выше — все оставляют каждый элемент там, где его
бросили. Каждое событие драга несёт `position`: левый верхний угол перетаскиваемого блока
внутри цели, от края её padding и с учётом её прокрутки — те `left` и `top`, которые ставят
абсолютно позиционированный элемент ровно туда. Он вычисляется после модификаторов, поэтому
привязанный узел приземляется на сетку.

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

Подробнее о свободном перетаскивании и целях — в [Drag and drop](https://zizigy.github.io/docs/guides/drag-and-drop).

## Sitemap

See the full [sitemap](https://zizigy.github.io/vue-dnd-kit/sitemap.md) for all pages.
