CSS 自定义高亮 API(Highlight API)实现代码语法高亮的纯前端方案
CSS 自定义高亮 APIHighlight API实现代码语法高亮的纯前端方案一、不再需要 Prism.js 了——一个让你两难的技术决策代码块语法高亮是技术博客的标配。传统方案是 build 时预编译Prism.js / Shiki将高亮的 HTML 写入静态页面。优点零运行时开销。缺点内容不能动态变化用户输入、实时预览、协同编辑。如果你需要在编辑器中实时高亮用户输入的代码或者在一个可编辑的代码块中即时反馈你就需要运行时的语法高亮。CSS Highlight APICSS.highlights正是为此而生——它不需要修改 DOM、不引入额外的 HTML 标签直接在文本节点上通过选区范围Range标记样式。二、Highlight API 的工作原理Highlight API 的工作流程主要包含以下几个步骤JS 解析器解析代码文本生成 Token 列表。为每个 Token 创建对应的Range对象。通过highlights.set(code-syntax, Highlight)将 Range 集合注册到CSS.highlights中其中 Highlight 对象本质上是 Range 的集合。触发::highlight 伪元素样式。渲染引擎仅渲染被 Range 覆盖的文本节点特定区域。整个过程中文本节点保持完整实现了零 DOM 变化。关键区别传统高亮方案Prism.js会修改 DOM把span插入文本节点中Highlight API 通过Range直接在文本渲染层标记样式DOM 保持原样。三、纯前端代码高亮实现// highlight/code-highlighter.ts // 基于 CSS Highlight API 的代码语法高亮 interface Token { type: keyword | string | number | comment | function | operator | punctuation; --- value: string; start: number; // 在源代码中的起始位置 end: number; // 在源代码中的结束位置 } /** * 基于 Highlight API 的代码语法高亮器 * * 设计意图替代 Prism.js / highlight.js 的运行时高亮方案 * 优势不修改 DOM、支持实时编辑、零额外标签开销 * * 局限需要浏览器支持 CSS Highlight APIChrome 105, Safari 17.2 */ class HighlightAPISyntaxHighlighter { private textNode: Text | null null; private tokens: Token[] []; /** * 对代码块应用语法高亮 * * param codeElement - 包含代码的 precode 元素 * param language - 编程语言javascript, typescript, css 等 */ highlight(codeElement: HTMLElement, language: string javascript): void { // 获取代码文本节点code 的直接子文本节点 const code codeElement.textContent || ; this.textNode this.getTextNode(codeElement); if (!this.textNode) { console.warn(无法找到文本节点回退到传统的 span 高亮方案); this.fallbackHighlight(codeElement, language); return; } // 1. 词法分析将代码文本分解为 Token 序列 this.tokens this.tokenize(code, language); // 2. 创建 CSS Highlight 注册 const highlights this.createHighlights(); // 3. 将 Highlight 对象注册到 CSS.highlights CSS.highlights.clear(); // 清除之前的高亮 for (const [type, highlight] of Object.entries(highlights)) { CSS.highlights.set(code-${type}, highlight); } } /** * 获取代码元素中的文本节点 * * 注意code 内可能有多个文本节点因为有换行符分割 * 我们需要把它们视为一个整体的文本区域 */ private getTextNode(element: HTMLElement): Text | null { const walker document.createTreeWalker( element, NodeFilter.SHOW_TEXT, null ); // 找到第一个非空文本节点 let node walker.nextNode(); while (node) { // 忽略回车符/换行符产生的空文本节点 if ((node.textContent?.length || 0) 0) { return node as Text; } node walker.nextNode(); } return null; } /** * 简化的词法分析器 * * 将源代码分割为 Token 序列 * * 注意这是一个简化版实现仅支持 JavaScript/TypeScript 的基本语法。 * 完整的词法分析器应考虑 * - 正则表达式字面量/pattern/ * - 模板字符串text * - JSX 标记Component */ private tokenize(code: string, language: string): Token[] { const tokens: Token[] []; let pos 0; // Token 模式定义 const patterns: Array{ type: Token[type]; regex: RegExp; } [ // 注释优先级最高——其他模式在注释内不生效 { type: comment, regex: /\/\/.*$|\/\*[\s\S]*?\*\//m }, // 字符串 { type: string, regex: /(?:[^\\]|\\.)*|(?:[^\\]|\\.)*|(?:[^\\]|\\.)*/ }, // 数字 { type: number, regex: /\b\d(\.\d)?\b/ }, // 关键字 { type: keyword, regex: /\b(const|let|var|function|return|if|else|for|while|class|import|export|from|async|await|try|catch|throw|new|typeof|instanceof)\b/ }, // 函数名标识符后跟括号 { type: function, regex: /\b([a-zA-Z_$][\w$]*)\s*(?\()/ }, ]; while (pos code.length) { let matched false; for (const { type, regex } of patterns) { regex.lastIndex pos; const match regex.exec(code); if (match match.index pos) { tokens.push({ type, value: match[0], start: pos, end: pos match[0].length }); pos match[0].length; matched true; break; } } if (!matched) { // 未匹配任何模式 → 普通文本推进一个字符 pos; } } return tokens; } /** * 创建 Highlight 对象 * * 按 Token 类型分组每个分组创建一个 Highlight 对象 */ private createHighlights(): Recordstring, Highlight { const highlights: Recordstring, Highlight {}; if (!this.textNode) return highlights; for (const token of this.tokens) { const type token.type; if (!highlights[type]) { highlights[type] new Highlight(); } // 创建 Range 覆盖 Token 对应的文本区域 const range new Range(); range.setStart(this.textNode, token.start); range.setEnd(this.textNode, token.end); // 将 Range 添加到对应类型的 Highlight 中 highlights[type].add(range); } return highlights; } /** * 传统方案降级修改 DOM * * 当浏览器不支持 Highlight API 时使用 */ private fallbackHighlight(codeElement: HTMLElement, language: string): void { const code codeElement.textContent || ; const tokens this.tokenize(code, language); // 从后往前替换避免位置偏移问题 tokens.sort((a, b) b.start - a.start); for (const token of tokens) { const span document.createElement(span); span.className token-${token.type}; span.textContent token.value; // 创建 Range 来替换 Token 位置的内容 const textNode this.getTextNode(codeElement); if (!textNode) continue; const range new Range(); range.setStart(textNode, token.start); range.setEnd(textNode, token.end); range.deleteContents(); range.insertNode(span); } } }/* * * CSS ::highlight 伪元素样式 * * * ::highlight() 是 CSS Highlight API 的样式入口 * 括号中的参数对应 CSS.highlights.set(name, highlight) 中的 name * * ⚠️ 注意::highlight 支持的属性很少—— * 仅 color, background-color, text-decoration, * text-shadow, -webkit-text-stroke 等少数属性 * * font-family, font-size 等排版属性在 ::highlight 中无效——这是规范限制 */ /* 关键字const, let, function, return... */ ::highlight(code-keyword) { color: #d73a49; font-weight: bold; } /* 字符串hello, world */ ::highlight(code-string) { color: #032f62; } /* 数字42, 3.14 */ ::highlight(code-number) { color: #005cc5; } /* 注释// comment, /* block */ */ ::highlight(code-comment) { color: #6a737d; font-style: italic; } /* 函数名myFunction() */ ::highlight(code-function) { color: #6f42c1; }// highlight/react-use-highlight.ts // React Hook 封装 import { useEffect, useRef } from react; /** * React Hook对代码块应用语法高亮 * * 使用方式 * function CodeBlock({ code }: { code: string }) { * const ref useCodeHighlight(typescript); * return pre ref{ref}code{code}/code/pre; * } */ function useCodeHighlight(language: string) { const ref useRefHTMLPreElement(null); const highlighter useRef(new HighlightAPISyntaxHighlighter()); useEffect(() { if (ref.current) { const code ref.current.querySelector(code); if (code) { highlighter.current.highlight(code, language); } } }, [language]); return ref; }四、Highlight API 的三个限制样式属性限制。::highlight只支持极少的 CSS 属性color、background-color、text-decoration、text-shadow。不能改变font-size、font-family不能添加padding/margin/border。这意味着高亮只能改变颜色不能改变文字的排版表现。Range 不能跨文本节点。如果你的代码块 DOM 是多文本节点的如codespanconst/span a 1/codeHighlight API 的 Range 只能覆盖单个文本节点。对于已有混合标签的代码块Highlight API 无法工作。浏览器兼容性。Chrome 1052022.8、Safari 17.22023.12、Firefox 尚未支持截至 2024。国内移动端覆盖率约 70%。对于需要广泛兼容的项目Highlight API 只能作为增强方案不能作为唯一方案。五、总结CSS Highlight API 是代码语法高亮的第三种方案方案时机DOM 修改动态内容Build 预编译构建时是span 标签不支持JS 运行时高亮运行时是span 标签支持Highlight API运行时否支持Highlight API 的核心优势是不修改 DOM——这在协同编辑、实时预览、代码 Diff 展示等场景中至关重要。但目前仍需要降级方案传统 span 高亮保持兼容性。最佳实践先检测CSS.highlights是否可用可用则用 API不可用则降级。

相关新闻

使用Jenkins进行自动化部署和持续集成-Java 项目实战(超详细)

使用Jenkins进行自动化部署和持续集成-Java 项目实战(超详细)

前言 在现代软件开发流程中,持续集成 CI、持续交付 CD已经成为企业级项目开发、运维的核心标准。Jenkins 作为开源、可扩展的自动化构建平台,能够打通代码拉取、编译打包、镜像制作、项目部署全流程,结合 GitLab、Maven、Docker、K8s 等技术…

2026/10/13 8:20:23 阅读更多 →
AI Agent的工具调用机制架构设计:从单工具调用到多工具智能编排的完整方案

AI Agent的工具调用机制架构设计:从单工具调用到多工具智能编排的完整方案

AI Agent的工具调用机制架构设计:从单工具调用到多工具智能编排的完整方案 一、Tool Calling的架构困境——当Agent不再是单一LLM推理 LLM驱动的AI Agent与传统聊天机器人的核心差异在于能力——Agent不输出文本就结束交互,而是像人类一样使用工具、调用…

2026/10/5 10:50:22 阅读更多 →
DXPopover高级特性探索:动画效果、手势控制与性能优化

DXPopover高级特性探索:动画效果、手势控制与性能优化

DXPopover高级特性探索:动画效果、手势控制与性能优化 【免费下载链接】DXPopover A Popover mimic Facebook app popover using UIKit 项目地址: https://gitcode.com/gh_mirrors/dx/DXPopover DXPopover是一款基于UIKit开发的高质量弹窗组件,完…

2026/10/6 11:49:28 阅读更多 →

最新新闻

机器学习入门第一性原理:线性模型核心推导与工程实践

机器学习入门第一性原理:线性模型核心推导与工程实践

1. 为什么线性模型是机器学习入门的“第一性原理”很多人学机器学习,一上来就被各种深度网络、注意力机制、Transformer 架构唬住了,觉得线性模型太简单、太基础,不值得花时间。但我带了这么多届学生、看了这么多份简历和项目,一个…

2026/10/13 11:02:04 阅读更多 →
HarmonyOS实战:用PixelMap实现图片颜色翻转与负片效果

HarmonyOS实战:用PixelMap实现图片颜色翻转与负片效果

如果你在搜索HarmonyOS开发里关于 PixelMap 的资料,看到最多的解释多半是“PixelMap是一张位图对象”。这句话没错,但基本等于没说。真正上手做图像处理时你会发现, PixelMap 既不是 Image 组件里那个能直接显示的图片,也不…

2026/10/13 11:02:04 阅读更多 →
免费绘画软件FireAlpaca完全指南:轻量替代PS/SAI的插画与动画实践

免费绘画软件FireAlpaca完全指南:轻量替代PS/SAI的插画与动画实践

1. 我为什么从 PS 和 SAI 换到 FireAlpaca 先交代背景。我一直是个靠画图吃饭的人,平时主要用 Photoshop 做商业插画和排版,但说实话,PS 对我的工作负担太重了:启动慢、吃内存、订阅费一年比一年贵。前几年还流行用 SAI&#xff0…

2026/10/13 11:02:04 阅读更多 →
级联故障风险评估:随机化学算法如何高效搜索最坏停电场景

级联故障风险评估:随机化学算法如何高效搜索最坏停电场景

做电力系统风险评估的同行,应该都遇到过这个尴尬:蒙特卡洛抽样跑了几万次,期望损失算出来漂漂亮亮,可一旦想回答“哪几组初始故障组合能把系统搞瘫”,统计结果往往是“查无此案”。原因很简单,大面积停电属…

2026/10/13 11:02:04 阅读更多 →
全链路 Trace 标签化染色与慢执行步骤智能归因

全链路 Trace 标签化染色与慢执行步骤智能归因

在传统的微服务架构中,一次 HTTP 请求通常沿着“网关 -> 业务中台 -> 缓存/数据库”的确定性静态调用拓扑展开,分布式追踪系统(如 Jaeger、Zipkin、SkyWalking)能够非常清晰地展现各服务的 RPC 耗时与依赖拓扑。 然而&#…

2026/10/13 11:02:04 阅读更多 →
C++数组指针字符串:从内存视角彻底搞懂核心难点

C++数组指针字符串:从内存视角彻底搞懂核心难点

我知道很多人第一次翻开郑莉这本《C语言程序设计》,到第六章"数组、指针和字符串"就开始心里打鼓。说实话,我当年也是这样:前面的流程控制、函数调用,照着抄总能写出来,可数组和指针一上来,什么&…

2026/10/13 11:01:04 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →