Building a custom visual (WYSIWYG) HTML editor from scratch using JavaScript relies on the contenteditable=“true” attribute combined with the modern Selection and Range APIs to format text and generate HTML source code natively.
While many legacy systems and older tutorials rely heavily on document.execCommand(), it has been completely deprecated across all modern browsers due to inconsistent behaviors. Building an enterprise-grade editor requires handling selection states and manually wrapping DOM text nodes. Step-by-Step Architecture for a Custom HTML Editor
Here is the complete roadmap, structured architecture, and functional code needed to implement a minimalist, secure HTML editor using vanilla HTML, CSS, and modern JavaScript. 1. Define UI Structure
Create a dedicated controls wrapper for the toolbar and an element with the contenteditable attribute. A textarea element hidden by default will capture and show the raw HTML code output.
Type your markup text here…
Use code with caution. 2. Apply Fundamental Styles
Keep layout structures distinct. Ensure the visual editing element has defined boundaries and handles whitespace properties properly to prevent layout breaking. Use code with caution. 3. Manage Range Node Modifications
To avoid using deprecated functions, fetch active cursor selections natively using the web standard window.getSelection() and wrap text fragments directly inside element nodes. javascript
// Global function to modify selection chunks function wrapSelectionInTag(tagName) { const selection = window.getSelection(); if (!selection.rangeCount) return; // Extract highlight boundaries const range = selection.getRangeAt(0); // Guard clause against empty selections if (range.collapsed) return; // Create wrapping element const element = document.createElement(tagName); try { // Extract selected DOM objects into the tag element.appendChild(range.extractContents()); // Insert new parent back into document context range.insertNode(element); // Clear selection for user experience selection.removeAllRanges(); } catch (error) { console.error(“DOM manipulation failed”, error); } } // Handler functions bound to UI triggers function formatText(style) { if (style === ‘bold’) wrapSelectionInTag(‘strong’); if (style === ‘italic’) wrapSelectionInTag(‘em’); } function insertHeading(tag) { wrapSelectionInTag(tag); } Use code with caution. 4. Build Code Syncing Elements
To extract the resulting code string out of your interactive workspace, dynamically sync the inner element contents with your plain-text target container. javascript
const visualView = document.getElementById(‘visualCanvas’); const sourceView = document.getElementById(‘sourceCanvas’); function viewSource() { const isVisualVisible = sourceView.style.display === ‘none’; if (isVisualVisible) { // Copy the rendered HTML text string directly to textarea source sourceView.value = visualView.innerHTML; visualView.style.display = ‘none’; sourceView.style.display = ‘block’; } else { // Copy the raw markup modifications back to the canvas layout visualView.innerHTML = sourceView.value; sourceView.style.display = ‘none’; visualView.style.display = ‘block’; } } Use code with caution. Critical Engineering Guardrails
When building a vanilla layout engine to output raw HTML formatting blocks, implement these absolute development requirements:
Sanitize Inputs Continuously: Never save output values or read strings directly without validating elements. Malicious actors could copy-paste strings containing nested tags, causing Cross-Site Scripting (XSS) vulnerabilities. Use sanitization blocks or third-party engines on backend storage receivers.
Block Element Fallbacks: Pressing the Enter key inside a custom element container defaults to creating structural paragraphs (
) or dividers (
visualView.addEventListener(‘keydown’, (e) => { if (e.key === ‘Enter’) { // Custom block level structure code goes here if default configuration normalization is required } }); Use code with caution.
Selection State Persistence: When users interact with toolbar buttons, focus shifts from the canvas workspace to the clicked target. Ensure calculations save or read your global tracking state data models so you don’t lose the cursor pointer focus within the layout stack.
If you are looking to expand this system, let me know if you would like to explore adding custom keyboard shortcuts, handling image insertion uploads, or configuring a sanitization structure to prevent XSS script injections.
Leave a Reply