当前位置: 首页 > news >正文

基于HTML的Word风格编辑器实现:从零打造功能完备的富文本编辑器

引言

在Web开发中,实现一个功能完备的富文本编辑器是一个常见需求。本文将基于HTML5和JavaScript,结合第三方库,打造一个具有Word风格界面的富文本编辑器,支持格式设置、图片插入、表格创建、文件导入导出等核心功能。
在这里插入图片描述

完整代码解析

以下是完整的HTML5富文本编辑器实现代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Word 编辑器</title><script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.4.0/mammoth.browser.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script><style>body {font-family: Arial, sans-serif;margin: 0;padding: 20px;background-color: #f5f5f5;}.container {max-width: 1200px;margin: 0 auto;background-color: white;border-radius: 8px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);padding: 20px;}.toolbar {display: flex;flex-wrap: wrap;gap: 5px;margin-bottom: 15px;padding-bottom: 15px;border-bottom: 1px solid #eee;}button, select, input {padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;background-color: white;cursor: pointer;}button:hover {background-color: #f0f0f0;}.editor {min-height: 500px;border: 1px solid #ddd;padding: 20px;border-radius: 4px;outline: none;}.file-input {display: none;}.status-bar {margin-top: 15px;padding-top: 10px;border-top: 1px solid #eee;color: #666;font-size: 14px;}.active {background-color: #e0e0e0;}.color-picker {width: 30px;height: 30px;padding: 0;border: 1px solid #ddd;}</style>
</head>
<body><div class="container"><h1>Word 编辑器</h1><div class="toolbar"><button id="bold-btn" title="加粗"><b>B</b></button><button id="italic-btn" title="斜体"><i>I</i></button><button id="underline-btn" title="下划线"><u>U</u></button><select id="heading-select"><option value="paragraph">正文</option><option value="h1">标题1</option><option value="h2">标题2</option><option value="h3">标题3</option></select><select id="font-family"><option value="Arial">Arial</option><option value="Times New Roman">Times New Roman</option><option value="Courier New">Courier New</option><option value="宋体">宋体</option><option value="黑体">黑体</option><option value="微软雅黑">微软雅黑</option></select><select id="font-size"><option value="1">8pt</option><option value="2">10pt</option><option value="3">12pt</option><option value="4">14pt</option><option value="5">18pt</option><option value="6">24pt</option><option value="7">36pt</option></select><input type="color" id="text-color" class="color-picker" value="#000000" title="文字颜色"><input type="color" id="bg-color" class="color-picker" value="#FFFFFF" title="背景颜色"><button id="align-left" title="左对齐">左对齐</button><button id="align-center" title="居中对齐">居中</button><button id="align-right" title="右对齐">右对齐</button><button id="insert-list" title="插入列表">列表</button><button id="insert-image" title="插入图片">图片</button><button id="insert-link" title="插入链接">链接</button><button id="insert-table" title="插入表格">表格</button><button id="undo-btn" title="撤销">撤销</button><button id="redo-btn" title="重做">重做</button><button id="import-word" title="导入Word">导入Word</button><button id="export-word" title="导出Word">导出Word</button><button id="export-html" title="导出HTML">导出HTML</button><input type="file" id="file-input" class="file-input" accept=".docx"><input type="file" id="image-input" class="file-input" accept="image/*" style="display: none;"></div><div id="editor" class="editor" contenteditable="true"></div><div class="status-bar"><span id="char-count">0</span> 字符 | <span id="word-count">0</span> 单词 | 光标位置: <span id="cursor-position">0:0</span></div></div><script>document.addEventListener('DOMContentLoaded', function() {const editor = document.getElementById('editor');const boldBtn = document.getElementById('bold-btn');const italicBtn = document.getElementById('italic-btn');const underlineBtn = document.getElementById('underline-btn');const headingSelect = document.getElementById('heading-select');const fontFamily = document.getElementById('font-family');const fontSize = document.getElementById('font-size');const textColor = document.getElementById('text-color');const bgColor = document.getElementById('bg-color');const alignLeft = document.getElementById('align-left');const alignCenter = document.getElementById('align-center');const alignRight = document.getElementById('align-right');const insertList = document.getElementById('insert-list');const insertImage = document.getElementById('insert-image');const insertLink = document.getElementById('insert-link');const insertTable = document.getElementById('insert-table');const undoBtn = document.getElementById('undo-btn');const redoBtn = document.getElementById('redo-btn');const importWord = document.getElementById('import-word');const exportWord = document.getElementById('export-word');const exportHtml = document.getElementById('export-html');const fileInput = document.getElementById('file-input');const imageInput = document.getElementById('image-input');const charCount = document.getElementById('char-count');const wordCount = document.getElementById('word-count');const cursorPosition = document.getElementById('cursor-position');// 初始化编辑器内容editor.innerHTML = '<p>开始编辑您的文档...</p>';// 更新字数统计和光标位置function updateCount() {const text = editor.innerText;charCount.textContent = text.length;wordCount.textContent = text.trim() === '' ? 0 : text.trim().split(/\s+/).length;// 更新光标位置const selection = window.getSelection();if (selection.rangeCount > 0) {const range = selection.getRangeAt(0);const preCaretRange = range.cloneRange();preCaretRange.selectNodeContents(editor);preCaretRange.setEnd(range.endContainer, range.endOffset);const line = preCaretRange.toString().split('\n').length;const column = range.endOffset;cursorPosition.textContent = `${line}:${column}`;}}editor.addEventListener('input', updateCount);editor.addEventListener('click', updateCount);editor.addEventListener('keyup', updateCount);updateCount();// 加粗boldBtn.addEventListener('click', function() {document.execCommand('bold', false, null);this.classList.toggle('active');});// 斜体italicBtn.addEventListener('click', function() {document.execCommand('italic', false, null);this.classList.toggle('active');});// 下划线underlineBtn.addEventListener('click', function() {document.execCommand('underline', false, null);this.classList.toggle('active');});// 标题样式headingSelect.addEventListener('change', function() {const value = this.value;if (value === 'paragraph') {document.execCommand('formatBlock', false, '<p>');} else {document.execCommand('formatBlock', false, `<${value}>`);}});// 字体fontFamily.addEventListener('change', function() {document.execCommand('fontName', false, this.value);});// 字号fontSize.addEventListener('change', function() {document.execCommand('fontSize', false, this.value);});// 文字颜色textColor.addEventListener('input', function() {document.execCommand('foreColor', false, this.value);});// 背景颜色bgColor.addEventListener('input', function() {document.execCommand('hiliteColor', false, this.value);});// 对齐方式alignLeft.addEventListener('click', function() {document.execCommand('justifyLeft', false, null);alignLeft.classList.add('active');alignCenter.classList.remove('active');alignRight.classList.remove('active');});alignCenter.addEventListener('click', function() {document.execCommand('justifyCenter', false, null);alignLeft.classList.remove('active');alignCenter.classList.add('active');alignRight.classList.remove('active');});alignRight.addEventListener('click', function() {document.execCommand('justifyRight', false, null);alignLeft.classList.remove('active');alignCenter.classList.remove('active');alignRight.classList.add('active');});// 插入列表insertList.addEventListener('click', function() {document.execCommand('insertUnorderedList', false, null);});// 插入图片insertImage.addEventListener('click', function() {const option = prompt('输入图片URL或选择"上传"从本地上传图片', '');if (option === '上传') {imageInput.click();} else if (option && option !== '') {document.execCommand('insertImage', false, option);}});imageInput.addEventListener('change', function(e) {const file = e.target.files[0];if (!file) return;const reader = new FileReader();reader.onload = function(event) {document.execCommand('insertImage', false, event.target.result);};reader.readAsDataURL(file);this.value = ''; // 重置input,以便可以重复选择同一文件});// 插入链接insertLink.addEventListener('click', function() {const url = prompt('请输入链接URL:');if (url) {const text = window.getSelection().toString() || '链接';document.execCommand('insertHTML', false, `<a href="${url}" target="_blank">${text}</a>`);}});// 插入表格insertTable.addEventListener('click', function() {const rows = prompt('输入行数:', '3');const cols = prompt('输入列数:', '3');if (rows && cols) {let tableHtml = '<table border="1" style="width:100%; border-collapse:collapse;">';for (let i = 0; i < parseInt(rows); i++) {tableHtml += '<tr>';for (let j = 0; j < parseInt(cols); j++) {tableHtml += '<td style="padding:8px;">内容</td>';}tableHtml += '</tr>';}tableHtml += '</table>';document.execCommand('insertHTML', false, tableHtml);}});// 撤销undoBtn.addEventListener('click', function() {document.execCommand('undo', false, null);updateCount();});// 重做redoBtn.addEventListener('click', function() {document.execCommand('redo', false, null);updateCount();});// 导入WordimportWord.addEventListener('click', function() {fileInput.click();});fileInput.addEventListener('change', function(e) {const file = e.target.files[0];if (!file) return;const reader = new FileReader();reader.onload = function(event) {const arrayBuffer = event.target.result;mammoth.extractRawText({arrayBuffer: arrayBuffer}).then(function(result) {editor.innerHTML = result.value;updateCount();}).catch(function(error) {console.error(error);alert('导入Word文件失败: ' + error.message);});};reader.readAsArrayBuffer(file);});// 导出Word - 使用HTML转DOCX的替代方案exportWord.addEventListener('click', function() {// 创建一个包含HTML内容的Blobconst htmlContent = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>body { font-family: Arial, sans-serif; margin: 20px; }h1 { color: #000000; }p { margin-bottom: 10px; }</style></head><body>${editor.innerHTML}</body></html>`;// 创建一个包含HTML内容的Blobconst blob = new Blob([htmlContent], { type: 'application/msword' });// 使用FileSaver.js保存文件saveAs(blob, "document.doc");});// 导出HTMLexportHtml.addEventListener('click', function() {const htmlContent = editor.innerHTML;const blob = new Blob([htmlContent], { type: 'text/html' });saveAs(blob, "document.html");});// 检查当前选区样式document.addEventListener('selectionchange', function() {const selection = window.getSelection();if (selection.rangeCount === 0) return;const range = selection.getRangeAt(0);const parentElement = range.commonAncestorContainer.parentElement;// 检查加粗boldBtn.classList.toggle('active', document.queryCommandState('bold'));// 检查斜体italicBtn.classList.toggle('active', document.queryCommandState('italic'));// 检查下划线underlineBtn.classList.toggle('active', document.queryCommandState('underline'));// 检查对齐方式const align = parentElement.style.textAlign || window.getComputedStyle(parentElement).textAlign;alignLeft.classList.remove('active');alignCenter.classList.remove('active');alignRight.classList.remove('active');if (align === 'left') alignLeft.classList.add('active');else if (align === 'center') alignCenter.classList.add('active');else if (align === 'right') alignRight.classList.add('active');// 更新光标位置updateCount();});// 添加键盘快捷键支持document.addEventListener('keydown', function(e) {// Ctrl+B - 加粗if (e.ctrlKey && e.key === 'b') {e.preventDefault();boldBtn.click();}// Ctrl+I - 斜体else if (e.ctrlKey && e.key === 'i') {e.preventDefault();italicBtn.click();}// Ctrl+U - 下划线else if (e.ctrlKey && e.key === 'u') {e.preventDefault();underlineBtn.click();}// Ctrl+Z - 撤销else if (e.ctrlKey && e.key === 'z') {if (!e.shiftKey) {e.preventDefault();undoBtn.click();}}// Ctrl+Y 或 Ctrl+Shift+Z - 重做else if ((e.ctrlKey && e.key === 'y') || (e.ctrlKey && e.shiftKey && e.key === 'z')) {e.preventDefault();redoBtn.click();}});});</script>
</body>
</html>

核心功能实现

1. 编辑器基础结构

编辑器采用经典的contenteditable属性实现可编辑区域:

<div id="editor" class="editor" contenteditable="true"></div>

配合CSS样式:

.editor {min-height: 500px;border: 1px solid #ddd;padding: 20px;border-radius: 4px;outline: none;
}

2. 格式设置功能

使用document.execCommand()实现基础格式设置:

// 加粗
boldBtn.addEventListener('click', function() {document.execCommand('bold', false, null);this.classList.toggle('active');
});// 斜体
italicBtn.addEventListener('click', function() {document.execCommand('italic', false, null);this.classList.toggle('active');
});// 标题样式
headingSelect.addEventListener('change', function() {const value = this.value;if (value === 'paragraph') {document.execCommand('formatBlock', false, '<p>');} else {document.execCommand('formatBlock', false, `<${value}>`);}
});

3. 样式状态同步

通过selectionchange事件监听选区变化,更新按钮状态:

document.addEventListener('selectionchange', function() {// 检查加粗状态boldBtn.classList.toggle('active', document.queryCommandState('bold'));// 检查斜体状态italicBtn.classList.toggle('active', document.queryCommandState('italic'));// 检查对齐方式const selection = window.getSelection();if (selection.rangeCount > 0) {const range = selection.getRangeAt(0);const parentElement = range.commonAncestorContainer.parentElement;const align = parentElement.style.textAlign || window.getComputedStyle(parentElement).textAlign;alignLeft.classList.remove('active');alignCenter.classList.remove('active');alignRight.classList.remove('active');if (align === 'left') alignLeft.classList.add('active');else if (align === 'center') alignCenter.classList.add('active');else if (align === 'right') alignRight.classList.add('active');}
});

4. 图片插入功能

支持URL输入和本地文件上传两种方式:

insertImage.addEventListener('click', function() {const option = prompt('输入图片URL或选择"上传"从本地上传图片', '');if (option === '上传') {imageInput.click(); // 触发隐藏的文件输入} else if (option && option !== '') {document.execCommand('insertImage', false, option);}
});// 本地文件上传处理
imageInput.addEventListener('change', function(e) {const file = e.target.files[0];if (!file) return;const reader = new FileReader();reader.onload = function(event) {document.execCommand('insertImage', false, event.target.result);};reader.readAsDataURL(file);this.value = ''; // 重置input
});

5. 表格插入功能

通过对话框获取行列数,动态生成HTML表格:

insertTable.addEventListener('click', function() {const rows = prompt('输入行数:', '3');const cols = prompt('输入列数:', '3');if (rows && cols) {let tableHtml = '<table border="1" style="width:100%; border-collapse:collapse;">';for (let i = 0; i < parseInt(rows); i++) {tableHtml += '<tr>';for (let j = 0; j < parseInt(cols); j++) {tableHtml += '<td style="padding:8px;">内容</td>';}tableHtml += '</tr>';}tableHtml += '</table>';document.execCommand('insertHTML', false, tableHtml);}
});

6. 文件导入导出

导入Word文档

fileInput.addEventListener('change', function(e) {const file = e.target.files[0];if (!file) return;const reader = new FileReader();reader.onload = function(event) {const arrayBuffer = event.target.result;mammoth.extractRawText({arrayBuffer: arrayBuffer}).then(function(result) {editor.innerHTML = result.value;updateCount();}).catch(function(error) {console.error(error);alert('导入Word文件失败: ' + error.message);});};reader.readAsArrayBuffer(file);
});

导出Word文档

exportWord.addEventListener('click', function() {const htmlContent = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Document</title><style>body { font-family: Arial, sans-serif; margin: 20px; }h1 { color: #000000; }p { margin-bottom: 10px; }</style></head><body>${editor.innerHTML}</body></html>`;const blob = new Blob([htmlContent], { type: 'application/msword' });saveAs(blob, "document.doc");
});

高级功能实现

1. 键盘快捷键支持

document.addEventListener('keydown', function(e) {// Ctrl+B - 加粗if (e.ctrlKey && e.key === 'b') {e.preventDefault();boldBtn.click();}// Ctrl+I - 斜体else if (e.ctrlKey && e.key === 'i') {e.preventDefault();italicBtn.click();}// Ctrl+U - 下划线else if (e.ctrlKey && e.key === 'u') {e.preventDefault();underlineBtn.click();}// Ctrl+Z - 撤销else if (e.ctrlKey && e.key === 'z') {if (!e.shiftKey) {e.preventDefault();undoBtn.click();}}// Ctrl+Y 或 Ctrl+Shift+Z - 重做else if ((e.ctrlKey && e.key === 'y') || (e.ctrlKey && e.shiftKey && e.key === 'z')) {e.preventDefault();redoBtn.click();}
});

2. 字数统计功能

function updateCount() {const text = editor.innerText;charCount.textContent = text.length;wordCount.textContent = text.trim() === '' ? 0 : text.trim().split(/\s+/).length;// 更新光标位置const selection = window.getSelection();if (selection.rangeCount > 0) {const range = selection.getRangeAt(0);const preCaretRange = range.cloneRange();preCaretRange.selectNodeContents(editor);preCaretRange.setEnd(range.endContainer, range.endOffset);const line = preCaretRange.toString().split('\n').length;const column = range.endOffset;cursorPosition.textContent = `${line}:${column}`;}
}

第三方库使用

  1. Mammoth.js:用于将Word文档(.docx)转换为HTML

    <script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.4.0/mammoth.browser.min.js"></script>
    
  2. FileSaver.js:用于文件保存功能

    <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
    

改进建议

  1. 样式保留:当前导入Word时只保留了纯文本,建议使用Mammoth的完整转换功能

    mammoth.convertToHtml({arrayBuffer: arrayBuffer}).then(function(result) {editor.innerHTML = result.value;updateCount();})
    
  2. 撤销重做系统document.execCommand的撤销重做功能有限,建议实现自定义的历史记录栈

  3. 响应式设计:在小屏幕设备上优化工具栏布局

  4. 协作编辑:添加WebSocket支持实现多人协作

总结

本文实现了一个功能完备的富文本编辑器,具有以下特点:

  1. 完整的Word风格界面
  2. 多种格式设置功能
  3. 图片和表格插入
  4. 文件导入导出
  5. 键盘快捷键支持
  6. 字数统计和光标位置显示

这个编辑器可以作为基础框架,根据实际需求进行扩展,如添加Markdown支持、PDF导出、模板功能等。对于生产环境使用,建议考虑使用成熟的编辑器库如Quill、TinyMCE或CKEditor,但理解底层实现原理对于深入掌握前端开发至关重要。

相关文章:

  • AI-02a5a7.神经网络-与学习相关的技巧-正则化
  • leetcode 合并区间 java
  • 【神经网络与深度学习】激活函数的可微可导
  • IDEA2025版本使用Big Data Tools连接Linux上Hadoop的HDFS
  • [面试精选] 0001. 两数之和
  • 【解决】SSH 远程失败之路由配置问题
  • laravel中如何使用Validator::make定义一个变量是 ,必传的,json格式字符串
  • 【git】在Windows上搭建git服务器
  • 使用Java实现Navicat密码的加密与解密
  • Python训练营打卡 Day31
  • 牛客网 NC14736 双拆分数字串 题解
  • 【windows】音视频处理工具-FFmpeg(合并/分离)
  • I2C 协议的理解以及在 OLED 上的应用
  • mac上安装 Rust 开发环境
  • 数据分析_商务运营考核指标体系搭建
  • 【爬虫】12306自动化购票
  • [原创](现代Delphi 12指南):[macOS 64bit App开发]: 如何获取目录大小?
  • os agent智能体软件 - 第三弹 - 纯语音交互
  • 解决npm install报错:getaddrinfo ENOTFOUND registry.nlark.com
  • 如何从不同位置将联系人导入 iPhone(完整指南)
  • 央行行长潘功胜主持召开金融支持实体经济座谈会
  • 上海中心城区首条“定制化低空观光航线”启航,可提前一天提需求
  • 历史缝隙里的人︱觑功名如画饼:盛世“做题家”的攀爬与坠落
  • 上海迪士尼蜘蛛侠主题园区正式动工,毗邻“疯狂动物城”
  • CBA官方对孙铭徽罚款3万、广厦投资人楼明停赛2场罚款5万
  • 见微知沪|科学既要勇攀高峰,又要放低身段