> For AI agents: the complete documentation index is available at https://gultyayev.github.io/ngx-virtual-dnd/llms.txt, the full documentation bundle is available at https://gultyayev.github.io/ngx-virtual-dnd/llms-full.txt.

# Animations and haptics

By default, rows displaced by the placeholder jump to their new position, and the preview disappears on drop. Provide `VDND_ANIMATION_CONFIG` to make rows slide and the dropped item glide into its slot instead. For feedback on every step, such as a haptic tick, listen to `(placeholderMove)`.


Live exampleSliding rows and a placeholder move counter[Open in new tab](/ngx-virtual-dnd/demo/examples/shift-animation/)

```ts file="<root>/src/app/examples/shift-animation/shift-animation.ts"
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import {
  DragPreviewComponent,
  DraggableDirective,
  DropEvent,
  DroppableGroupDirective,
  PlaceholderMoveEvent,
  reorderItems,
  VDND_ANIMATION_CONFIG,
  VirtualSortableListComponent,
} from 'ngx-virtual-dnd';

interface Task {
  id: string;
  title: string;
}

@Component({
  selector: 'app-shift-animation-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    VirtualSortableListComponent,
    DroppableGroupDirective,
    DraggableDirective,
    DragPreviewComponent,
  ],
  // Lists in this component slide displaced rows instead of snapping them into place
  providers: [{ provide: VDND_ANIMATION_CONFIG, useValue: { shiftDuration: 200 } }],
  template: `
    <p class="status">
      Placeholder moves: <strong data-placeholder-moves>{{ moves() }}</strong>
      @if (lastMove()) {
        · last <strong data-last-placeholder-move>{{ lastMove() }}</strong>
      }
    </p>

    <div vdndGroup="tasks">
      <ng-template #taskTpl let-task>
        <div class="row" [vdndDraggable]="task.id">{{ task.title }}</div>
      </ng-template>

      <vdnd-sortable-list
        class="list"
        droppableId="tasks"
        [items]="tasks()"
        [itemHeight]="48"
        [containerHeight]="288"
        [itemIdFn]="taskId"
        [itemTemplate]="taskTpl"
        (drop)="onDrop($event)"
        (placeholderMove)="onPlaceholderMove($event)"
      />
    </div>

    <vdnd-drag-preview />
  `,
})
export class ShiftAnimationExampleComponent {
  readonly tasks = signal<Task[]>(
    Array.from({ length: 500 }, (_, i) => ({ id: `task-${i + 1}`, title: `Task ${i + 1}` })),
  );

  readonly moves = signal(0);
  readonly lastMove = signal('');

  readonly taskId = (task: Task): string => task.id;

  onPlaceholderMove(event: PlaceholderMoveEvent): void {
    // One event per displacement: a good place for haptic feedback
    navigator.vibrate?.(10);
    this.moves.update((count) => count + 1);
    this.lastMove.set(`${event.previousIndex ?? 'entered'} → ${event.currentIndex}`);
  }

  onDrop(event: DropEvent): void {
    reorderItems(event, this.tasks);
  }
}

```

## Shift animation

The animation is opt-in. Provide the config app-wide, or in a component's `providers` to animate only the lists in that subtree:

```ts
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { VDND_ANIMATION_CONFIG } from 'ngx-virtual-dnd';

export const appConfig: ApplicationConfig = {
  providers: [{ provide: VDND_ANIMATION_CONFIG, useValue: { shiftDuration: 200 } }],
};
```

| Option          | Default                      | Meaning                                                            |
| --------------- | ---------------------------- | ------------------------------------------------------------------ |
| `shiftDuration` | `200`                        | Slide duration in ms. `0` turns it off                             |
| `shiftEasing`   | `cubic-bezier(0.2, 0, 0, 1)` | Any CSS easing function                                            |
| `dropDuration`  | `200`                        | [Drop animation](#drop-animation) duration in ms. `0` turns it off |
| `dropEasing`    | `cubic-bezier(0.2, 0, 0, 1)` | Any CSS easing function                                            |

- The slide animates the `translate` CSS property, so it runs on the compositor. It works with virtual scrolling and with [dynamic heights](https://gultyayev.github.io/ngx-virtual-dnd/guide/features/dynamic-heights.md).
- It is skipped when the user has turned on reduced motion (`prefers-reduced-motion: reduce`).
- If the placeholder moves again mid-slide, rows continue from where they currently are.
- Drop positions are calculated from geometry, so a row that is still sliding never changes where an item lands.
- Rows also slide on drop or cancel. After a drop they usually already sit in their final slots, so a slide in progress simply finishes. After a cancel, displaced rows slide back.
- Your own `transform` on rows is kept: the slide moves the row as a whole, and your rotation or scale does not distort the slide. If you set `translate` on rows yourself, the slide is added to it, but then it runs on the main thread instead of the compositor, so prefer `transform` for your own offsets. A `translate` you change while a row is sliding applies once the slide ends.

The config is read each time an animation starts, so a getter can switch it at runtime, for example from a user setting:

```ts
{
  provide: VDND_ANIMATION_CONFIG,
  useFactory: () => {
    const settings = inject(SettingsService);
    return {
      get shiftDuration() {
        return settings.reduceAnimations() ? 0 : 200;
      },
    };
  },
}
```

## Drop animation

With the same config, releasing an item no longer makes the preview vanish. The preview glides from where you let go onto the item's final position, then disappears. The real item stays invisible until the preview lands on it, so it is never shown twice. A cancelled drag (Escape
) glides back to where the item started.
- It is purely visual. `drop` and `dragEnd` fire right away, and the list is already in its new order while the preview travels.
- The preview lands on the item as it is rendered after your `(drop)` handler. Commit the move in the handler (for example with `moveItem()` or `reorderItems()`). If you commit later, for example after a server call, the preview glides back to the item's old slot.
- If the item is not rendered, or is scrolled out of its list's view, the preview fades out where it was released.
- Starting a new drag ends the animation at once.
- While it plays, the preview box has the class `vdnd-drag-preview-dropping`.
- It is skipped under reduced motion, like the shift animation. Set `dropDuration: 0` to keep only the shift animation.

### Where it applies

Rows animate in `<vdnd-sortable-list>`, `<vdnd-virtual-scroll>` and every `*vdndVirtualFor` list, including those inside `<vdnd-virtual-viewport>` and `<vdnd-virtual-content>`.

:::warning
Do not add your own CSS `transition` on the rows' `transform`, `translate` or `top` to get this effect. Rows are repositioned by layout (or by an inline `top` in a `*vdndVirtualFor` placed directly in a `vdndScrollable`), and a transition on any of them competes with the built-in slide.
:::

## Placeholder move events

`vdndDroppable` and `<vdnd-sortable-list>` emit `placeholderMove` each time the placeholder moves within that list, which is every time rows are displaced:

```angular-html
<vdnd-sortable-list ... (placeholderMove)="onPlaceholderMove($event)" />
```

```ts
onPlaceholderMove(event: PlaceholderMoveEvent): void {
  navigator.vibrate?.(10); // or Haptics.selectionChanged() from @capacitor/haptics
}
```

The event fires when the placeholder enters the list and on every move inside it. It does not fire for the initial pick-up or when the placeholder leaves the list. `currentIndex` is where the item would land if it were dropped now, so it also suits [screen reader announcements](https://gultyayev.github.io/ngx-virtual-dnd/guide/features/accessibility.md#screen-reader-announcements). See [`PlaceholderMoveEvent`](https://gultyayev.github.io/ngx-virtual-dnd/api/events.md#placeholdermoveevent) for every field.

Keep the handler cheap, and bind it only where you use it: each event runs change detection for your component and its ancestors (see [Performance](https://gultyayev.github.io/ngx-virtual-dnd/guide/features/performance.md#placeholder-move-listeners)).
