Drag the mouse to create a selection and select the element

Welcome to personal blog website: Firefly forest: https://blog.xkongkeji.com

Recently, to make an editor, you need a function of dragging and dropping the selection area with the mouse to facilitate batch selection of elements and corresponding operations. All of you have this article.

Effect display


Create selection component

1. To select an element, you must first create a selection

  • Determine the location of the selected area according to two coordinate points and draw the selected area
  • Delete the width and height of the selection according to the two coordinates
let size = computed(() => {
      let width =
        props.endPoint.x === 0
          ? 0
          : Math.abs(props.startPoint.x - props.endPoint.x);
      let height =
        props.endPoint.y === 0
          ? 0
          : Math.abs(props.startPoint.y - props.endPoint.y);
      return {
        width,
        height,
      };
  • Determine the starting coordinate point
    • No matter where you click, you need to find the coordinate point in the upper left corner of the rectangle drawn by the two coordinate points.
    • The coordinate point in the upper left corner is obviously the minimum value of all coordinates, that is, the point where x and Y take the minimum value (as follows)
    • It is also necessary to consider the case when the end point has not been generated, that is, excluding the case where the end point is the initial value, that is, 0
 let Point = computed(() => {
      let x =
        props.endPoint.x === 0
          ? props.startPoint.x
          : Math.min(props.startPoint.x, props.endPoint.x);
      let y =
        props.endPoint.y === 0
          ? props.startPoint.y
          : Math.min(props.startPoint.y, props.endPoint.y);
      return {
        x,
        y,
      };
    });zaige
  • This will draw a selection

2. Filter out the selected elements according to the selected area

  • The element of the selected selection is mainly to select all selectable element nodes in the editing area according to nodeType.
  • How to calculate optional elements depends on your needs. I marked canChecked on the element node to exclude non optional elements through this attribute
 * Gets the collection of elements that can be selected under the element
 * @param parentElement Parent element
 * @param keyCode Optional element identification
 * @returns
 */
function getChildrens(parentElement: HTMLElement, keyCode: string) {
  const ary = [];
  const childs = parentElement.childNodes;
  for (let i = 0; i < childs.length; i++) {
    if (childs[i].nodeType === 1) {
      if ((childs[i] as HTMLElement).getAttribute(keyCode) !== null) {
        ary.push(childs[i]);
      }
    }
  }
  return ary as Array<HTMLElement>;
}

3. Judge whether the node is in the selected area

  • This is mainly based on the information returned by the getBoundingClientRect method

  • If the top and left of the selected area are less than the judgment element and the bottom and are greater than the judgment element, it is considered that the element is in the selected area.
/**
 * Determine whether the element is in the selection
 * @param selectBoxElement Selection element
 * @param canCheckedElements  List of selectable elements
 */
function judgeContainElement(
  selectBoxElement: HTMLElement,
  canCheckedElements: Array<HTMLElement>
) {
  const ContainElement: Array<HTMLElement> = [];
  const { left, right, bottom, top } = selectBoxElement.getBoundingClientRect();
  canCheckedElements.forEach((item) => {
    const child = item.getBoundingClientRect();
    if (
      child.left > left &&
      child.top > top &&
      child.bottom < bottom &&
      child.right < right
    ) {
      ContainElement.push(item);
    }
  });
  return ContainElement;
}

4. So far, we can get the selected elements in the selection, and then we can do the required operations on the selected elements, that is, as shown in the effect picture/

The complete code is attached

1. Component infrastructure file

<template>
  <div
    id="select-area"
    class="select-area"
    :style="[
      { width: size.width + 'px' },
      { height: size.height + 'px' },
      { top: Point.y + 'px' },
      { left: Point.x + 'px' },
    ]"
  ></div>
</template>

<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
interface Point {
  x: number;
  y: number;
}
export default defineComponent({
  name: 'SelectArea',
  props: {
    startPoint: {
      type: Object as PropType<Point>,
      required: true,
    },
    endPoint: {
      type: Object as PropType<Point>,
      required: true,
    },
  },
  setup(props) {
    let Point = computed(() => {
      let x =
        props.endPoint.x === 0
          ? props.startPoint.x
          : Math.min(props.startPoint.x, props.endPoint.x);
      let y =
        props.endPoint.y === 0
          ? props.startPoint.y
          : Math.min(props.startPoint.y, props.endPoint.y);
      return {
        x,
        y,
      };
    });
    let size = computed(() => {
      let width =
        props.endPoint.x === 0
          ? 0
          : Math.abs(props.startPoint.x - props.endPoint.x);
      let height =
        props.endPoint.y === 0
          ? 0
          : Math.abs(props.startPoint.y - props.endPoint.y);
      return {
        width,
        height,
      };
    });
    return {
      Point,
      size,
    };
  },
});
</script>
<style lang="less" scoped>
.select-area {
  position: fixed;
  background-color: rgba(255, 192, 203, 0.1);
  border: 1px solid red;
  z-index: 9;
}
</style>

2. Export component files

import { createVNode, render } from 'vue';
import SelectAreaConstructor from './SelectArea.vue';
let instence: HTMLElement | undefined;
let instenceIsExit = false;
const SelectArea = function(options: any) {
  if (instenceIsExit) {
    document.body.removeChild(instence as HTMLElement);
    instenceIsExit = false;
  }
  const vm = createVNode(SelectAreaConstructor, options);
  const container = document.createElement('div');
  render(vm, container);
  instence = container.firstElementChild as HTMLElement;
  document.body.appendChild(instence);
  instenceIsExit = true;
  return instence;
};

const close = () => {
  if (instenceIsExit) {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    instenceIsExit = false;
    document.body.removeChild(instence as HTMLElement);
    instence = undefined;
  }
};
export { SelectArea, close };

3. Application file setup section

 setup() {
    let selectProps = reactive({
      startPoint: {
        x: 0,
        y: 0,
      },
      endPoint: {
        x: 0,
        y: 0,
      },
    });
    let mouseKey = ref(false); // Whether to monitor mouse movement (move out of the editing area and no longer monitor mouse movement events)
    let mouseComplete = ref(false); // Whether the mouse movement event is completed (the process from mouse pressing to lifting)
    const headleMouseDown = (e: MouseEvent) => {
      close();
      selectProps.startPoint.x = e.clientX;
      selectProps.startPoint.y = e.clientY;
      SelectArea(selectProps);
      mouseKey.value = true;
      mouseComplete.value = false;
    };
    const headleMouseMove = (e: MouseEvent) => {
      if (mouseKey.value && !mouseComplete.value) {
        selectProps.endPoint.x = e.clientX;
        selectProps.endPoint.y = e.clientY;
        const div = document.querySelector('#select-area');
        const parent = document.querySelector('.edit-area');
        const containDiv = selectElement(
          parent as HTMLElement,
          div as HTMLElement,
          'canChecked'
        );
        containDiv.canCheckedElements.forEach((item) => {
          item.style.border = 'none';
        });
        containDiv.containElements.forEach((item) => {
          item.style.border = '1px solid red';
          item.style.cursor = 'move';
        });
      }
    };
    const headleDrag = (e: MouseEvent) => {
      // Prevent conflicts with drag events
      e.preventDefault();
    };
    const headleMouseUp = () => {
      mouseKey.value = false;
      mouseComplete.value = true;
      selectProps.startPoint.x = 0;
      selectProps.startPoint.y = 0;
      selectProps.endPoint.x = 0;
      selectProps.endPoint.y = 0;
      close();
    };
    window.addEventListener('mousedown', headleMouseDown);
    window.addEventListener('mousemove', headleMouseMove);
    window.addEventListener('mouseup', headleMouseUp);
    onUnmounted(() => {
      window.removeEventListener('mousedown', headleMouseDown);
      window.removeEventListener('mousemove', headleMouseMove);
      window.removeEventListener('mouseup', headleMouseUp);
    });
    const saveWeekly = () => {
      console.log('click');
    };
    return {
      headleMouseDown,
      headleMouseMove,
      headleMouseUp,
      headleDrag,
      saveWeekly,
    };
  },

4. Helper function file

/**
 * Gets the collection of elements that can be selected under the element
 * @param parentElement Parent element
 * @param selectBoxElement Select box element
 * @param keyCode Optional element identification
 * @returns
 */
function selectElement(
  parentElement: HTMLElement,
  selectBoxElement: HTMLElement,
  keyCode: string
) {
  if (keyCode) {
  }
  const canCheckedElements = getChildrens(parentElement, keyCode);
  const containElements = judgeContainElement(
    selectBoxElement,
    canCheckedElements
  );
  return {
    containElements,
    canCheckedElements,
  };
}
export { selectElement };

/**
 *
 * Gets the collection of elements that can be selected under the element
 * @param parentElement Parent element
 * @param keyCode Optional element identification
 * @returns
 */
function getChildrens(parentElement: HTMLElement, keyCode: string) {
  const ary = [];
  const childs = parentElement.childNodes;
  for (let i = 0; i < childs.length; i++) {
    if (childs[i].nodeType === 1) {
      if ((childs[i] as HTMLElement).getAttribute(keyCode) !== null) {
        ary.push(childs[i]);
      }
    }
  }
  return ary as Array<HTMLElement>;
}
function judgeContainElement(
  selectBoxElement: HTMLElement,
  canCheckedElements: Array<HTMLElement>
) {
  const ContainElement: Array<HTMLElement> = [];
  const { left, right, bottom, top } = selectBoxElement.getBoundingClientRect();
  canCheckedElements.forEach((item) => {
    const child = item.getBoundingClientRect();
    if (
      child.left > left &&
      child.top > top &&
      child.bottom < bottom &&
      child.right < right
    ) {
      ContainElement.push(item);
    }
  });
  return ContainElement;
}

Keywords: Front-end TypeScript Vue

Added by halojoy on Mon, 17 Jan 2022 09:34:42 +0200