Documentation/Vue d'ensemble
Canevas de design
Un éditeur à la Figma — des frames et des calques sur un canevas zoomable, des repères intelligents, un rectangle de sélection, Alt pour dupliquer, et un panneau des calques qui est l'arbre du design.
Cette page a été traduite par IA et peut contenir des inexactitudes. Lire l'original en anglais
Chargement de la démo…
<script setup lang="ts">
import { DnDProvider, type Announcements } from '@vue-dnd-kit/core';
import DesignCanvas from './DesignCanvas.vue';
import { KINDS } from './design';
import type { DragData } from './editor';
import type { ToolData } from './useLayerDrop';
// Screen readers hear names, not ids: a layer's, or the layer a tool adds. Rows of the
// layers panel keep the built-in texts (`undefined`), which say where they are in the tree.
const nameOf = (data: unknown) => {
const { layer, tool } = (data ?? {}) as Partial<DragData & ToolData>;
return layer?.name ?? (tool && `New ${KINDS[tool].name.toLowerCase()}`);
};
const placeOf = (over: { id: unknown; data: unknown }) =>
over.id === 'canvas' ? 'the canvas' : `frame ${(over.data as { name: string }).name}`;
const announcements: Partial<Announcements> = {
onDragStart: ({ active }, { count }) => {
const name = nameOf(active.data);
if (!name) return undefined;
const others = count > 1 ? ` and ${count - 1} more` : '';
return `Picked up ${name}${others}. Arrow keys move it, Enter drops it, Escape cancels.`;
},
onDragOver: ({ active, over }) => (nameOf(active.data) ? (over ? `Over ${placeOf(over)}` : 'Off the canvas') : undefined),
onDragEnd: ({ active, over, dropped }) => {
const name = nameOf(active.data);
if (!name) return undefined;
return dropped && over ? `${name} dropped on ${placeOf(over)}.` : `${name} went back.`;
},
onDragCancel: ({ active }) => (nameOf(active.data) ? `${nameOf(active.data)} went back.` : undefined),
};
</script>
<template>
<!-- Space pans, as in any design tool, so Enter alone picks a layer up. Every dragged
layer gets its clone at pickup, and the page doesn't scroll under a drag. -->
<DnDProvider
:keyboard="{ keys: { start: ['Enter'], drop: ['Enter'] }, step: 10 }"
overlay-previews="all"
:auto-scroll="false"
:announcements="announcements"
screen-reader-instructions="Press Enter to pick up a layer, the arrow keys to move it, Enter to drop it and Escape to cancel. Without a drag, the arrow keys nudge the selected layers."
>
<DesignCanvas />
</DnDProvider>
</template>Ce qu'il faut regarder
Shape.vueetArtboard.vue: chaque calque est un élément déplaçable simple, et une frame se fait glisser par son nom (handle), avec ses calques à l'intérieur. Un appui sélectionne tout de suite, comme dans Figma : la sélection du provider est donc ce qu'un glissement emmène ; appuyez sur l'un de plusieurs calques sélectionnés et ils bougent tous.useLayerDrop.ts: le canevas et chaque frame sont des cibles de dépôt qui fonctionnent de la même façon. Un calque relâché au-dessus d'une frame y entre ; au-dessus du canevas, il en sort. Maintenez Alt et ce sont des copies qui atterrissent, les calques restant où ils étaient.LayersPanel.vue: le panneau estmakeSortableTreesur le design lui-même. L'ordre des lignes est l'ordre d'empilement, et déposer une ligne dans une frame y déplace le calque.Canvas.vue: les repères intelligents sont un modificateur propre à la démo ;makeSelectionAreatrace le rectangle de sélection, etmakeDragAreadonne ses règles à chaque glissement qui commence sur le canevas.editor.ts,geometry.tsetdesign.ts: le design, son historique, la vue et les calculs de l'aimantation et de l'alignement, sans aucun glisser-déposer.
Dessiné au zoom
Le canevas zoome sans scale() CSS : chaque calque est mis en page à sa taille multipliée par le
zoom, en pixels d'écran. Ce que mesure la bibliothèque est donc ce qui est à l'écran : les cibles
de dépôt, le rectangle de sélection et les clones de l'overlay n'ont besoin de rien de
particulier. Le monde à l'intérieur du canevas n'est déplacé que par le panoramique, avec
translate().
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` };
});
La molette fait défiler et zoomer (Ctrl ou un pincement), tout comme un glissement
avec Space, l'outil main et les boutons dans le coin : de simples événements de
pointeur et de molette, comme les poignées de redimensionnement. Espace étant la main, un calque
se saisit avec Enter seul (le keyboard.keys du provider).
Déplacer des calques entre les frames
Un calque est un élément déplaçable simple avec un overlay à lui, DragGroup.vue. Il dispose les
calques glissés comme ils sont placés : un DragPreview (le clone pris à la saisie) de chacun,
placé d'après le data de son calque, là où il est dessiné. Les calques eux-mêmes s'effacent
pendant que leurs clones bougent, sauf si Alt est maintenu.
const { isDragging, selected } = makeDraggable(el, {
id: () => props.shape.id,
type: 'layer',
data: () => editor.dragData(props.shape), // { layer, left, top }
overlay: DragGroup,
});
Au dépôt, position est l'endroit où le coin du calque saisi a été relâché dans la cible, en
pixels d'écran. Divisé par le zoom, il dit de combien ce calque s'est déplacé dans le monde, et
chaque calque glissé se déplace d'autant, dans la frame ou sur le canevas :
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);
Une frame accepte des calques, pas des frames : un glissement qui transporte une frame passe
au-dessus des autres frames (disabled, lu en direct) jusqu'au canevas. Les cibles ont
overDropAnimation: false, car les calques apparaissent juste là où leurs clones ont été relâchés.
Les outils de la barre d'outils sont aussi des éléments déplaçables simples : déposés sur une
frame, ils y ajoutent leur calque, centré sur le point de dépôt ; cliqués, au milieu de la 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),
});
Repères intelligents
smartGuides est un modificateur : il reçoit le décalage du glissement, calcule où seraient les
calques glissés dans le monde, et les attire sur le bord ou le centre le plus proche d'un autre
calque à moins de 6 pixels d'écran. L'overlay, la cible en dessous et le dépôt suivent tous. Un
moniteur dessine les lignes roses et les distances d'après le delta du glissement, le décalage
après les modificateurs.
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 });
Les poignées de redimensionnement s'aimantent sur les mêmes lignes et dessinent les mêmes repères.
Sélectionner
Un rectangle de sélection tracé sur le canevas vide sélectionne les frames et les calques isolés ;
tracé dans une frame, les calques de la frame. Les deux sont des makeSelectionArea : la zone la
plus intérieure prend l'appui, et possède les éléments déplaçables qu'elle contient.
Shift ajoute à la sélection (strategy: 'toggle').
const { isSelecting, style: band } = makeSelectionArea(body, {
strategy: () => (editor.shiftHeld.value ? 'toggle' : 'select'),
disabled: editor.panning,
});
L'éditeur ne sélectionne jamais une frame en même temps qu'un calque qu'elle contient : glissé,
le calque bougerait deux fois. Un rectangle capture le pointeur dès qu'il commence : un clic sur
l'espace vide d'une frame n'atteint donc jamais la frame ; Artboard.vue sélectionne la frame
quand un appui à cet endroit est relâché là où il a commencé.
Le panneau des calques
Les items de l'arbre sont le design : des frames avec leurs children, la ligne du haut devant.
canNest laisse les frames accepter des calques et garde les frames au premier niveau. Les lignes
ont des clés à elles (row:…), car les ids des calques appartiennent à leurs éléments
déplaçables sur le canevas ; la sélection est celle du canevas : une ligne s'allume donc avec son
calque.
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 s'exécute avant que l'arbre ne soit écrit : l'éditeur sait donc encore de quelle frame
vient chaque calque, et sauvegarde le design pour l'annulation. Un calque déplacé dans une frame
qui ne le recouvre pas serait rogné hors de vue : il va plutôt au milieu de la frame. Avec
animateChanges, les lignes glissent aussi pour les changements faits sur le canevas et par
l'annulation. Une ligne est typée (type: 'row') : le canevas et les frames, qui acceptent des
calques et des outils, la refusent donc.
Clavier
Allez sur un calque avec Tab (il est sélectionné), puis Enter le saisit, les flèches
le déplacent de 10 pixels (Shift : 40), et Enter le dépose dans la
frame en dessous. Sans glissement, les flèches décalent la sélection d'un pixel. Un outil se
saisit de la même façon, et ses flèches sautent entre le canevas et les frames
(keyboardMode: 'droppables'). Les autres raccourcis sont listés dans le panneau Design quand rien
n'est sélectionné.
En savoir plus : Glisser-déposer · Votre propre modificateur · Des règles pour une partie de la page · Rectangle de sélection · Arbres