从零打造 Medium 级富文本编辑器:grande.js 全方位实战指南

从零打造 Medium 级富文本编辑器:grande.js 全方位实战指南

【免费下载链接】grande.js It's a Medium at Starbucks. Pinky ring out. 【免费下载链接】grande.js 项目地址: https://gitcode.com/gh_mirrors/gr/grande.js

你是否曾惊叹于 Medium 编辑器的优雅体验?一键格式化、智能工具栏、流畅交互——这些看似简单的功能背后,藏着复杂的 DOM 操作与事件处理逻辑。本文将带你深入剖析 grande.js(GitHub 加速计划旗下富文本编辑库)的实现原理,从环境搭建到高级定制,用 1000 行代码构建属于你的沉浸式编辑体验。

为什么选择 grande.js?

特性grande.js传统编辑器现代富文本框架
体积~15KB (未压缩)50KB+100KB+
依赖原生 JSjQueryReact/Vue
定制难度中等(API 友好)高(需重写核心)低(组件化)
兼容性IE9+IE8+ES6+
核心优势Medium 同款交互功能全面生态完善

grande.js 作为 Medium 编辑器的轻量级实现,完美平衡了功能深度与集成难度。它不依赖任何框架,通过原生 API 实现了格式化工具栏、选区检测、样式切换等核心能力,特别适合博客系统、内容管理平台等场景的快速集成。

环境准备与安装

快速上手

# 克隆仓库
git clone https://gitcode.com/gh_mirrors/gr/grande.js.git
cd grande.js

# 安装依赖(如需构建)
npm install

# 直接使用浏览器打开演示页面
open index.html

项目结构解析

grande.js/
├── css/                 # 样式文件
│   ├── editor.css       # 编辑器核心样式
│   ├── menu.css         # 工具栏样式
│   └── icomoon/         # 图标字体
├── js/
│   └── grande.js        # 核心库文件
├── index.html           # 演示页面
└── test/                # 测试用例

核心文件仅需引入 grande.js 和配套 CSS 即可工作,无需复杂的构建流程。

基础使用指南

1. 初始化编辑器

<!-- HTML 结构 -->
<article contenteditable="true" id="editor"></article>

<!-- 引入资源 -->
<link rel="stylesheet" href="css/editor.css">
<link rel="stylesheet" href="css/menu.css">
<script src="js/grande.js"></script>

<!-- 初始化代码 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
  // 绑定编辑器元素
  const editor = document.getElementById('editor');
  grande.bind([editor], { animate: true });
});
</script>

2. 核心 API 解析

方法参数描述
grande.bind()nodes: NodeList, options: Object绑定可编辑元素并初始化
grande.select()-触发文本选择事件(用于测试)

配置选项

{
  animate: true,  // 是否启用工具栏动画
  allowImages: false  // 是否支持图片上传(当前开发中)
}

实现原理深度剖析

工具栏交互机制

grande.js 的浮动工具栏是其标志性特性,实现逻辑包含三个关键步骤:

mermaid

核心代码实现:

// 选区变化检测
document.onmouseup = function(event) {
  setTimeout(function() {
    triggerTextSelection(event);  // 延迟执行确保选区稳定
  }, 1);
};

// 工具栏定位
function setTextMenuPosition(top, left) {
  textMenu.style.top = top + "px";
  textMenu.style.left = left + "px";
  textMenu.className = options.animate ? "text-menu active" : "text-menu";
}

富文本格式化原理

通过 document.execCommand() 实现基础格式化,但做了大量兼容性处理:

// 样式切换核心逻辑
function triggerTextStyling(node) {
  const className = node.className;
  for (const tag in tagClassMap) {
    if (tagClassMap[tag] === className) {
      // 检查当前元素是否已应用该样式
      if (hasParentWithTag(getFocusNode(), tag)) {
        document.execCommand("formatBlock", false, "p");  // 移除样式
      } else {
        document.execCommand("formatBlock", false, tag);  // 应用样式
      }
      break;
    }
  }
}

支持的格式化标签映射:

const tagClassMap = {
  "b": "bold",
  "i": "italic",
  "h1": "header1",
  "h2": "header2",
  "blockquote": "quote",
  "a": "url"
};

高级功能与定制

自定义工具栏按钮

  1. 扩展 CSS 样式(menu.css):
.text-menu .code {
  background: #f5f5f5;
  padding: 2px 8px;
}
  1. 修改工具栏模板(grande.js):
// 在 toolbarTemplate 中添加
<button class='code'>code</button>
  1. 绑定格式化事件:
// 在 bindTextStylingEvents 中添加
if (className === 'code') {
  document.execCommand('formatBlock', false, 'pre');
}

图片上传功能(实验性)

虽然官方 roadmap 中标记为开发中,但可通过以下方式扩展:

// 监听文件选择事件
imageInput.onchange = function(event) {
  const file = event.target.files[0];
  const reader = new FileReader();
  
  reader.onload = function(e) {
    const figEl = document.createElement("figure");
    figEl.innerHTML = `<img src="${e.target.result}"/>`;
    editNode.insertBefore(figEl, imageBound.bottomElement);
  };
  
  reader.readAsDataURL(file);
};

常见问题与解决方案

Q: 工具栏位置偏移?

A: 检查 getBoundingClientRect() 计算是否考虑页面滚动,修复代码:

// 修正坐标计算
setTextMenuPosition(
  clientRectBounds.top - 5 + root.pageYOffset,  // 添加滚动偏移
  (clientRectBounds.left + clientRectBounds.right) / 2
);

Q: Firefox 下格式错乱?

A: Firefox 对 execCommand 支持差异,需特殊处理:

// 文本属性检测兼容
function getTextProp(el) {
  if (el.nodeType === Node.TEXT_NODE) return "data";
  return isFirefox ? "textContent" : "innerText";
}

性能优化建议

  1. 事件节流:文本选择事件触发频率高,添加节流控制:
let isProcessing = false;
document.onmouseup = function(event) {
  if (!isProcessing) {
    isProcessing = true;
    setTimeout(() => {
      triggerTextSelection(event);
      isProcessing = false;
    }, 50);
  }
};
  1. 样式缓存:减少 DOM 查询次数:
// 缓存工具栏元素
const textMenuButtons = document.querySelectorAll(".text-menu button");

未来展望与扩展方向

grande.js 目前处于维护状态,但核心架构仍具扩展性:

mermaid

推荐扩展方向:

  • 集成 marked.js 实现 Markdown 双向转换
  • 添加代码高亮插件(如 Prism.js)
  • 实现本地存储自动保存

总结

grande.js 以精简的代码实现了 Medium 编辑器的核心体验,其事件驱动的架构和原生 API 的巧妙运用,为我们展示了轻量级富文本编辑器的设计哲学。通过本文的解析与实践,你不仅能够快速集成这一工具,更能深入理解 DOM 操作、选区管理等前端核心技术。

【免费下载链接】grande.js It's a Medium at Starbucks. Pinky ring out. 【免费下载链接】grande.js 项目地址: https://gitcode.com/gh_mirrors/gr/grande.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值