Skip to content

Draggable Items

Code

vue
<script setup lang="ts">
  import { ref } from 'vue';

  const items = ref([
    { id: 1, group: 1, type: 'range', cssVariables: { '--item-background': 'var(--color-2)' }, start: 1000000, end: 4500000 },
    { id: 2, group: 2, type: 'range', cssVariables: { '--item-background': 'var(--color-4)' }, start: 4500000, end: 6000000 },
    { id: 3, group: 3, type: 'range', start: 6000000, end: 8000000 },
  ]);

  let previousDragTimePos = 0;
  let currentDragAction: 'resize-start' | 'resize-both' | 'resize-end' | undefined;
  let currentDragItemId = null;

  function handleItemDrag ({ time, event, item }) {
    if (event.type === 'pointerdown') {
      if (!event.target.dataset.action) {
        return;
      }

      currentDragAction = event.target.dataset.action as typeof currentDragAction;
      currentDragItemId = item.id;
      previousDragTimePos = time;
    }
    else if (event.type === 'pointermove') {
      if (!currentDragAction) {
        return;
      }

      const foundItem = items.value.find(i => i.id === currentDragItemId)!;
      const delta = time - previousDragTimePos;

      if (currentDragAction === 'resize-start' || currentDragAction === 'resize-both') {
        foundItem.start += delta;
      }
      if (currentDragAction === 'resize-end' || currentDragAction === 'resize-both') {
        foundItem.end += delta;
      }

      previousDragTimePos = time;
    }
  }

  window.addEventListener('pointerup', () => {
    currentDragAction = undefined;
  }, { capture: true });

</script>

<template>
  <Timeline
    :items="items"
    :groups="[{id: 1}, {id: 2}, {id: 3}]"
    :viewportMin="0"
    :viewportMax="8000000"
    @pointermove="handleItemDrag"
    @pointerdown="handleItemDrag"
  >
    <template #item>
      <div class="draggable" data-action="resize-both">
        <div class="draggable-handle" data-action="resize-start"></div>
        <div class="draggable-handle" data-action="resize-end"></div>
      </div>
    </template>
  </Timeline>
</template>

<style lang="scss" scoped>
  .draggable {
    position: absolute;
    inset: 0;
    display: flex;
    justify-content: space-between;
    cursor: move;

    .draggable-handle {
      position: relative;
      width: 1.2rem;
      height: 100%;
      cursor: ew-resize;
      opacity: .6;

      &::before {
        content: '';
        border-inline: 1px solid white;
        width: 4px;
        height: 40%;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        position: absolute;
      }

      &:hover {
        opacity: 1;
      }
    }
  }
</style>