How No‑Code Platforms Transform Frontend Development: Architecture, Canvas & Drag‑Drop Techniques
This article explores the rise of no‑code/low‑code builder platforms, detailing their core concepts, architecture, UIDL specifications, sandboxing, drag‑and‑drop mechanics, smart snapping, component insertion, and event handling, offering practical insights for frontend engineers building modern visual editors.
What Is a Builder Platform?
Before introducing builder platforms, the unavoidable theme is NCLC (No Code & Low Code). NCLC, as the core concept behind site‑building products, has driven successive waves of technology.
Recent years have seen various products around NCLC, such as project management Meego, low‑code system Retool, online document Notion, and even a division into 12 development tracks.
The most rapidly growing and representative product form in the industry is the site‑building category. Classic examples include early Dreamweaver, the billion‑dollar SaaS Webflow, and various domestic builder platforms.
Builder platforms are popular because they solve two major enterprise pain points: development efficiency and staff transformation.
Development efficiency: Builder platforms, like IDEs, aim to boost productivity, but they focus on "reuse and composition" rather than raw business logic. Drag‑and‑drop components replace traditional coding.
For example, creating a poster‑style activity page with many images and animations would take days with code, but with a builder platform it can be done by dragging and configuring assets once.
In short, builder platforms provide a new development paradigm that hides complexity for specific scenarios such as e‑commerce activities, marketing promotions, and back‑office tools.
Staff transformation: In the fast‑changing market, digital transformation is widely accepted, and NCLC enables employees without programming experience to build applications.
Among builder platform users, non‑technical operations staff are the most common; they can generate required pages without developer involvement, reducing resource waste and improving workflow efficiency.
Derived Concepts
During development, builder platforms have spawned many concepts. The core can be summarized as:
Builder platform = Editor (canvas + configurator) + Generator, with assets as materials and UIDL as the communication protocol.
The editor and generator are decoupled via UIDL, so page types depend only on the used materials.
Editor renders front‑end material and produces UIDL.
Generator consumes UIDL and, based on predefined templates, produces the final page.
This layered design separates responsibilities, allowing each layer to evolve independently.
Architecture Design
IOC Architecture
Modules are coupled through an IOC architecture: each module defines its usage contract, and the container injects dependencies at runtime. This makes modules independent of concrete implementations and facilitates independent iteration and extensibility.
For example, different terminals (TV, mobile) share the same canvas functionality but differ in resolution and focus handling; IOC allows these differences to be added as extensions.
Overall, IOC provides a plugin‑friendly, extensible design.
Editor‑Renderer Layer Separation
The canvas is split into a rendering layer and an editing layer, communicating via a protocol to stay decoupled.
Users interact with the editing layer; interactions are reflected on the rendering layer.
When a component is selected, eight anchor points appear on the rendering layer.
Page preview can be achieved by removing the editor’s mask for instant preview.
Interaction experience improves because anchor points exist only on the rendering layer.
The design cleanly separates editing specifications from rendering specifications.
interface ComponentAddAction {}
interface ComponentDragAction {}
... interface ComponentFocusRenderer {}
interface ComponentMirrorRenderer {}
...Some implementations use an iframe to separate rendering and editing, providing sandboxing and easier multi‑user collaboration.
Iframe offers natural sandboxing, preventing style and logic leakage.
Multi‑user editing can switch from iframe communication to WebSocket.
Event System
The platform builds a comprehensive event system, where each functional module can consume events corresponding to editing actions, rendering actions, or global actions.
Typical events include:
Init event on page load triggers side‑bar material loading, canvas rendering, and dependency loading.
dragEnd event on component drop triggers material loading, configurator parsing, and canvas cleanup.
Specification Design
Design around concrete scenarios; avoid blind feature stacking.
Specification first: standards drive the editor.
Two key specifications are covered next: UIDL and Material specifications.
UIDL Specification
UIDL: Describes all UI‑related structured information when building a page.
Originally proposed by teleporthq, UIDL is a JSON‑based universal format that enables:
Generating the same UI with various tools and frameworks.
Seamless transition between code outputs.
Advanced programmatic manipulation.
interface ComponentProps { id: string; [key: string]: any; }
interface Schema { id: string; type: MaterialId; name: string; children: Schema[]; props: ComponentProp; }
interface UIDL { meta: { version: string }; project: { id: string; title: string; version: string; author: User; url: string }; schema: Schema; materials: { components: Array<Component> }; }Material Specification
Material: Describes the components, plugins, and actions needed to enrich a page.
Materials are classified by:
Terminal: mobile web, mini‑program, TV, etc.
Form: component, plugin, action.
Function: basic component, container component, interactive component, etc.
External attributes (terminal, business line) and internal attributes (form, function) together define a material.
Canvas Design
This part dives into the canvas’s core challenges and implementation approaches.
Example case: a user named "Xiaoshuai" needs to create a promotional page. He drags an image, text, and button component onto the canvas, configures them, and publishes the page.
The key workflow is: add component → drag component → select component → configure component (configurator) → publish (generator).
Add Component
Two core steps:
Load the component’s material into the platform.
Generate a Schema node for the component and insert it into UIDL after drag ends.
Schema Generation
Pseudo‑code for generating a Schema node:
genSchema(component: ComponentMaterial): Schema {
const children: Schema[] = [];
const props = {};
const styles: React.CSSProperties = SchemaService.defaultStyles;
return {
id: this.genComponentId(),
type: component.id,
name: component.name,
props,
children,
styles
};
}Load Material
Materials are loaded on demand to keep the builder responsive. The loading strategy must align with the bundling format.
Packaging Specification
Four common module formats are discussed: AMD, CJS, UMD, ESM. Modern front‑end trends favor ESM for its standardization and tooling support.
AMD
CJS
ESM
Advantages
1. Asynchronous loading, suitable for modular scenarios.
2. Good compatibility with RequireJS.
Advantages
Synchronous loading, intuitive syntax.
Advantages
1. Supports both sync and async loading.
2. Optimized for front‑end bundling.
3. Standardized, future‑proof.
Disadvantages
1. Syntax not intuitive.
2. Requires runtime conventions.
3. Limited bundling optimization.
4. Not standardized.
Disadvantages
1. Not browser‑compatible.
2. Isolated spec.
3. Not standardized.
Disadvantages
Compatibility issues in CJS projects; many libraries need conversion.
Scenario
Browser
Scenario
Node
Scenario
Browser & Node
Material Loading Solutions
Two main solutions are compared:
Solution
Advantages
Disadvantages
systemJS
1. Supports AMD/ESM/CJS.
2. Actively maintained with bundler support.
No context concept leads to compatibility gaps.
RequireJS
1. Supports AMD and CJS.
2. Good spec compatibility.
Syntax not intuitive; requires promisify.
Overall, systemJS is recommended for modern bundlers and complex dependency handling.
Dependency Analysis
Materials may depend on:
Base frameworks/libraries : React, React‑DOM, etc.
Frameworks specific to a component : e.g., xg‑player.
Other components : button list depends on button component.
To avoid redundancy, dependencies are handled differently:
Base frameworks are treated as shared and not bundled with each material.
Component‑specific frameworks are bundled with the material.
Component dependencies are declared so that dependent materials load first.
Sandboxing
Each material runs as an isolated executable unit within the same runtime. Isolation can be logical (Eval, Function, Proxy) or style‑based (CSS modules, Shadow DOM).
Logical Isolation
const varBox = {};
const fakeWindow = new Proxy(window, {
get(target, key) { return varBox[key] || window[key]; },
set(target, key, value) { varBox[key] = value; return true; }
});
const fn = new Function('window', code);
fn(fakeWindow);Style Isolation
Options include CSS‑modules, styled‑components, or wrapping the component in a Shadow DOM.
let elementRef = document.querySelect('#sub-app');
let shadow = elementRef.attachShadow({mode: 'open'});Drag Component
Drag‑and‑drop is a core difficulty. The platform must capture start, move, and end events, providing data such as direction, distance, position, and boundary scrolling.
Using MouseDown/MouseMove/MouseUp instead of native drag events gives finer control over position and direction.
Drag Library
Libraries abstract event listening and data collection. Typical phases:
Start – MouseDown, distance threshold, start callback.
Move – MouseMove/Scroll, non‑fixed layout handling, direction, distance, position, move callback, auto‑scroll, cursor change.
End – MouseUp, final position, end callback.
Mirror Component
A mirror component is rendered during drag to provide immediate visual feedback and preload resources.
let componentMap = {};
let mirror = { move: xxx, destroy: xxx };
onMouseDown = (e) => {
const schema = genSchema(e);
const schema = loadComponent(schema);
mirror = renderMirror(schema);
};
onMouseMove = (e) => { mirror.move(e); };
onMouseUp = (e) => { mirror.destroy(); };
loadComponent = (schema) => {
if (componentMap[schema.type]) return;
componentMap[schema.type] = systemjs.loadModule(schema.url);
};
renderMirror = (schema) => {
const mirrorEl = document.createElement('div');
document.body.appendChild(mirrorEl);
const Mirror = componentMap[schema.type];
ReactDOM.render(<Mirror />, mirrorEl);
};Component Anchors
Component anchors are indispensable in drag‑and‑drop.
Eight anchors appear around a selected component, enabling resizing and alignment.
Smart Snapping
Smart snapping improves placement accuracy. Three types are covered:
Position Snapping
If a component’s position is within a small threshold (1‑5 px) of another component’s edge, it automatically aligns.
Distance Snapping
The platform detects consistent margins between components and aligns new components to match existing spacing.
Size Snapping
When resizing, if the new width or height matches another component within a tolerance, the size snaps to that value.
Component Insertion
When inserting a component, its Schema node is added to UIDL, and the canvas re‑renders based on the updated UIDL.
type Props = { id: ComponentId; key?: string };
const DynamicComponent: React.FC<Props> = ({ id }) => {
const [schema, version] = useSchema(id);
const moduleMap = useSelector(state => state.material.moduleMap);
const { children = [], type, props, name } = schema;
const Module = moduleMap[type];
return (
<Module key={id} {...props}>
{children.map(child => (
<DynamicComponent key={child.id} id={child.id} />
))}
</Module>
);
};Select Component
Event Dispatch
After a component is selected, an event system propagates the interaction. Two approaches exist: per‑component event binding or a global listener that resolves the target element. The article recommends per‑component binding for simplicity.
Using display:contents can create a ghost node that forwards events without affecting layout.
function withEventProvider<P>(Component: React.ComponentType<P>): React.ComponentType<P> {
const Wrapped: React.ComponentType<P> = (props) => (
<div style={{ display: 'contents' }} onClick={e => console.log(e, props)}>
<Component {...props} />
</div>
);
const name = Component.displayName ?? Component.name ?? 'Unknown';
Wrapped.displayName = `withEventProvider(${name})`;
return Wrapped;
}Quick Operations
Delete Component : Remove the component’s Schema node and re‑render.
Copy & Paste : Store a copied Schema in a temporary variable; on paste, insert a new node with adjusted left and top to avoid overlap.
Cut Component : Combine copy‑paste with deletion.
Text Editing : Components declare data-edit="propKey"; the editor makes those nodes contentEditable and syncs changes back to the Schema.
Component Rotation : Compute angle difference between mouse‑down and mouse‑move vectors relative to the component’s center.
Multiple Component Selection
Selection Area
When dragging the mouse, a translucent rectangle shows the selection region. The final selection is the minimal bounding rectangle covering all fully contained components.
let startPoint = null;
const area = { width: 0, height: 0, x: 0, y: 0 };
const onMouseDown = e => { startPoint = new Point(e); registerMoveAndUp(); };
const onMouseMove = e => {
area.width = Math.abs(e.x - startPoint.x);
area.height = Math.abs(e.y - startPoint.y);
area.x = Math.min(e.x, startPoint.x);
area.y = Math.min(e.y, startPoint.y);
};Batch Operations
After selecting multiple components, users can move or resize them together. The platform may create a temporary outer container for the group, move it, and then apply the delta to each component’s Schema.
Conclusion
The article has dissected the core entity of a builder platform—the canvas—covering architecture, UIDL, sandboxing, drag‑and‑drop, smart snapping, component insertion, event dispatch, quick operations, and multi‑selection.
Future articles will address the configurator and generator modules.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
