可视化编辑 🔧 前端用“低代码”方式动态变更网页内容

一、前言

业务需求的快速迭代对前端开发效率提出了严峻挑战,传统全代码开发模式依赖大量手工编码和调试,在面对简单或标准化的页面时,往往造成开发资源的冗余消耗。在这种情况下,"低代码"开发模式应运而生,它为前端开发提供了一种全新的思路和方法,以可视化画布替代手工编码,开发者只需拖拽组件、配置属性与交互,平台即刻生成标准 JSON Schema;渲染引擎解析 Schema 并调用内置组件库,秒级输出目标页面。这种方式大幅降低了开发门槛,显著提高了开发效率,同时保障了业务需求的快速响应能力。

低代码开发模式不仅适用于"搭建"页面的场景,同样适用于页面迭代更新的场景。在进行页面变更时,我们可以在现有页面中选中需要变更的元素,然后通过可视化编辑器对其进行编辑,如修改文本内容、调整位置、更改样式、修改类名以及其他属性值等。编辑结果同样以协议方式保存为 JSON Schema。当将 JSON Schema 输入到执行器中时,执行器会根据 JSON Schema 对现有页面进行动态变更,完成页面结构的动态调整与功能迭代。本文将深入探讨这一种用"低代码"的方式动态变更网页内容的模式。

二、使用流程

低代码变更网页遵循与低代码搭建网页相同的技术范式,通过图形化界面实现网页元素的低代码化修改,主要流程分为两个阶段:

  1. 编辑流程:打开待变更页面 -> 启用可视化编辑器扩展 -> 页面中选中需要编辑元素 ->在编辑器中对元素编辑 -> 保存产出 JSON Schema;
  2. 变更流程:用户在待变更页面中接入执行器 -> 将 JSON Schema 输入到执行器中时执行变更 -> 根据协议页面完成对应的变更;

整体流程主要基于两大核心部分,编辑器和执行器:

  1. 编辑器,即在页面中进行编辑操作,主要载体为浏览器扩展;
  2. 执行器,即在页面中应用变更内容,主要载体为变更 SDK;

三、应用场景

看到这里,很多人不由得想问了,如果网页本身就是用原生方式开发的,那为什么还需要用这种低代码方式来进行内容变更,直接用原生开发方式不行吗?答案,当然是可以的。低代码方式来进行变更网页内容并不是要取代原生开发,而是为某些高频、急迫或低技术门槛的场景提供"加速器":

  1. AB 可视化实验:通过拖拽式可视化编辑器快速产出多套对照版本,通过 AB 实验来验证效果;
  2. 临时性的变更:页面接入变更 SDK 后,任何内容的临时变更都可以快速发布上线,避开传统冗长流程;
  3. 非研发人员操作:产运等非研发角色无需等待研发资源,自己在可视化画布上点选即可完成页面微调;
  4. ......

动态变更现有网页是通过外部注入的方式对页面布局、样式、交互等进行动态调整,主要包含以下变更场景:

四、技术实现

4.1 数据协议

支持 6 大类 14 小类变更:

javascript 复制代码
/**
 * 内容变更
 */
const htmlSchema = [
  // 增加内容
  { type: 'html', selector: '#id', action: 'append', value: 'hello world' },
  // 替换内容
  { type: 'html', selector: '#id', action: 'set', value: 'hello world' },
  // 移除内容
  { type: 'html', selector: '#id', action: 'remove' },
];

/**
 * 类名变更
 */
const classSchema = [
  // 增加类名
  { type: 'class', selector: '#id', action: 'append', value: 'text-14px text-red' },
  // 设置类名(完全覆盖)
  { type: 'class', selector: '#id', action: 'set', value: 'text-14px lh-22px' },
  // 移除类名(空格区分),若为空则清除所有类名
  { type: 'class', selector: '#id', action: 'remove', value: 'text-14px bg-green' },
];

/**
 * 样式变更
 */
const styleSchema = [
  // 增加样式
  { type: 'style', selector: '#id', action: 'append', value: 'color: red; font-size: 14px;' },
  // 设置样式(完全覆盖)
  { type: 'style', selector: '#id', action: 'set', value: 'color: red; font-size: 14px;' },
  // 移除样式(驼峰式且空格区分),若为空则清除所有行内样式
  { type: 'style', selector: '#id', action: 'remove', value: 'color fontSize' },
];

/**
 * 属性变更
 */
const attributeSchema = [
  // 属性值增加(不建议用)
  { type: 'attribute', selector: '#id', attribute: 'data-id', action: 'append', value: '123' },
  // 属性值设置
  { type: 'attribute', selector: '#id', attribute: 'data-id', action: 'set', value: '123' },
  // 移除属性
  { type: 'attribute', selector: '#id', attribute: 'data-id', action: 'remove' },
];

/**
 * 位置移动
 */
const positionSchema = [
  // 将 #id 元素移动到 #parent 之内 #child 之前,如果没有 insertBeforeSelector,则放到 parentSelector 内部最后
  { type: 'position', selector: '#id', insertBeforeSelector: '#child', parentSelector: '#parent' },
];

/**
 * 插入内容
 */
const widgetSchema = [
  { type: 'widget', selector: '#id', widgetInsertPosition: 'beforebegin', value: '<div>hello world</div>' },
];

4.2 编辑器

编辑器的载体是浏览器扩展,主要作用是在待变更的网页上进行所见即所得的可视化编辑,完成预期的改动产出对应的 JSON Schema。浏览器扩展实现指南请阅读 👉《浏览器扩展开发指南:WXT + React + TS + TailwindCSS + AntDesign》

4.2.1 元素选中

鼠标在页面移动时,hover 的目标元素会实现浮现边框;轻点即可选中,该交互可基于click、pointermove 和 pointerdown 事件精准捕获。

typescript 复制代码
const clickHandler = (event: MouseEvent) => {
  // 点击次数为1
  if (event.detail === 1) {
    let element = event.target as HTMLElement;
    const tagType = element?.tagName.toLowerCase();
    if (tagType && ["b", "i", "u"].includes(tagType)) {
      element = element?.parentElement as HTMLElement;
    }
    if (!element) return;
    if (element.id === CONTAINER_ID) return;
    if (!elementUnderEdit) {
      setElementUnderEdit(element);
    }
    event.preventDefault();
    event.stopPropagation();
  }
};

const onPointerDown = (event: PointerEvent) => {
  const element = event.target as HTMLElement;
  if (elementUnderEdit) return;
  if (elementUnderEdit === element) return;
  if (element.id === CONTAINER_ID) return;
  event.preventDefault();
  event.stopPropagation();
};

const onPointerMove = throttle((event: PointerEvent) => {
  if (elementUnderEdit) return;
  const { clientX: x, clientY: y } = event;
  let domNode: Element | null = document.elementFromPoint(x, y);
  const tagType = domNode?.tagName.toLowerCase();
  if (tagType && ["b", "i", "u"].includes(tagType)) {
    domNode = domNode?.parentElement || null;
  }
  if (domNode?.id === CONTAINER_ID) {
    clearHoverAttribute();
    setHighlightedElement(null);
    return;
  }
  if (!domNode || domNode.hasAttribute(hoverAttributeName)) return;

  clearHoverAttribute();
  domNode.setAttribute(hoverAttributeName, "");
  setHighlightedElement(domNode as HTMLElement);
}, 50);

document.addEventListener("click", clickHandler, true);
document.addEventListener("pointermove", onPointerMove, true);
document.addEventListener("pointerdown", onPointerDown, true);

4.2.2 元素识别

选中元素后生成唯一识别表示,用于在变更时识别到相应的元素进行执行,也就是数据协议中的selector字段,根据 CSS Selector Generator Benchmark 中内容可以使用以下库来生成选择器,当然也可以参考业界埋点的相关机制来进行处理:

4.2.3 变更设置

在可视化编辑器中每选中一个不同的元素标签,都会呈现与之匹配的编辑面板,就像在低代码平台中使用组件一样:组件不同,右侧即刻切换专属属性,每个属性再配以最趁手的设置器,所见即所得,零门槛调参。例如,img 标签就有专属图片地址设置器,a 标签就有链接地址设置器和打开方式设置器等,不同元素也支持不同类型的样式设置,例如文本、背景、边框、尺寸和布局等。

技术实现上,我们把被选中的元素抽象为 SettingTarget;再由设置器规则集 SettingRules 给出它"能改什么、不能改什么",确定当前 DOM 元素支持的设置器和对应的属性值,在设置面板中进行展示,设置器主要分为三种:

  1. 内容设置器:只有非自闭合标签才能编辑内容,img、input 等自闭合标签只能改属性。
  2. 属性设置器:针对元素的 HTML 属性(href、src、alt...)量身打造。
  3. 样式设置器:覆盖 CSS 全生态:文本、背景、边框、尺寸、布局......

除了样式和内容设置之外,DOM 元素还支持新增组件和元素移动:

  • 新增组件:选中元素 → 写入 HTML → 指定插入位置;
  • 元素移动:拖拽放置到期望的位置,可用 pointerdown / pointermove / pointerup 实现;

所有变更最终沉淀为一份符合协议规范的 JSON Schema,可直接用于执行器执行变更。

4.3 执行器

执行器的作用是根据 JSON Schema 来变更网页,达到跟可视化编辑器中编辑一致的效果。更多内容请阅读 👉 《DOM-Modifier:持久化修改第三方页面解决方案》,SDK 地址 👉 DOM-Modifier

4.3.1 整体流程

DOM 内容变更会根据元素选择器找到目标元素执行相应变更,并且对目标元素通过 MutationObserver 添加监听,当目标元素发生变动时会重新执行变更;同时开启全局监听,当前未找到目标元素但后续出现时会完成变更执行。监听页面变动以执行变更主要涉及两类监听:

  1. 页面全局监听:主要针对变更的目标元素不在当前页面中后面才会出现的场景。
  2. 变更元素监听:主要针对变更的目标元素发生变化影响变更需要重新执行变更。

4.3.2 监听设置

新增组件需要监听目标元素的子元素层级和目标元素父元素的子元素层级变动;元素移动需要监听目标元素父元素的子元素层级变动。

  • subtree:监听以 target 为根节点的整个子树,包括子树中所有节点的属性,而不仅仅是针对 target
  • childList:监听 target 节点中发生的节点的新增与删除(同时,如果 subtree 为 true,会针对整个子树生效)
  • attributes:观察所有监听的节点属性值的变化,当声明了 attributeFilter 或 attributeOldValue,默认值则为 false
  • attributeFilter:一个用于声明哪些属性名会被监听的数组。如果不声明该属性,所有属性的变化都将触发通知

4.3.3 变更执行

💡 【元素移动】元素移动位置通过 parentNode 和 insertBeforeNode 来进行新位置定位

html 复制代码
<parentNode>
  //...
  <target></target>
  <insertBeforeNode></insertBeforeNode>
  //...
</parentNode>

💡 【新增组件】新增组件基于目标元素主要有 4 个位置可以进行插入:beforebegin、afterbegin、beforeend、afterend

html 复制代码
<!-- beforebegin -->
<p>
  <!-- afterbegin -->
  foo
  <!-- beforeend -->
</p>
<!-- afterend -->

五、总结

低代码范式不仅仅可用于网页搭建,也可以用于网页变更。从"写死代码"到"拖改即生效",低代码把网页迭代变成了一场实时可视的微调实验。它用 JSON Schema 充当通用语言,:任何文本、样式、布局甚至业务逻辑的变动,都能以毫秒级的粒度热插进现有页面。开发者不再需要为一次按钮位移而重新构建流水线,运营同学也能亲手把促销文案同步上线。最终,我们收获的不只是更快的交付,更是一种可沉淀、可追踪、可回滚的页面资产------当需求再一次变更,只需打开画布,点选、拖拽、发布,页面即刻与业务同频呼吸。

相关推荐
寅时码38 分钟前
我开源了一款 Canvas “瑞士军刀”,十几种“特效与工具”开箱即用
前端·开源·canvas
CF14年老兵40 分钟前
🚀 React 面试 20 题精选:基础 + 实战 + 代码解析
前端·react.js·redux
CF14年老兵41 分钟前
2025 年每个开发人员都应该知道的 6 个 VS Code AI 工具
前端·后端·trae
十五_在努力44 分钟前
参透 JavaScript —— 彻底理解 new 操作符及手写实现
前端·javascript
拾光拾趣录1 小时前
🔥99%人答不全的安全链!第5问必翻车?💥
前端·面试
IH_LZH1 小时前
kotlin小记(1)
android·java·前端·kotlin
lwlcode1 小时前
前端大数据渲染性能优化 - 分时函数的封装
前端·javascript
Java技术小馆1 小时前
MCP是怎么和大模型交互
前端·面试·架构
玲小珑1 小时前
Next.js 教程系列(二十二)代码分割与打包优化
前端·next.js
coding随想1 小时前
HTML5插入标记的秘密:如何高效操控DOM而不踩坑?
前端·html