Appearance
Basic Draggable Component Code ​
Dependencies
This component requires:
- Vue 3 with Composition API
- SCSS support
Full Component Code ​
vue
<script setup>
// vue imports
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
// constants
// store
// libraries
// components
// Props
const props = defineProps({
positionX: {
required: false,
type: Number,
default: 200,
},
positionY: {
required: false,
type: Number,
default: 200,
},
dragAlongWithCursor: {
type: Boolean,
default: false,
required: false,
},
placement: {
type: String,
default: 'bottom',
required: false,
},
});
// Rem to pixel conversion
const remToPixel = ref(16); // Default to 16px (1rem = 16px typically)
const calculateRemToPixel = () => {
// Get the root font size
if (typeof document !== 'undefined') {
const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
remToPixel.value = rootFontSize || 16;
}
};
const emit = defineEmits(['setPosition', 'onStartDrag', 'onDragging', 'onStopDrag']);
let draggableComponent = ref(null);
let isDragging = ref(false);
let posX = ref(0);
let posY = ref(0);
let mouseX = ref(0);
let mouseY = ref(0);
const position = computed(() => {
let x;
let y;
let positionXValue = props.positionX;
let positionYValue = props.positionY;
// setPosXY();
mouseX.value = 0;
mouseY.value = 0;
const remSizeInPX = remToPixel.value;
const bodyElement = document.querySelector('body');
const bodyHeight = bodyElement.offsetHeight;
const bodyWidth = bodyElement.offsetWidth;
if (positionXValue === 'center') {
x = bodyWidth / 2 + 4 * remSizeInPX;
} else {
x = positionXValue;
}
if (positionYValue === 'center') {
y = bodyHeight / 2;
} else {
y = positionYValue;
}
console.log('position', x, y);
return {
left: x,
top: y,
};
});
// Methods
const startDrag = (event) => {
isDragging.value = true;
mouseX.value = event.clientX;
mouseY.value = event.clientY;
document.addEventListener('mousemove', dragging);
document.addEventListener('mouseup', stopDrag);
emit('onStartDrag');
};
const stopDrag = () => {
isDragging.value = false;
document.removeEventListener('mousemove', dragging);
document.removeEventListener('mouseup', stopDrag);
emit('setPosition', {
positionX: position.value.left + posX.value,
positionY: position.value.top + posY.value,
placement: props.placement,
});
emit('onStopDrag');
posX.value = 0;
posY.value = 0;
};
const dragging = (event) => {
console.log('dragging');
const remSizeInPX = remToPixel.value;
const bodyElement = document.querySelector('body');
const bodyHeight = bodyElement.offsetHeight;
const bodyWidth = bodyElement.offsetWidth;
let promptElementHeight = 2.5 * remSizeInPX;
// foyrAi prompt input element width
let promptElementWidth = 2 * remSizeInPX;
if (isDragging.value) {
let clientX = event.clientX;
let clientY = event.clientY;
if (clientX < 1 * remSizeInPX) {
clientX = 1 * remSizeInPX;
}
if (clientX > bodyWidth - promptElementWidth - 4 * remSizeInPX) {
clientX = bodyWidth - promptElementWidth - 4 * remSizeInPX;
}
if (clientY < 1 * remSizeInPX) {
clientY = 1 * remSizeInPX;
}
if (clientY > bodyHeight - promptElementHeight) {
clientY = bodyHeight - promptElementHeight;
}
const deltaX = clientX - mouseX.value;
const deltaY = clientY - mouseY.value;
posX.value += deltaX;
posY.value += deltaY;
mouseX.value = clientX;
mouseY.value = clientY;
let lastEmitTime = 0;
const throttleTime = 16;
const now = Date.now();
if (now - lastEmitTime >= throttleTime) {
emit('onDragging');
lastEmitTime = now;
}
}
};
const dragSmartBarAlong = (event) => {
const remSizeInPX = remToPixel.value;
if (props.dragAlongWithCursor) {
posX.value = event.clientX < 4 * remSizeInPX ? 5 * remSizeInPX : event.clientX + remSizeInPX;
posY.value = event.clientY < 5.375 * remSizeInPX ? 5.375 * remSizeInPX : event.clientY;
}
};
// Lifecycle Hooks
onMounted(() => {
calculateRemToPixel();
// Recalculate on resize
window.addEventListener('resize', calculateRemToPixel);
document.addEventListener('mousemove', dragSmartBarAlong);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', calculateRemToPixel);
document.removeEventListener('mousemove', dragSmartBarAlong);
});
</script>
<template>
<div ref="draggableComponent" class="basic-draggable-floating-component-wrapper" :class="placement" :style="{
left: position.left + posX + 'px',
top: position.top + posY + 'px',
}">
<!-- @mousedown="startDrag" -->
<slot name="main" v-bind:startDrag="startDrag"></slot>
</div>
</template>
<style lang="scss" scoped>
.basic-draggable-floating-component-wrapper {
position: fixed;
z-index: 999;
&.left {
transform: translate(-100%, -50%);
}
&.right {
transform: translate(0%, -50%);
}
&.top {
transform: translate(-50%, -100%);
}
&.bottom {
transform: translate(-50%, 0%);
}
}
</style>