Changing Overlay Container
This example demonstrates how to dynamically change the overlay container component during drag operations, allowing you to create more interactive and contextual drag experiences.
Overview
The Changing Overlay example shows how to dynamically swap the overlay container component during a drag operation. This gives you complete control over the visual representation of dragged elements based on context - for example, when hovering over a drop zone. Unlike static overlays, this approach allows for rich visual feedback that adapts to the current drag state.
Key Concepts
- Dynamically changing the overlay container component during drag operations
- Using
markRaw
to optimize performance and prevent Vue reactivity warnings - Accessing and modifying the store's active container directly
- Responding to hover and leave events to trigger container changes
Example Code
Main Container Component
<script setup lang="ts">
import { useDraggable, useDroppable } from 'vue-dnd-kit';
import ExampleOverlay from './ExampleOverlay.vue';
import { markRaw } from 'vue';
const { elementRef, handleDragStart, isDragging } = useDraggable();
const { elementRef: dropZoneRef, isOvered } = useDroppable({
events: {
onHover: (store) =>
(store.activeContainer.component.value = markRaw(ExampleOverlay)),
onLeave: (store) => (store.activeContainer.component.value = null),
},
});
</script>
<template>
<div class="container">
<div
ref="elementRef"
@pointerdown="handleDragStart"
class="draggable-item"
:class="{ 'is-dragging': isDragging }"
>
Move me to drop zone
</div>
<div
ref="dropZoneRef"
class="drop-zone"
:class="{ 'is-overed': isOvered }"
>
Drop zone
</div>
</div>
</template>
Custom Overlay Component
<script setup lang="ts">
import { useDragContainer } from 'vue-dnd-kit';
import { computed, CSSProperties } from 'vue';
const { elementRef, pointerPosition } = useDragContainer();
const computedStyle = computed<CSSProperties>(() => ({
transform: `translate3d(${
(pointerPosition.current.value?.x ?? 0) -
(pointerPosition.offset.pixel.value?.x ?? 0)
}px, ${
(pointerPosition.current.value?.y ?? 0) -
(pointerPosition.offset.pixel.value?.y ?? 0)
}px, 0)`,
zIndex: 1000,
position: 'fixed',
top: 0,
left: 0,
transition: '0.3s cubic-bezier(0.165, 0.84, 0.44, 1)',
}));
</script>
<template>
<div
ref="elementRef"
class="user-card-skeleton"
:style="computedStyle"
>
<div class="user-card-content">
<div class="avatar-skeleton"></div>
<div class="info-container">
<div class="name-skeleton"></div>
<div class="subtitle-skeleton"></div>
</div>
<div class="body-container">
<div class="text-line-skeleton"></div>
<div class="text-line-skeleton medium"></div>
<div class="text-line-skeleton short"></div>
</div>
<div class="footer-container">
<div class="button-skeleton"></div>
</div>
</div>
</div>
</template>
<style scoped>
.user-card-skeleton {
width: 100%;
max-width: 350px;
background-color: #16161625;
padding: 16px;
border-radius: 8px;
backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.user-card-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.avatar-skeleton {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: rgba(62, 175, 124, 0.1);
border: 1px dashed rgba(62, 175, 124, 0.3);
position: relative;
overflow: hidden;
}
.info-container {
display: flex;
flex-direction: column;
gap: 8px;
}
.name-skeleton {
height: 20px;
width: 70%;
border-radius: 4px;
background-color: rgba(62, 175, 124, 0.1);
border: 1px dashed rgba(62, 175, 124, 0.3);
position: relative;
overflow: hidden;
}
.subtitle-skeleton {
height: 16px;
width: 50%;
border-radius: 4px;
background-color: rgba(62, 175, 124, 0.1);
border: 1px dashed rgba(62, 175, 124, 0.3);
position: relative;
overflow: hidden;
}
.body-container {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 8px;
}
.text-line-skeleton {
height: 16px;
width: 100%;
border-radius: 4px;
background-color: rgba(62, 175, 124, 0.1);
border: 1px dashed rgba(62, 175, 124, 0.3);
position: relative;
overflow: hidden;
}
.text-line-skeleton.medium {
width: 85%;
}
.text-line-skeleton.short {
width: 70%;
}
.footer-container {
margin-top: 8px;
}
.button-skeleton {
height: 30px;
width: 40%;
border-radius: 4px;
background-color: rgba(62, 175, 124, 0.1);
border: 1px dashed rgba(62, 175, 124, 0.3);
position: relative;
overflow: hidden;
}
.avatar-skeleton::after,
.name-skeleton::after,
.subtitle-skeleton::after,
.text-line-skeleton::after,
.button-skeleton::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(62, 175, 124, 0.1) 50%,
rgba(255, 255, 255, 0) 100%
);
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
</style>
How It Works
This example demonstrates a powerful technique for changing overlay containers dynamically:
- Initial State: We start with the default overlay (or none) when dragging begins
- Hover Detection: When hovering over the drop zone, we change the overlay container
- Dynamic Switching: The overlay switches to a card-like skeleton UI when over the drop zone
- Reset on Leave: When leaving the drop zone, the overlay returns to its default state
The key part is directly modifying the store's active container component:
// When hovering over a drop zone
onHover: (store) => (store.activeContainer.component.value = markRaw(ExampleOverlay)),
// When leaving the drop zone
onLeave: (store) => (store.activeContainer.component.value = null),
Using markRaw for Component References
When setting component references in Vue, it's crucial to use markRaw()
to prevent Vue from making the component reactive. This avoids console warnings and performance issues:
// Correct - use markRaw
import { markRaw } from 'vue';
store.activeContainer.component.value = markRaw(ExampleOverlay);
// Incorrect - will cause warnings and performance issues
store.activeContainer.component.value = ExampleOverlay;
Custom Overlay Positioning
The custom overlay uses the positioning data from useDragContainer()
to correctly position itself:
const { elementRef, pointerPosition } = useDragContainer();
const computedStyle = computed(() => ({
transform: `translate3d(${
(pointerPosition.current.value?.x ?? 0) -
(pointerPosition.offset.pixel.value?.x ?? 0)
}px, ${
(pointerPosition.current.value?.y ?? 0) -
(pointerPosition.offset.pixel.value?.y ?? 0)
}px, 0)`,
// other styles...
}));
This technique provides a fully customized drag experience that can adapt to different contexts during the drag operation, enhancing the user experience with rich visual feedback.