How Reference Lines and Snap Alignment Work in the Luban H5 Editor

Reference lines and snap alignment in Luban H5 are implemented through a drag mixin that calculates distances between elements in real-time, stores guide line data in Vuex, and renders SVG overlays on the canvas while automatically snapping elements when within 5 pixels of alignment.

The Luban H5 editor is an open-source visual page builder that provides precise positioning controls through reference lines and snap alignment. These features help users align elements perfectly without manual pixel-tweaking. The implementation spans three core layers: distance calculation logic in a Vue mixin, centralized state management, and SVG-based visual rendering.

Architecture Overview

The reference line and snap alignment system follows a real-time event-driven pipeline:

  1. Drag Initiation – The drag.js mixin captures the bounding boxes of all sibling elements on the canvas
  2. Distance Calculation – During each mousemove event, the system calculates distances between the dragged element and others across six alignment points (left, centerX, right, top, middle, bottom)
  3. Snap Decision – When the distance falls below the 5-pixel threshold (SNAP_TOLERANCE), the element coordinates are adjusted to match the target alignment
  4. Visual Feedback – Guide line data is committed to the Vuex store and rendered as SVG lines overlaying the canvas
  5. Cleanup – On mouseup, the reference lines are cleared from state

Core Implementation Files

Distance Calculation and Snap Logic

The heart of the system lives in src/components/core/mixins/drag.js. This mixin handles the mathematical comparison of element positions and applies the snap transformation.

// src/components/core/mixins/drag.js
export default {
  data () {
    return {
      snapTolerance: 5,  // px threshold for snapping
      otherBoxes: []     // cached bounding boxes of sibling elements
    }
  },
  methods: {
    onDragStart () {
      // Collect bounding boxes of all other visible elements
      this.otherBoxes = this.$store.getters['editor/otherElementBoxes'](this.element.id)
    },
    onDrag (e) {
      const curBox = this.getBoxFromEvent(e)
      const lines = []

      this.otherBoxes.forEach(box => {
        // X-axis alignment checks: left, center, right
        ;['left', 'centerX', 'right'].forEach(k => {
          const diff = Math.abs(curBox[k] - box[k])
          if (diff <= this.snapTolerance) {
            lines.push({ orientation: 'v', offset: box[k] })  // vertical guide
            curBox[k] = box[k]                               // snap to alignment
          }
        })
        // Y-axis checks (top, middle, bottom) follow identical pattern
      })

      // Update store to trigger guide line rendering
      this.$store.commit('editor/SET_REFERENCE_LINES', lines)
      this.updateElementPosition(curBox)
    },
    onDragEnd () {
      this.$store.commit('editor/CLEAR_REFERENCE_LINES')
    }
  }
}

State Management

The Vuex module in src/store/modules/editor.js maintains the transient state of reference lines during drag operations.

// src/store/modules/editor.js
const state = () => ({
  referenceLines: []  // Array of { orientation: 'v'|'h', offset: Number }
})

const mutations = {
  SET_REFERENCE_LINES (state, lines) {
    state.referenceLines = lines
  },
  CLEAR_REFERENCE_LINES (state) {
    state.referenceLines = []
  }
}

export default { state, mutations }

Visual Rendering

The actual drawing of guide lines occurs in src/components/core/editor/canvas/edit.vue, which renders an SVG overlay on top of the canvas.

<!-- src/components/core/editor/canvas/edit.vue -->
<template>
  <div class="canvas-wrapper">
    <!-- Canvas content -->
    
    <svg class="reference-lines" v-if="referenceLines.length">
      <line v-for="(ln, i) in referenceLines"
            :key="i"
            :x1="ln.orientation === 'v' ? ln.offset : 0"
            :y1="ln.orientation === 'h' ? ln.offset : 0"
            :x2="ln.orientation === 'v' ? ln.offset : canvasWidth"
            :y2="ln.orientation === 'h' ? ln.offset : canvasHeight"
            class="guide-line"/>
    </svg>
  </div>
</template>

<script>
export default {
  computed: {
    referenceLines () {
      return this.$store.state.editor.referenceLines
    }
  }
}
</script>

<style scoped>
.guide-line {
  stroke: #b4b4b4;
  stroke-width: 1;
  stroke-dasharray: 4 2;
}
</style>

The Snap Algorithm in Detail

The snap alignment algorithm evaluates six distinct alignment points across both axes:

Horizontal alignment points:

  • left: Left edge of the element
  • centerX: Horizontal center
  • right: Right edge

Vertical alignment points:

  • top: Top edge
  • middle: Vertical center
  • bottom: Bottom edge

For each alignment point, the algorithm calculates the absolute distance between the dragged element and every other element on the canvas. When the distance is less than or equal to the 5-pixel tolerance threshold, two actions occur simultaneously:

  1. Visual feedback: A reference line is generated with the orientation (v for vertical, h for horizontal) and the exact pixel offset where alignment occurs
  2. Position correction: The dragged element's coordinate is snapped to exactly match the target alignment point, ensuring pixel-perfect positioning

This process repeats for every mousemove event during the drag operation, providing real-time feedback and automatic correction.

Summary

  • Reference lines and snap alignment in Luban H5 are implemented through a coordinated system spanning a Vue mixin, Vuex store, and SVG overlay components.
  • The drag mixin (src/components/core/mixins/drag.js) handles real-time distance calculations and applies the 5-pixel snap tolerance.
  • Vuex mutations (SET_REFERENCE_LINES, CLEAR_REFERENCE_LINES) manage the transient state of guide lines during drag operations.
  • SVG rendering in edit.vue displays dashed gray lines that indicate alignment relationships without interfering with the canvas content.
  • The system evaluates six alignment points (left, centerX, right, top, middle, bottom) across both axes to provide comprehensive snapping behavior.

Frequently Asked Questions

What is the default snap tolerance in Luban H5?

The default snap tolerance is 5 pixels. This threshold is defined in the drag.js mixin as snapTolerance: 5. When the distance between the dragged element and a target alignment point is 5 pixels or less, the element automatically snaps to that position and a reference line appears.

How are reference lines rendered on the canvas?

Reference lines are rendered as SVG elements in an overlay component. The edit.vue file watches the Vuex store for referenceLines state changes and renders <line> elements with dashed strokes (stroke-dasharray: 4 2). Vertical lines span the full canvas height at specific X offsets, while horizontal lines span the full width at specific Y offsets.

Can the snap alignment be disabled or customized?

While the source code shows a hardcoded snapTolerance: 5 in the drag mixin, the architecture supports customization through Vuex state or component props. The tolerance value is stored in the component's data object, making it accessible for modification. However, the current implementation in src/components/core/mixins/drag.js uses a fixed 5-pixel threshold without exposed configuration options in the UI.

Which alignment points does the snap system recognize?

The system recognizes six distinct alignment points across both axes. On the X-axis, it checks left, centerX (horizontal center), and right. On the Y-axis, it checks top, middle (vertical center), and bottom. This comprehensive coverage allows elements to snap to edges, centers, and midpoints of other elements on the canvas.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →