How I Replicated WeChat’s Fancy Layouts in WangEditor via Custom Extensions
This article walks through extending WangEditor V5 with custom plugins to reproduce the rich, multi‑style layouts found in WeChat public‑account articles, covering the analysis of required HTML/CSS patterns, defining new Slate‑based elements, registering menus, and rendering the styles within the editor.
When a product team required the ability to create WeChat‑style rich articles directly inside their own rich‑text editor, the author decided to implement the feature natively rather than using screenshot tricks.
WeChat articles often contain animated titles, highlighted text blocks with borders or background colors, and images wrapped in styled sections. By examining several WeChat posts the author identified these recurring patterns and expressed them as a hierarchical JSON structure that can be rendered by a custom element.
The author surveyed open‑source editors and chose WangEditor V5 because its documentation is abundant and its core is built on slate.js , a highly customizable React‑based editor framework. WangEditor also uses a virtual‑DOM update model and a plug‑in architecture, making it suitable for the required extensions.
WangEditor V5 relies on slate.js to define nodes and operations. All editor actions must go through Slate’s API, so any new feature must be added by extending Slate’s node types and rendering pipeline.
First, a new element type wechat-multi-style is defined. Its TypeScript declaration includes a jsonData field of type StyleNode, which describes the HTML tag, style object, children, and leaf flag. An example JSON node is shown below:
{
type: 'wechat-multi-style',
element: 'section',
children: [
{
element: 'section',
style: { width: '100%', padding: '0 11px 0 19px' },
children: [
{
element: 'section',
style: { width: '23px', display: 'flex' },
children: []
},
{
element: 'section',
style: { width: '48px', display: 'flex' },
children: []
},
{
element: 'section',
style: { border: '1px solid #8CC4FF', width: '100%' },
children: [
{
element: 'section',
style: { width: '100%', textAlign: 'left', background: '#EAF3FF', padding: '7px 12px 6px 12px', transform: 'translate(-5px, -5px)' },
children: [
{
element: 'p',
styleIndex: 'mark-style-text',
style: { fontSize: '18px', color: '#323232', lineHeight: '28px', wordBreak: 'break-word' },
children: [
{ element: 'span', leaf: true, text: '别致的设计,赋予这款汽车无可比拟的美感。...' }
]
}
]
}
]
}
]
}
],
styleIndex: 'titleStyle-5'
}Next, three files are created to handle conversion between the custom element and HTML: elem-to-html.ts, parse-elem-html.ts, and render-elem.ts. The rendering file contains a recursive function renderStyleNode that builds a virtual‑DOM node from the JSON definition and returns it to the editor.
function renderStyleNode(node: StyleNode, textNodes: VNode[] | null): VNode | VNode[] {
const { element = 'section', style, children, leaf } = node;
const attributes: any = {};
if (style) { attributes.style = style; }
if (leaf) { return h(element, attributes, textNodes); }
if (children && children.length > 0) {
const childNodes = children.map(child => renderStyleNode(child, textNodes));
return h(element, attributes, childNodes);
}
return h(element, attributes);
}
function renderWechatMultiStyle(elemNode: SlateElement, children: VNode[] | null, editor: IDomEditor): VNode {
const { jsonData } = elemNode as WechatMultiStyleElement;
return renderStyleNode(jsonData, children) as VNode;
}
export const wechatMultiStyleElementRenderElemConf = {
type: 'wechat-multi-style',
renderElem: renderWechatMultiStyle,
isInline: false,
isVoid: false,
};The custom element is then registered with WangEditor via the Boot.registerRenderElem, Boot.registerElemToHtml, and Boot.registerParseElemHtml calls.
To let users pick a style, a DropPanelMenu is implemented. The menu builds a list of preview sections from a predefined style configuration, attaches click handlers that call applyStyleToEditor, and dynamically adjusts each list item’s height.
getPanelContentElem(editor: IDomEditor): HTMLElement {
const container = document.createElement('div');
const ul = document.createElement('ul');
panelStyles.forEach(panelConfig => {
const li = document.createElement('li');
const previewElement = this.createPanelNestedSection(panelConfig);
li.appendChild(previewElement);
li.addEventListener('click', () => {
this.applyStyleToEditor(editor, panelConfig);
this.hideDropPanel?.(editor);
});
ul.appendChild(li);
});
// compute heights …
container.appendChild(ul);
return container;
}The applyStyleToEditor function looks up the selected style by its styleIndex, creates a new node of type wechat-multi-style, and inserts it into the editor.
private applyStyleToEditor(editor: IDomEditor, panelStyleConfig: PanelStyleConfig) {
const titleStyleConfig = titleStyles.find(config => config.styleIndex === panelStyleConfig.styleIndex);
if (!titleStyleConfig) { console.warn(`未找到styleIndex为${panelStyleConfig.styleIndex}的标题样式配置`); return; }
if (titleStyleConfig.type === 'wechat-multi-style') {
editor.insertNode({
type: 'wechat-multi-style',
jsonData: titleStyleConfig,
children: [{ text: titleStyleConfig.text || '模板样式' }]
} as WechatMultiStyleElement);
}
}After registering the menu and the render logic, the editor displays a dropdown of WeChat‑style options. Selecting an option inserts a styled section that matches the original WeChat layout, as shown in the screenshots below.
In summary, the author successfully built a full workflow—from analyzing WeChat’s HTML/CSS patterns, through defining a custom Slate element and its JSON representation, to registering render hooks and a drop‑panel menu—demonstrating how WangEditor V5 can be extended to support complex, WeChat‑like rich‑text layouts.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
xkx's Tech General Store
Code with the left hand, enjoy with the right; a keystroke sweeps away worries.
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.
