编辑器相关知识整理
富文本编辑器在前端范畴一直是比较复杂、难度非常大一件事情,各个社区、论坛都有自己写的比较好的编辑器,但是估计是因为研发成本太高,故此没有对外开源。 做的比较好的石墨文档、语雀、谷歌文档等, 用语雀比较多,功能真的很完善,表格操作、md格式编辑、脑图编辑等。
这里先整理了一些知识:
- hax整理的:https://github.com/hax/hax.github.com/issues/41
- 基本覆盖的比较全了, 衍生自定义的定制实现:
- 基于quill的vue实现: https://surmon-china.github.io/vue-quill-editor/
- 基于quill的react实现:https://github.com/zenoamaro/react-quill
- 基于draft的实现:https://github.com/margox/braft-editor
富文本编辑器的原理:
基于document的execCommand处理,
手动实现一个编辑器
/**
* Pencil.js
* @param {*} el
* @param {*} options
* @docs https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
*/
function Pencil(el, options) {
options = options || {};
if (typeof el === 'string')
el = document.querySelector(el);
el.className = 'editor';
el.toolbar = document.createElement('div');
el.content = document.createElement('div');
el.toolbar.className = 'editor-toolbar';
el.content.className = 'editor-content';
el.content.contentEditable = true;
el.content.oninput = options.change.bind(el);
el.content.onkeydown = function (e) {
if (e.which === 9) e.preventDefault();
};
el.exec = function (command, value) {
return document.execCommand(command, false, value);
};
var actions = Pencil.actions;
options.actions.map(function (action) {
if (typeof action === 'string')
return actions[action];
if (actions[action.name])
return Object.assign({}, actions[action.name], action);
return action;
}).forEach(function (action, i) {
var button = document.createElement('button');
var title = action.title || action.name || ('F' + (i + 1));
button.title = title;
button.innerHTML = action.icon || title;
button.onclick = action.action.bind(el, el);
button.className = 'editor-toolbar-button';
el.toolbar.appendChild(button);
});
el.appendChild(el.toolbar);
el.appendChild(el.content);
return el;
}
Pencil.actions = {
bold: {
icon: '<b>B</b>',
title: 'Bold',
action: editor => editor.exec('bold')
},
italic: {
icon: '<i>I</i>',
title: 'Italic',
action: editor => editor.exec('italic')
},
underline: {
icon: '<u>U</u>',
title: 'Underline',
action: editor => editor.exec('underline')
},
strikethrough: {
icon: '<strike>S</strike>',
title: 'Strike-through',
action: editor => editor.exec('strikeThrough')
},
heading1: {
icon: '<b>H<sub>1</sub></b>',
title: 'Heading 1',
action: editor => editor.exec('formatBlock', '<H1>')
},
heading2: {
icon: '<b>H<sub>2</sub></b>',
title: 'Heading 2',
action: editor => editor.exec('formatBlock', '<H2>')
},
paragraph: {
icon: '¶',
title: 'Paragraph',
action: editor => editor.exec('formatBlock', '<P>')
},
quote: {
icon: '“ ”',
title: 'Quote',
action: editor => editor.exec('formatBlock', '<BLOCKQUOTE>')
},
olist: {
icon: '#',
title: 'Ordered List',
action: editor => editor.exec('insertOrderedList')
},
ulist: {
icon: '•',
title: 'Unordered List',
action: editor => editor.exec('insertUnorderedList')
},
code: {
icon: '</>',
title: 'Code',
action: editor => editor.exec('formatBlock', '<PRE>')
},
line: {
icon: '―',
title: 'Horizontal Line',
action: editor => editor.exec('insertHorizontalRule')
},
link: {
icon: '🔗',
title: 'Link',
action: editor => {
const url = window.prompt('Enter the link URL')
url && editor.exec('createLink', url)
}
},
image: {
icon: '📷',
title: 'Image',
action: editor => {
const url = window.prompt('Enter the image URL')
url && editor.exec('insertImage', url)
}
},
bgcolor: {
icon: '<span style="background:#fadb20; padding:1px 3px">A</span>',
title: 'Background Color',
action: editor => {
const color = window.prompt('Enter the Color')
color && editor.exec('backColor', color)
}
},
copy: {
icon: '©',
title: 'Copy to Clipboard',
action: editor => editor.exec('copy')
},
paste: {
icon: '📋',
title: 'Patse',
action: editor => editor.exec('paste')
},
cut: {
icon: '✂️',
title: 'Cut Selection',
action: editor => editor.exec('cut')
},
small: {
icon: 'A-',
title: 'Decrease Font Size',
action: editor => editor.exec('decreaseFontSize')
},
big: {
icon: 'A+',
title: 'Increase Font Size',
action: editor => editor.exec('increaseFontSize')
},
font: {
icon: 'Aa',
title: 'Font Family',
action: editor => {
const font = window.prompt('Enter the Font Name')
font && editor.exec('fontName', font)
}
},
size: {
icon: 'Aa',
title: 'Font Size',
action: editor => {
const size = window.prompt('Enter the Font Size(1-7)')
size && editor.exec('fontSize', size)
}
},
color: {
icon: '<span style="color:red;">Aa</span>',
title: 'Font Color',
action: (editor, e) => {
const icon = e.target;
const colorPicker = document.createElement('input');
colorPicker.type = 'color';
colorPicker.onchange = e => {
console.log('colorPicker', e);
const color = e.target.value;
icon.style.color = color;
editor.exec('foreColor', color)
};
colorPicker.click();
}
},
html: {
icon: '</>',
title: 'Insert HTML Code',
action: editor => {
const html = window.prompt('Enter the HTML')
html && editor.exec('insertHTML', html)
}
},
center: {
icon: '_A_',
title: 'Text Align Center',
action: editor => editor.exec('justifyCenter')
},
left: {
icon: 'A__',
title: 'Text Align Left',
action: editor => editor.exec('justifyLeft')
},
right: {
icon: '__A',
title: 'Text Align Right',
action: editor => editor.exec('justifyRight')
},
clearFormat: {
icon: '🧹',
title: 'Clear Format',
action: editor => editor.exec('removeFormat')
},
subscript: {
icon: 'A<sub>a</sub>',
title: 'Sub Script',
action: editor => editor.exec('subscript')
},
superscript: {
icon: 'A<sup>a</sup>',
title: 'Super Script',
action: editor => editor.exec('superscript')
},
indent: {
icon: '➡️',
title: 'Indent',
action: editor => editor.exec('indent')
},
outdent: {
icon: '⬅️',
title: 'Outdent',
action: editor => editor.exec('outdent')
},
table: {
icon: 'T',
title: 'Table',
action: editor => {
var table = '';
table += '<table border="1" >';
table += '<tr>';
table += '<td>Heading1</td>';
table += '<td>Heading2</td>';
table += '<td>Heading3</td>';
table += '</tr>';
table += '<tr>';
table += '<td>1</td>';
table += '<td>2</td>';
table += '<td>3</td>'
;
table += '</tr>';
table += '</table>';
editor.exec('insertHTML', table)
}
}
};
参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand