Analysis of Key Technical Principles for a Visual Drag-and-Drop Component Library - 3

This article is the third in the visual drag-and-drop series. The previous two articles analyzed the technical principles of 17 features: Editor Custom components Drag and drop Delete components, adjust layer hierarchy Zoom in and out Undo, redo Component property settings Snap Preview, save code Bind events Bind animations Import PSD Mobile mode Drag rotation Copy, paste, and cut Data interaction Publish Building on this foundation, this article will analyze the technical principles of the following features: Combining and splitting multiple components Text component Rectangle component Lock component Shortcuts Grid lines Another implementation method for editor snapshots If you're not familiar with my previous two articles, I recommend reading them first before continuing with this one: Technical Analysis of Key Points for Visual Drag-and-Drop Component Library Technical Analysis of Key Points for Visual Drag-and-Drop Component Library (Part 2) Although my visual drag-and-drop component library is just a DEMO, compared to some existing products on the market (such as processon, Modao), it implements most of the basic functions. If you're interested in low-code platforms but aren't familiar with them, I strongly recommend reading my three articles along with the project source code. I'm sure you'll gain a lot. Here are the project and online DEMO links: Project repository Online DEMO 18. Combining and splitting multiple components There are relatively more technical points for combining and splitting, with the following 4 aspects: Selection area Movement and rotation after combination Scaling after combination Restoring child component styles after splitting Selection area Before combining multiple components, we need to select them first. Using mouse events, we can easily display the selection area: mousedown records the starting coordinates mousemove calculates the movement area using the current coordinates and starting coordinates If the mouse is pressed and moved toward the upper left, we need to set the current coordinates as the starting coordinates and then calculate the movement area // Get the editor's displacement information const rectInfo = this.editor.getBoundingClientRect() this.editorX = rectInfo.x this.editorY = rectInfo.y const startX = e.clientX const startY = e.clientY this.start.x = startX - this.editorX this.start.y = startY - this.editorY // Show selection area this.isShowArea = true const move = (moveEvent) => { this.width = Math.abs(moveEvent.clientX - startX) this.height = Math.abs(moveEvent.clientY - startY) if (moveEvent.clientX { const rectInfo = $(`#component${item.id}`).getBoundingClientRect() style.left = rectInfo.left - this.editorX style.top = rectInfo.top - this.editorY style.right = rectInfo.right - this.editorX style.bottom = rectInfo.bottom - this.editorY if (style.left bottom) bottom = style.bottom }) } else { style = getComponentRotatedStyle(component.style) } if (style.left bottom) bottom = style.bottom }) this.start.x = left this.start.y = top this.width = right - left this.height = bottom - top // Set displacement and size information for the selected area and the component data within the area this.$store.commit('setAreaData', { style: { left, top, width: this.width, height: this.height, }, components: areaData, }) }, getSelectArea() { const result = [] // Area starting coordinates const { x, y } = this.start // Calculate all component data, determine if they are in the selected area this.componentData.forEach(component => { if (component.isLock) return const { left, top, width, height } = component.style if (x [], }, element: { type: Object, }, }, created() { const parentStyle = this.element.style this.propValue.forEach(component => { // component.groupStyle's top left are positions relative to the group component // If component.groupStyle already exists, it means it has been calculated once. No need to calculate again if (!Object.keys(component.groupStyle).length) { const style = { ...component.style } component.groupStyle = getStyle(style) component.groupStyle.left = this.toPercent((style.left - parentStyle.left) / parentStyle.width) component.groupStyle.top = this.toPercent((style.top - parentStyle.top) / parentStyle.height) component.groupStyle.width = this.toPercent(style.width / parentStyle.width) component.groupStyle.height = this.toPercent(style.height / parentStyle.height) } }) }, methods: { toPercent(val) {

Apr 13, 2025 - 16:35
 0
Analysis of Key Technical Principles for a Visual Drag-and-Drop Component Library - 3

This article is the third in the visual drag-and-drop series. The previous two articles analyzed the technical principles of 17 features:

  1. Editor
  2. Custom components
  3. Drag and drop
  4. Delete components, adjust layer hierarchy
  5. Zoom in and out
  6. Undo, redo
  7. Component property settings
  8. Snap
  9. Preview, save code
  10. Bind events
  11. Bind animations
  12. Import PSD
  13. Mobile mode
  14. Drag rotation
  15. Copy, paste, and cut
  16. Data interaction
  17. Publish

Building on this foundation, this article will analyze the technical principles of the following features:

  1. Combining and splitting multiple components
  2. Text component
  3. Rectangle component
  4. Lock component
  5. Shortcuts
  6. Grid lines
  7. Another implementation method for editor snapshots

If you're not familiar with my previous two articles, I recommend reading them first before continuing with this one:

Although my visual drag-and-drop component library is just a DEMO, compared to some existing products on the market (such as processon, Modao), it implements most of the basic functions.

If you're interested in low-code platforms but aren't familiar with them, I strongly recommend reading my three articles along with the project source code. I'm sure you'll gain a lot. Here are the project and online DEMO links:

18. Combining and splitting multiple components

There are relatively more technical points for combining and splitting, with the following 4 aspects:

  • Selection area
  • Movement and rotation after combination
  • Scaling after combination
  • Restoring child component styles after splitting

Selection area

Before combining multiple components, we need to select them first. Using mouse events, we can easily display the selection area:

  1. mousedown records the starting coordinates
  2. mousemove calculates the movement area using the current coordinates and starting coordinates
  3. If the mouse is pressed and moved toward the upper left, we need to set the current coordinates as the starting coordinates and then calculate the movement area
// Get the editor's displacement information
const rectInfo = this.editor.getBoundingClientRect()
this.editorX = rectInfo.x
this.editorY = rectInfo.y

const startX = e.clientX
const startY = e.clientY
this.start.x = startX - this.editorX
this.start.y = startY - this.editorY
// Show selection area
this.isShowArea = true

const move = (moveEvent) => {
    this.width = Math.abs(moveEvent.clientX - startX)
    this.height = Math.abs(moveEvent.clientY - startY)
    if (moveEvent.clientX < startX) {
        this.start.x = moveEvent.clientX - this.editorX
    }

    if (moveEvent.clientY < startY) {
        this.start.y = moveEvent.clientY - this.editorY
    }
}

When the mouseup event is triggered, we need to calculate the displacement and size information of all components in the selected area to obtain a minimum area that can contain all components in the region. The effect is shown in the following figure:

The code for this calculation process:

createGroup() {
  // Get component data in the selected area
  const areaData = this.getSelectArea()
  if (areaData.length <= 1) {
      this.hideArea()
      return
  }

  // Create a Group component based on the selected area and the displacement information of each component in the area
  // Need to traverse each component in the selected area, get their left top right bottom information for comparison
  let top = Infinity, left = Infinity
  let right = -Infinity, bottom = -Infinity
  areaData.forEach(component => {
      let style = {}
      if (component.component == 'Group') {
          component.propValue.forEach(item => {
              const rectInfo = $(`#component${item.id}`).getBoundingClientRect()
              style.left = rectInfo.left - this.editorX
              style.top = rectInfo.top - this.editorY
              style.right = rectInfo.right - this.editorX
              style.bottom = rectInfo.bottom - this.editorY

              if (style.left < left) left = style.left
              if (style.top < top) top = style.top
              if (style.right > right) right = style.right
              if (style.bottom > bottom) bottom = style.bottom
          })
      } else {
          style = getComponentRotatedStyle(component.style)
      }

      if (style.left < left) left = style.left
      if (style.top < top) top = style.top
      if (style.right > right) right = style.right
      if (style.bottom > bottom) bottom = style.bottom
  })

  this.start.x = left
  this.start.y = top
  this.width = right - left
  this.height = bottom - top

  // Set displacement and size information for the selected area and the component data within the area
  this.$store.commit('setAreaData', {
      style: {
          left,
          top,
          width: this.width,
          height: this.height,
      },
      components: areaData,
  })
},

getSelectArea() {
    const result = []
    // Area starting coordinates
    const { x, y } = this.start
    // Calculate all component data, determine if they are in the selected area
    this.componentData.forEach(component => {
        if (component.isLock) return
        const { left, top, width, height } = component.style
        if (x <= left && y <= top && (left + width <= x + this.width) && (top + height <= y + this.height)) {
            result.push(component)
        }
    })

    // Return all components in the selected area
    return result
}

Let me briefly describe the processing logic of this code:

  1. Use the getBoundingClientRect() browser API to get information about each component in four directions relative to the browser viewport, which are left top right bottom.
  2. Compare these four pieces of information for each component to obtain the leftmost, topmost, rightmost, and bottommost values, thereby deriving a minimum area that can contain all components in the region.
  3. If there is already a Group component in the selected area, we need to calculate its child components rather than calculating the combined component.

Movement and rotation after combination

To facilitate moving, rotating, scaling, and other operations on multiple components together, I created a new Group combined component: