Frontend Development 15 min read

Building a Vue‑Based Visual Editor: Architecture, JSON Schema & Drag‑Drop

This article outlines the design and implementation of a Vue‑powered visual editor, covering its three‑part architecture (editor, component library, backend), JSON schema hierarchy, component creation and configuration, state management with Vuex, drag‑and‑drop mechanics, and server‑side rendering.

37 Mobile Game Tech Team
37 Mobile Game Tech Team
37 Mobile Game Tech Team
Building a Vue‑Based Visual Editor: Architecture, JSON Schema & Drag‑Drop

Background

Frontend visual editors are implemented in various ways in large companies, with different businesses assigning different functionalities; see https://github.com/taowen/awesome-lowcode for many Chinese low‑code platform implementations. Although there are many AI‑driven page slicing and generation cases, achieving rapid output requires substantial manpower and effort, so it is not recommended for typical teams.

Purpose

Designing such a visual editor aims to achieve the following goals:

Visual editing with drag‑and‑drop on the canvas.

Rich style configuration.

Extract common business logic into reusable components with configurable parameters.

Design Approach

The project is divided into three parts:

Editor – provides project management, element dragging, parameter configuration, preview and publishing capabilities.

Component Library – encapsulates business logic into a maintainable component library.

Backend – stores project data and parses the JSON produced by the editor to generate pages.

Tech Stack

The team is familiar with Vue, so both the editor and component library are built with Vue.

The backend uses egg.js, a mature framework that avoids building from scratch, though other frameworks are also possible.

Technology choices can be adjusted according to personal or team preferences.

Design Overview

1. JSON Schema

The JSON schema is the foundation of the project and is generated by the editor. It must keep a consistent format so the backend can parse and render the page.

1. Project Level

Project‑level JSON describes basic project information.

<code>{
  id: '', // project unique identifier
  name: '', // project name
  label: [], // project tags
  description: '', // project description
  author: '', // author
  pages: [] // pages
}</code>

2. Page Level

Page‑level JSON corresponds to an HTML page, including scripts, styles, and elements.

<code>{
  id: '', // page unique identifier
  type: '', // page type
  route: '', // page route
  title: '', // page title
  style: {
    width: '',
    height: '',
    ...
  },
  elements: [], // components
  plugins: [] // page plugins
}</code>

3. Component Level

Components are the building blocks of a page.

<code>{
  id: '', // component unique identifier
  elementName: '', // component name
  style: {
    position: '',
    width: '',
    height: '',
    border: ''
  },
  props: {}, // component props
  children: [], // child components
  ...
}</code>

The

props

concept mirrors Vue’s props, allowing components to render according to supplied parameters.

2. Component Library

Components are essential page bricks. Besides basic components (text, image, video), business‑specific components (carousel, navigation) must be abstracted into reusable modules. Building a component library requires two code bases: the component itself and a configuration panel for its props.

1. Component

A simple video component example:

<code>&lt;template&gt;
  &lt;video :src="src" :autoplay="autoplay" /&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
  name: 'aicc-video',
  props: {
    src: { type: String, default: '' },
    autoplay: { type: String, default: '' }
  }
}
&lt;/script&gt;</code>

Importing the component in the editor:

<code>import AICCVideo from '@/components/AICCVideo'</code>

Importing the component on the server (as a script):

<code>&lt;script src="https://.../aiccvideo.min.js"&gt;&lt;/script&gt;</code>

2. Configuration Panel

The panel generates custom JSON data for the editor. Using the video component as an example:

<code>&lt;template&gt;
  &lt;div class="props-video"&gt;
    &lt;input v-model="propsValue.src" /&gt;
    &lt;input v-model="propsValue.autoplay" /&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
  name: 'props-video',
  props: {
    value: { type: Object, default: () => ({}) }
  },
  data() { return { propsValue: {} } },
  watch: {
    value(val) { this.propsValue = val },
    propsValue(val) { this.$emit('input', val) }
  }
}
&lt;/script&gt;</code>

The panel is registered in the editor just like a component, with

v-model

binding the component’s props.

3. Editor

The editor’s core task is to modify the JSON schema. Vuex is used for state management because it fits medium‑to‑large Vue projects.

Vuex is a state‑management pattern and library for Vue.js applications.

Key store snippets:

<code>const state = {
  projectData: {}, // project JSON data
  activePageUUID: '', // currently edited page
  activeElementUUID: '' // currently selected component
}

export function initProject({ commit, state }, data) {
  let projectData = data
  if (!data) projectData = initProject() // generate default JSON for empty project
  dispatch('setActivePageUUID', projectData.pages[0].uuid)
}

export function setActivePageUUID({ commit }, uuid) { state.activePageUUID = uuid }
export function setActiveElementUUID({ commit }, uuid) { state.activeElementUUID = uuid }

export function activePage(state) {
  const idx = state.projectData.pages.findIndex(p => state.activePageUUID === p.uuid)
  return state.projectData.pages[idx]
}

export function activeElement(state) {
  const pIdx = state.projectData.pages.findIndex(p => state.activePageUUID === p.uuid)
  const activePage = state.projectData.pages[pIdx]
  const eIdx = activePage.elements.findIndex(e => state.activeElementUUID === e.uuid)
  // return the active element (omitted for brevity)
}
</code>

Components bind to the store via

mapGetters

and

mapState

to obtain the current page, element, and project data.

<code>&lt;template&gt;
  &lt;div&gt;
    &lt;component :is="activeElement.elementName" v-model="activeElement.props" /&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
import { mapGetters, mapState } from 'vuex'
export default {
  computed: {
    ...mapGetters({ activeElement: 'editor/activeElement' }),
    ...mapState({
      pages: state => state.editor.projectData.pages,
      activePageUUID: state => state.editor.activePageUUID,
      activeElementUUID: state => state.editor.activeElementUUID
    })
  }
}
&lt;/script&gt;</code>

1. Drag‑and‑Drop Mechanics

To enable element dragging on the canvas:

Listen for

mousedown

on the target element.

Record the element’s offsetTop/offsetLeft and the mouse coordinates.

On

mousemove

, calculate the distance moved and compute new coordinates.

On

mouseup

, remove listeners and optionally apply the final position.

<code>function mousedown(e) {
  let newTop = null
  let newLeft = null
  const cTop = e.currentTarget.offsetTop
  const cLeft = e.currentTarget.offsetLeft
  const mouseX = e.clientX
  const mouseY = e.clientY

  const move = mEvent => {
    mEvent.stopPropagation()
    mEvent.preventDefault()
    const cX = mEvent.clientX
    const cY = mEvent.clientY
    const distanceX = cX - mouseX
    const distanceY = cY - mouseY
    newTop = distanceX + cTop
    newLeft = distanceY + cLeft
  }
  const up = () => {
    document.removeEventListener('mousemove', move, true)
    document.removeEventListener('mouseup', up, true)
  }
  document.addEventListener('mousemove', move, true)
  document.addEventListener('mouseup', up, true)
}
</code>

For performance, use

transform

during

mousemove

and apply the final

top/left

on

mouseup

.

4. Server‑Side Rendering

The JSON produced by the editor is parsed on the server to generate a structured page using Vue’s render function.

Generate page title/SEO info from page data and create a

${name}.html

file based on the route.

Include the bundled component library via a script tag.

Since the render function does not need a compiler, load

vue.runtime.js

only.

Use a template engine to replace placeholders with page info and component arrays.

Iterate over

elements

and call

createElement('ComponentName', { props: {...elementJSON} })

.

Convert style JSON into CSS rules like

selector { key: value }

.

Write the final HTML string to disk with

fs.writeFileSync(filePath, htmlString)

.

Conclusion

This article provides a high‑level overview of how to assemble a Vue‑based visual editor, covering architecture, JSON schema design, component creation, state management, drag‑and‑drop implementation, and server‑side rendering. Detailed implementation of each module can be expanded into separate topics such as component packaging, monorepo management with Lerna, real‑time resizing, data validation, and plugin systems like WeChat sharing.

JSON SchemaVuecomponent libraryServer-side RenderingVisual Editordrag-and-drop
37 Mobile Game Tech Team
Written by

37 Mobile Game Tech Team

37 Mobile Game Tech Team

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.