---
title: "Холст для дизайна"
description: "Редактор в духе Figma — фреймы и слои на масштабируемом холсте, умные направляющие, рамка выделения, Alt для дублирования и панель слоёв, которая и есть дерево дизайна."
canonical_url: "https://zizigy.github.io/vue-dnd-kit/ru/docs/examples/design-canvas"
---

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

<example height="43rem" hint="Перетащите слой в другой фрейм; нарисуйте рамку, Shift-кликните, удерживайте Alt, чтобы бросать копии" name="design-canvas" title="Холст для дизайна">



</example>

## На что обратить внимание

- **Shape.vue** и **Artboard.vue**: каждый слой — обычный перетаскиваемый элемент, а фрейм
тащат за его имя (`handle`), вместе со слоями внутри. Нажатие выделяет сразу, как в Figma, так
что драг берёт выделение провайдера: нажмите на один из нескольких выделенных слоёв, и
двинутся все.
- **useLayerDrop.ts**: холст и каждый фрейм — цели, которые работают одинаково. Слой,
отпущенный над фреймом, перемещается в него; над холстом — из него. Удерживайте
<kbd value="Alt">



</kbd>

, и вместо этого приземляются копии, а слои остаются на месте.
- **LayersPanel.vue**: панель — это `makeSortableTree` на самом дизайне. Порядок строк — порядок
наложения, а бросок строки во фрейм перемещает слой в этот фрейм.
- **Canvas.vue**: умные направляющие — собственный модификатор демо; `makeSelectionArea` рисует
рамку выделения, а `makeDragArea` даёт свои правила каждому драгу, который начинается на холсте.
- **editor.ts**, **geometry.ts** и **design.ts**: дизайн, его история, вид и математика
привязки и выравнивания, без всякого drag and drop.

## Нарисовано в масштабе

Холст масштабируется без CSS `scale()`: каждый слой раскладывается в своём размере, умноженном на
масштаб, в экранных пикселях. Тогда то, что измеряет библиотека, — это то, что на экране, так
что целям, рамке выделения и клонам в оверлее не нужно ничего особенного. Мир внутри холста
сдвигается только панорамированием, через `translate()`.

```ts [Shape.vue]
const style = computed(() => {
  const { zoom } = editor.view;
  const { x, y, width, height } = props.shape;
  return { left: `${x * zoom}px`, top: `${y * zoom}px`, width: `${width * zoom}px`, height: `${height * zoom}px` };
});
```

Колесо панорамирует и масштабирует (<kbd value="Ctrl">



</kbd>

 или щипок), так же как драг с
<kbd value="Space">



</kbd>

, инструмент «рука» и кнопки в углу: обычные события указателя и колеса, как и
ручки изменения размера. Раз Space — это рука, слой берётся одной клавишей <kbd value="Enter">



</kbd>


(`keyboard.keys` провайдера).

## Перемещение слоёв между фреймами

Слой — обычный перетаскиваемый элемент с собственным оверлеем, `DragGroup.vue`. Он раскладывает
перетаскиваемые слои так, как они лежат: `DragPreview` (клон, снятый при захвате) каждого,
размещённый по `data` его слоя, там, где тот нарисован. Сами слои отходят в сторону, пока
двигаются их клоны, если не зажат <kbd value="Alt">



</kbd>

.

```ts [Shape.vue]
const { isDragging, selected } = makeDraggable(el, {
  id: () => props.shape.id,
  type: 'layer',
  data: () => editor.dragData(props.shape), // { layer, left, top }
  overlay: DragGroup,
});
```

При броске `position` — место внутри цели, где отпустили угол взятого слоя, в экранных пикселях.
Делённое на масштаб, оно говорит, насколько этот слой сдвинулся в мире, и каждый
перетаскиваемый слой сдвигается на столько же — во фрейм или на холст:

```ts [useLayerDrop.ts]
const corner = target ? editor.boxOf(target) : { x: -view.x / view.zoom, y: -view.y / view.zoom };
const toWorld = (x: number, y: number) => ({ x: corner.x + x / view.zoom, y: corner.y + y / view.zoom });

const from = editor.boxOf(grabbed); // the layer of `active.id`
const to = toWorld(position.x, position.y);
editor.move(active.ids, { x: to.x - from.x, y: to.y - from.y }, target, editor.altHeld.value);
```

Фрейм принимает слои, а не фреймы: драг, который несёт фрейм, проходит над другими фреймами
(`disabled`, читается вживую) на холст. У целей `overDropAnimation: false`, так как слои появляются
ровно там, где отпустили их клоны. Инструменты на панели инструментов — тоже обычные
перетаскиваемые элементы: брошенные на фрейм, они добавляют в него свой слой с центром в точке
броска; по клику — в середину вида.

```ts [Artboard.vue]
makeDroppable(body, {
  id: () => props.frame.id,
  accept: 'layer',
  disabled: () => !!active.value?.items.some((item) => item.type.includes('frame')),
  overDropAnimation: false,
  onDrop: useLayerDrop(() => props.frame),
});
```

## Умные направляющие

`smartGuides` — модификатор: он получает смещение драга, вычисляет, где в мире оказались бы
перетаскиваемые слои, и притягивает их к ближайшему краю или центру другого слоя в пределах 6
экранных пикселей. Оверлей, цель под ним и бросок следуют за этим. Монитор рисует розовые линии и
расстояния по `delta` драга — смещению после модификаторов.

```ts [Canvas.vue]
const smartGuides: Modifier = ({ transform, active }) => {
  const moving = editor.boundsOf(active.ids);
  if (!moving) return transform; // a tool from the toolbar
  const { zoom } = view;
  const box = offsetBox(moving, transform.x / zoom, transform.y / zoom);
  const targets = editor.snapTargets(active.ids);
  return {
    x: transform.x + snapBox(box, targets, 'x', SNAP / zoom) * zoom,
    y: transform.y + snapBox(box, targets, 'y', SNAP / zoom) * zoom,
  };
};

// Every drag that starts on the canvas, frames and layers alike, gets the guides.
makeDragArea(viewport, { modifiers: [smartGuides], disabled: panning });
```

Ручки изменения размера привязываются по тем же линиям и рисуют те же направляющие.

## Выделение

Рамка, нарисованная на пустом холсте, выделяет фреймы и отдельные слои; нарисованная внутри
фрейма — слои фрейма. Обе — `makeSelectionArea`: нажатие забирает самая внутренняя область, и
ей принадлежат перетаскиваемые элементы внутри неё. <kbd value="Shift">



</kbd>

 добавляет к выделению
(`strategy: 'toggle'`).

```ts [Artboard.vue]
const { isSelecting, style: band } = makeSelectionArea(body, {
  strategy: () => (editor.shiftHeld.value ? 'toggle' : 'select'),
  disabled: editor.panning,
});
```

Редактор никогда не выделяет фрейм вместе со слоем внутри него: при драге слой сдвинулся бы
дважды. Рамка захватывает указатель, когда начинается, поэтому клик по пустому месту фрейма
никогда не доходит до фрейма: `Artboard.vue` выделяет фрейм, когда нажатие там отпущено в том же
месте, где началось.

## Панель слоёв

Элементы дерева — это дизайн: фреймы со своими `children`, верхняя строка — впереди. `canNest`
позволяет фреймам принимать слои и держит фреймы на верхнем уровне. У строк собственные ключи
(`row:…`), так как id слоёв принадлежат их перетаскиваемым элементам на холсте; выделение — то же,
что у холста, так что строка подсвечивается вместе со своим слоем.

```ts [LayersPanel.vue]
makeSortableTree(tree, {
  items: editor.layers,
  getKey: (layer) => `row:${layer.id}`,
  canNest: (layer, parent) => parent.kind === 'frame' && layer.kind !== 'frame',
  maxDepth: 1,
  animateChanges: true,
  onDrop: ({ items, from, to }) => {
    // Positions count from the frame's corner: a layer changing frames takes the
    // difference, so it stays where it is on the canvas.
  },
});
```

`onDrop` выполняется до записи дерева, поэтому редактор ещё знает, из какого фрейма пришёл
каждый слой, и сохраняет дизайн для отмены. Слой, перемещённый во фрейм, который его не
покрывает, был бы обрезан и не виден: вместо этого он встаёт в середину фрейма. С `animateChanges`
строки скользят и при изменениях, сделанных на холсте и отменой. У строки есть тип
(`type: 'row'`), поэтому холст и фреймы, которые принимают слои и инструменты, её отклоняют.

## Клавиатура

Перейдите табом к слою (он выделится), затем <kbd value="Enter">



</kbd>

 берёт его, стрелки двигают его на
10 пикселей (<kbd value="Shift">



</kbd>

: на 40), а <kbd value="Enter">



</kbd>

 бросает его во фрейм под ним. Без
драга стрелки сдвигают выделение на пиксель. Инструмент берётся так же, а его стрелки прыгают
между холстом и фреймами (`keyboardMode: 'droppables'`). Остальные сочетания клавиш перечислены в
панели Design, когда ничего не выделено.

Подробнее: [drag and drop](https://zizigy.github.io/docs/guides/drag-and-drop#leave-it-where-it-was-dropped) ·
[свой модификатор](https://zizigy.github.io/docs/guides/constraints#your-own-modifier) ·
[правила для части страницы](https://zizigy.github.io/docs/guides/constraints#rules-for-a-part-of-the-page) ·
[выделение рамкой](https://zizigy.github.io/docs/guides/multi-drag#rubber-band-selection) ·
[деревья](https://zizigy.github.io/docs/guides/trees)

## Sitemap

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