Rich text editor allows users to apply different formats, styles and other functions when entering and editing text content, such as graphic and text mixing, and has the ability to see what you see is what you get. Components with simple plain text editing<input>
Different from other things, rich text editors provide more functionality and flexibility, allowing users to create richer and structured content. Modern rich text editors are no longer limited to text and pictures, but also include videos, tables, code blocks, attachments, formulas and other relatively complex modules.
- Open source address:/WindRunnerMax/BlockKit
- Online editing:/BlockKit/
- Project Notes:/WindRunnerMax/BlockKit/blob/master/
Why?
So why do you need to implement a new rich text editor from zero design? Editors are recognized as sinkholes, and there are already many excellent editor implementations. For example, highly expressive data structure designQuill
, combinedReact
View layer'sDraft
, pure editor engineSlate
, highly modularProseMirror
, out of the boxTinyMCE/TipTap
, integrated collaborative solutionsEtherPad
etc.
I am also concerned about the implementation of various rich text editors, including editor implementation articles on various sites. But I found that there are very few of them talking about the underlying design of rich text editors, and most of them talking about the application layer, such as how to use the editor engine to implement certain functions, etc. Although the implementation of these application layers itself will have some complexity, the underlying design is a more worthy of discussion.
In addition, I think rich text editors are very similar to low-code designs, to be preciseNo Code
A kind of implementation. In essence, both low code and rich text are based onDSL
Description to operateDOM
Structure, but rich text is mainly operated through keyboard inputDOM
, while no code is used to operate by dragging and dropping.DOM
, I think there should be some common design ideas here.
I happened to be focusing on the application layer implementation of the editor some time ago. I also encountered many problems in the specific implementation process and recorded related articles. However, in the process of implementation of the application layer, I encountered many places that I personally think can be optimized, especially at the data structure level. I hope to apply some of my ideas. Specifically, there are several main reasons:
Editor column
The knowledge you get from paper is always shallow, and you must practice it yourself to know it.
My blog is from20
I started writing in 2019, and I recorded a lot of content. I basically wrote whatever I thought of, after all, it was used as a record of my daily study. Then in24
I have written a lot of articles about rich text editors in 2019, mainly to organize the problems and solutions I encountered in daily life, focusing on the design of the application layer, for example:
- A preliminary study on document virtual scrolling in rich texts
- A preliminary study on OT collaborative algorithm for rich texts
- ...
In addition, I also studied it some time agoslate
Rich text editor-related implementations and alsoslate
Some warehouses have been mentionedPR
. I also wrote someslate
Related articles, and also based onslate
Implementing a document editor, which also focuses more on the implementation of the application layer, for example:
- WrapNode data structure and operation transformation
- Node node and Path path mapping
- ...
After implementing many application layer functions, I found that there are many areas for in-depth research on the entire editor. In particular, some implementations seem very natural, but if you study them carefully, you will find that there are many details to explore, such asDOM
Common zero-width characters after structure,Mention
The rendering of nodes, etc., can be taken out to record articles separately. This is actually the most important reason why I want to implement the editor from scratch.
24
I have written a lot of business stuff since 2019.25
I feel a little bit difficult about the topics in the year, and I don’t have any other aspects at the moment. It is a better choice to write editor-related content, so it will be easier to choose the topics for the article. However, although I want to write content related to the editor in depth, I will still record it when I encounter problems. For example, recently, there is a based onimmer
CooperateOT-JSON
The idea of implementing state management can be realized.
For the specific implementation of the editor, my current goal is to implement the available editor, rather than a very good compatibility and fully functional editor. Mainly, there are many excellent editor implementations now, and there are many ecological plug-ins to support, which can meet most needs. The editor I want to implement is mainly compatibleChrome
Just browser, but I won’t consider the issues on mobile for the time being. However, if the editor can be made better, you can naturally do compatibility adaptation.
However, at present, we still tentatively design and implement the editor. We will inevitably encounter many problems during this period, and these problems will also become the main content of the column. At the beginning, I was going to complete the editor before starting to write an article. Later, I found that the historical solutions in the design process were also valuable, so I decided to record the design process together. If the editor can be really suitable for production environments in the future, then these articles can be traced to why the module is designed in this way, which is probably excellent. Overall, we cannot become fat by eating in one bite, but it is OK to eat in one bite.
In-depth editor
This part reminds me of a sentence: our rich text editor is like this, you don’t understand if you don’t write it.
Editor is a very detailed project, and often requires in-depth research on the browser.API
,For exampledocument
OncaretPositionFromPoint
Method to obtain the selection position of a certain point, which is usually used to position the landing point after dragging text. In addition, there are many constituencies related toAPI
,For exampleSelection
、Range
Wait, these are the basis of editor implementation.
Then it is very meaningful to go deep into the underlying editor. Many times we need to deal with the browser, which will be valuable even for our daily business development. Here I would like to talk about the zero-width characters in the editor and use this example to learn the detailed design of the editor. This is a very interesting topic. Content like this is an interesting thing that you will not pay attention to if you don’t study it.
As the name suggests, zero-width characters are characters with no width, so it is easy to infer that these characters are not visually displayed. Therefore, these characters can be used as invisible placeholder content to achieve special effects. For example, information hiding can be implemented to realize the function of watermarks, encrypted information sharing, etc. Some novel sites will trace the piracy through this method and glyph replacement.
And in a rich text editor, if we check elements in the developer tool, we may find something similar to​
Right nowU+200B
Similar characters, this is the common zero-width character. For example, in the editor of Feishu Document, we use("[data-enter]")
You can check the zero-width characters that exist.
<!-- ("[data-enter]") -->
<span data-string="true" data-enter="true" data-leaf="true">\u200B</span>
<span data-string="true" data-enter="true" data-leaf="true">​</span>
So from the name point of view, this zero-width character is not visually displayed because it is zero-width. But in the editor, this character is very important. Simply put, we need this character to place the cursor and do additional display effects. It should be noted that we are referring toContentEditable
This part of the design is not necessary if the editor of the self-drawn selection.
Let’s talk about the additional display effect first. For example, when we select the text content of Feishu document, if we select the end of the text, we will find that the end will have an additional shape similar to the end.xxx|
effect. If you don't pay attention to it, you may think that this is the editor's default behavior, but in fact, this effect is not the sameslate
stillquill
None of them exists.
In fact, this effect is achieved using zero-width characters. Insert zero-width characters at the end of the line content to achieve the text selection effect at the end. Actually, this effect isword
More common in this, that is, the carriage return symbol for additional rendering.
<div contenteditable="true">
<div><span>Zero-width characters at the end Line 1</span><span>​</span></div>
<div><span>Zero-width characters at the end Line 2</span><span>​</span></div>
<div><span>Pure text at the end Line 1</span></div>
<div><span>Pure text at the end Line 2</span></div>
</div>
Then if this zero-width character is just a rendering effect, it may not actually play a role. But this effect is very useful in interaction, for example, at this time we have3
Line text, if at this time from1
Select the line at the end2
When running, pressTab
key, then the contents of these two lines will be indented.
Then if there is no display effect, the indent operation is performed at this time, and the user may think that the first one has been selected.2
OK, but actually selected1/2
Two lines of text. If this happens, users may think it isBUG
, and we have actually received feedback on this interaction effect.
123|
4|x56
A simple survey was also conducted on the implementation of various online documents: Based oncontenteditable
In the implemented editor, Feishu Documentation, earlyEtherPad
There is this interactive implementation; in the editor of the self-drawing selection, this implementation exists for DingTalk Document;Canvas
In the editor implemented by the engine, Tencent Documents,Google Doc
This implementation exists.
In the rendering effect part, another important function of zero-width characters is to support the line content. When our line content is empty, this line isDOM
The content of the structure is empty, which causes the height collapse of this line to0
, and the cursor cannot be placed. To solve this problem, we can choose to insert zero-width characters into the line content, so that the line content can be supported and the cursor can be placed. Of course use<br>
It is also possible to support the height of the line. Using these two solutions will have their own advantages and disadvantages, and the compatibility will be different.
<div data-line-node></div>
<div data-line-node><br></div>
<div data-line-node><span>​</span></div>
Similar toNotion
There is also a relatively important interactive effect in this block structure editor. That is, block-level structures are independently selected. For example, we can directly select the entire code block independently, rather than just selecting the text in it. This effect is rarely implemented in current open source editors, and it requires reorganizing the design of the selection in a block structure.
Generally speaking, this interaction can also be implemented using zero-width characters. Because our selections usually need to be placed on text nodes, it is easy to imagine that we can place zero-width characters at the end of the line where the block structure is located, and the entire block is selected when the selection is on zero-width characters. Use zero-width characters instead of<br>
The advantage is that the zero-width character itself is zero-width and does not cause additional line breaks.
<div>
<pre><code>
xxx
</code></pre>
<span data-zero-block>​</span>
</div>
Structurally, zero-width characters have a very important implementation. In the editorcontenteditable=false
There will be special manifestations in nodes similar toinline-block
In nodes, for exampleMention
In a node, when there is nothing in front of and behind the node, we need to add zero-width characters before and after it to place the cursor.
In the following example,line-1
It is impossible to place the cursor on@xxx
After the content, although we can place the cursor before, the cursor position is atline node
On, it does not meet our expected text nodes. Then we must add zero-width characters after that,line-2/3
We can see the correct cursor placement effect in this. Here0.1px
It's also for the purpose of compatible cursor placementmagic
, there is no suchhack
In other words, the cursor of the node of the same level cannot be placed in theinline-block
After the node.
<div contenteditable style="outline: none">
<div data-line-node="1">
<span data-leaf><span contenteditable="false" style="margin: 0 0.1px;">@xxx</span></span>
</div>
<div data-line-node="2">
<span data-leaf>​</span>
<span data-leaf><span contenteditable="false" style="margin: 0 0.1px;">@xxx</span></span>
<span data-leaf>​</span>
</div>
<div data-line-node="3">
<span data-leaf>​<span contenteditable="false">@xxx</span>​</span>
</div>
</div>
In addition, the editor naturally needs to deal with characters, sojs
ShownUnicode
Coding is being implemented,emoji
It is the most common and prone to problems. Except for its single length2
In addition to this situation, the combinationemoji
Also uses unique zero-width hyphen\u200d
To express it.
"🎨".length
// 2
"🧑" + "\u200d" + "🎨"
// 🧑🎨
Data structure design
The design of the editor data structure is a very wide impact, whether it is maintaining the editor's text content, nesting block structures, serialization and deserialization, etc., or at the platform application level.diff
Algorithms, search replacements, collaborative algorithms, etc., as well as data conversion and export of back-end servicesmd/word/pdf
, data storage, etc. will all involve the editor's data structure design.
Generally speaking, based onJSON
Nested data structures to express the editorModel
It is very common, for exampleSlate
、ProseMirror
、Lexical
etc. byslate
As an example, whether it is data structure or selection design, it is as inclined as possible toHTML
Therefore, there can be nesting of many hierarchical nodes.
[
{
type: "paragraph",
children: [{ text: "editable" }],
},
{
type: "ul",
children: [
{
type: "li",
children: [{ text: "list" }],
},
],
},
];
Expressing document content through linear flat structure is also a common implementation solution, for exampleQuill
、EtherPad
、Google Doc
etc. byquill
As an example, the data structure expression on its content will not be nested, of course, essentiallyJSON
structure, while constituencies adopt a more streamlined expression.
[
{ insert: "editable\n" },
{ insert: "list\n", attributes: { list: "bullet" } },
];
Of course there are many special data structure designs, such asvscode/monaco
ofpiece table
Data structure. Isn't a code editor a rich text editor? After all, it can support code highlighting functions, but it's similar topiece table
I haven't studied the structure in depth yet.
Here I hope to express the entire rich text structure in a linear data structure. Although nested structures can express document content more intuitively, the operation of content will be more complicated, especially when there are nested content. byslate
For example, in0.50
Previous versionAPI
The design is very complex and requires a relatively large understanding cost, although it has been simplified a lot later:
// /ianstormtaylor/slate/blob/6aace0/packages/slate/src/interfaces/
export type NodeOperation =
| InsertNodeOperation
| MergeNodeOperation
| MoveNodeOperation
| RemoveNodeOperation
| SetNodeOperation
| SplitNodeOperation;
export type TextOperation = InsertTextOperation | RemoveTextOperation;
From here, we can see thatslate
It is necessary to complete the operation of the document content9
Types ofOp
. If it is based on a linear structure, we only need three types of operations to express the operations of the entire document. Of course for some similarMove
The operation requires additional selectionsRange
Computation processing is equivalent to handing over the calculation cost to the application layer.
// /WindRunnerMax/BlockKit/blob/c24b9e/packages/delta/src/delta/
export interface Op {
// Only one property out of {insert, delete, retain} will be present
insert?: string;
delete?: number;
retain?: number;
attributes?: AttributeMap;
}
In addition, nested structurenormalize
It will become very complex, and the time complexity caused by changes will also increase, especially the dirty path marking algorithm and the tagged data processing also need to be as followsOp
deal with. There are also user operations that cannot be controlled very well, sonormalize
Standardize data during the process, otherwise, the following is for example pastingHTML
There may be a large amount of data nesting when it is.
[{
children: [{
children: [{
children: [{
children: [{
// ...
text: "content"
}]
}]
}]
}]
}]
Let me give you a more practical example, if we have nested content in format at this time. For examplequote
andlist
Nested in two formats. If the data structure of our document is a nested structure at this time, the operation content will exist.ul > quote
orquote > ul
In two situations, under normal circumstances, we must design rules to donormalize
; and under the flat structure, all attributes are written inattrs
Within, the data format changes caused by different operations are completely idempotent.
// slate
[{
type: "quote",
children: [{
type: "ul",
children: [{ text: "text" }]
}],
}, {
type: "ul",
children: [{
type: "quote",
children: [{ text: "text" }]
}],
}]
// quill
[{
insert: "text",
attributes: { blockquote: true, list: "bullet" }
}]
Flat data structures will have advantages in data processing, but at the view level, it will be more difficult for flat data structures to express structured data, such as expressing nested structures such as code blocks and tables. But this is not impossible, for exampleGoogle Doc
The complex table nesting is a completely linear structure, and there is a clever design inside, so I won't expand it here.
In addition, if we need to implement an editor for online documents, it may be necessary to do so throughout the management processdiff
, that is, to obtain the addition, deletion and modification of the data structures on both sides. In this case, the flat data structure can better process text content, andJSON
Nested structured data will be much trouble. There are also some other peripheral applications regarding data processing, and the overall complexity has been greatly improved.
Finally, there are still implementations related to collaboration, and the collaborative algorithm is an optional module for rich text editors. Whether based onOT
The collaborative algorithm orOp-Based CRDT
The collaborative algorithms all need to transmit the aboveop
Type and data, then it is obvious9
Such operationop
Types will be better3
Such operationop
The types are more complex.
- : Text Data Type
- ShareDB Rich-Text: Delta OT Data Type
- ShareDB JSON0: JSON OT Data Type
- ShareDB Slate: Slate OT Data Structure Adapter
- YJS YText: Delta data type implementation
- YJS YMap/YArray: JSON data type implementation
- YJS Slate: Slate Data Structure Adapter
Therefore, I want to be able to implement the entire editor structure with a linear data structure, soquill
ofdelta
It's a very good choice. butquill
It is a self-implemented view layer structure, not a combinationreact
In the form of view layers, the advantage of combining these view layers is that you can directly use component library styles to implement the editor, avoiding that each component needs to be implemented by itself. Then I'm going to base it onquill
The data structure to implement the core layer of rich text editors from zero, and likeslate
This way, the basic view layer is combined.
Program selection
Actually, there is an interesting question here, why can't you use it?1mb
The amount of code can be implemented in some casesoffice word
The ability of editors is because the browser has done a lot of things for us and throughAPI
Provided to developers, including input method processing, font parsing, typography engine, view rendering, etc.
Therefore, we need to design a solution to interact with the browser, after all, we actually need to interact with the browser. The most classic description of rich text editors is divided into three levels:
-
L0
: Based on browser-providedContentEditable
Implement rich text editing, using browserExecute the command operation. It is an early lightweight editor that can be quickly developed in a short time, but the space for customization is very limited.
-
L1
: Also based on the browser providedContentEditable
Implement rich text editing, but data drivers can customize the execution of data models and commands. Common implementations include Yuque, Feishu Documents, etc., which can meet most usage scenarios, but cannot break through the browser's own layout effect. | -
L2
: based onCanvas
Implement the layout engine independently and only rely on a small number of browsersAPI
. Common implementations includeGoogle Docs
, Tencent Documents, etc., the specific implementation needs to be completely controlled by yourself, which is equivalent to using artboards instead ofDOM
It is very technically difficult to draw rich text.
In fact, in the current open source products, these three types of editors are involved, especially most open source products.L1
Type implementation. And this is differentiated and does not depend on itContentEditable
But it is not a completely self-drawing engine, but a dependencyDOM
The implementation of presenting content plus self-drawing selection can actually be considered asL1.5
level.
For the purpose of learning, we naturally choose to implement more open source products, so that we can better learn from and analyze related content when encountering problems. Therefore, I also plan to choose based onContentEditable
, implement data-driven standardsMVC
The rich text editor of the model interacts with the browser in this way to realize basic rich text editing capabilities. Before this, let’s first understand the basic editor implementation:
ExecCommand
If we only need the most basic in-line styles, such as bold, italic, underline, etc., which may be sufficient in some basic input boxes, then we can naturally choose to useexecCommand
To achieve it. Even directly based onexecCommand
The advantage is that it will be very small, for examplepellThe implementation only requires3.54KB
code volume, in additionreact-contenteditableSo far, it is realized.
We can also achieve the minimum that can be boldedDEMO
,execCommand
The command can becontenteditable
The element in the selection is executed,The method accepts three parameters, namely the command name, display user interface, and command parameters. The user interface is usually displayed
false
,Mozilla
Not implemented, while the command parameters are optional, for example, the hyperlink command needs to pass the specific link address.
<div>
<button >Bolding</button>
<div style="border: 1px solid #eee; outline: none" contenteditable>
123123
</div>
</div>
<script>
$ = () => {
("bold");
};
</script>
Of course, this example is too simple. We can also judge the bold state of the bold button when the selection is changed to display the current selection state. But we need to alignexecCommand
The command behavior of the above mentioned is very poor controllability, so we need to passIterate over all selection nodes to judge the status of the current selection.
Actually, it is also important to note here,execCommand
The behavior of commands is inconsistent in each browser, which is also a type of browser-compatible behavior we mentioned earlier. However, we have no way to control these behaviors, and these are their default behaviors:
- In the air
contenteditable
In the case of the editor, press Enter directly,Chrome
The manifestation in the process is that it will be inserted<div><br></div>
, and inFireFox(<60)
The manifestation in the process is that it will be inserted<br>
,IE
The manifestation in the process is that it will be inserted<p><br></p>
。 - In an editor with text, if inserting a carriage input in the middle of the text, e.g.
123|123
,existChrome
The content in123<div>123</div>
, and inFireFox
The content is formatted as<div>123</div><div>123</div>
。 - Also in an editor with text, if you insert Enter in the middle of the text and then delete Enter, for example
123|123->123123
,existChrome
The content in the performance will return to the original123123
, and inFireFox
The performance in the<div>123123</div>
。 - When two lines of text exist at the same time, if two lines of content are selected at the same time, then execute
("formatBlock", false, "P")
Command, inChrome
The performance in this is that two lines of content will be wrapped in the same<p>
In, and inFireFox
The performance in this is that two lines of content will be wrapped separately<p>
Label. - ...
In addition, there is a function similar to implementing bolding, which we cannot control is used<b></b>
To achieve bolding or<strong></strong>
To achieve boldness. There are also browser compatibility issues, such asIE
Used in the browser<strong></strong>
To achieve boldness,Chrome
It is used<b></b>
To achieve boldness,IE
andSafari
Not supported to passheading
Commands to implement title commands, etc. And for some more complex functions, such as pictures, code blocks, etc., it cannot be implemented well.
Of course, the default behavior is not completely useless, in some cases we may want to implement pureHTML
editor. After all, if it is based onMVC
The editor implementation of the mode will be processedModel
Invalid data content, which leads to the originalHTML
Content is lost, so the default behavior of relying on the browser in this demand context is probably the most effective, in this case we may be mainly concerned aboutXSS
The process has been handled.
ContentEditable
ContentEditable
yesHTML5
An attribute in which the element can be editable and combined with the built-inexecCommand
It's the most basic thing we talked about aboveDEMO
. Then if you want to implement the most basic text editor, you only need to enter the following content in the address bar:
data:text/html,<div contenteditable style="border: 1px solid black"></div>
Then passTo execute command modification
HTML
Although the solution is simple, we have also talked about its poor controllability. In addition to the aboveexecCommand
In addition to the compatibility issues of command execution, there are many moreDOM
The behaviors on the need to be processed compatiblely, such as the following sentences in a simple bold format:
123**456**789
There are many ways to express such content, and the editor can think that the display effect is equivalent, and this type may also be required at this time.DOM
Structural equivalent treatment:
<span>123<b>456</b>789</span>
<span>123<strong>456</strong>789</span>
<span>123<span style="font-weight: bold;">456</span>789</span>
But here is just visually equal, and it corresponds completely toModel
When you go, it will naturally be a troublesome thing. In addition, the expression of the constituent area is also a complex problem.DOM
Structure as an example:
<span>123</span><b><em>456</em></b><span>789</span>
If we want to express the selection fold in4
When this character is left, multiple expressions will also appear to achieve this position, which will actually rely heavily on the browser's default behavior:
{ node: 123, offset: 3 }
{ node: <em></em>, offset: 0 }
{ node: <b></b>, offset: 0 }
Therefore, in order to strengthen expansion and controllability, it also solves the problem that data and views cannot correspond.L1
The rich text editor uses the concept of custom data models. That's rightDOM
The data structure extracted from the tree, the same data structure can ensure renderingHTML
The same is true. The data model is directly controlled with custom commands to ultimately ensure rendering.HTML
Document consistency. For the expression of the selection, it is necessary toDOM
Continuous constituenciesnormalize
SelectionModel
。
Actually, this is what we often seeMVC
Model, when executing commands, the current model will be modified and then displayed on the rendering of the view. Simply put, it is to build a data model that describes the structure and content of the document and use a customexecCommand
Modify the data description model. At this stage, the rich text editor solves the problem that dirty data and complex functions in rich text are difficult to achieve by extracting the data model. We can also roughly describe the process:
<script>
const editor = {
// Model selection
selection: {},
execCommand: (command, value) => {
// Execute specific commands, such as bold
// After the command is executed, update the Model and call DOM rendering
},
}
const model = [
// Data model
{ type: "bold", text: "123" },
{ type: "span", text: "123123" },
];
const render = () => {
// Render the specific DOM according to type
};
("selectionchange", () => {
// When the selection is changed
// Update model selection according to dom selection
});
</script>
And similar solutions, whether it isquillstillslateAll are dispatched like this. Similar toslate
The implementation is connected through the adapterReact
After that, more complex compatibility processing is needed. existReact
Join in the nodeContentEditable
After that, something like the following will appearwarning
:
<div
contentEditable
suppressContentEditableWarning
></div>
// A component is `contentEditable` and contains `children` managed by React. It is now your responsibility to guarantee that none of those nodes are unexpectedly modified or duplicated. This is probably not intentional.
thiswarning
It means,React
Can't guaranteeContentEditable
In-housechildren
It will not be modified or copied unexpectedly, which may not be expected. That is to say, exceptReact
It will need to be executedDOM
Operation, usedContentEditable
After that, this behavior becomes uncontrollable, and naturally this problem will also appear in our editor.
There are also some other behaviors, such as in the following example, we cannot get from123
Character selected456
superior. That is, there is a leap hereContentEditable
Once the node is used, the browser's default behavior cannot be handled normally. Although this processing is very reasonable, it will also be implemented for us.blocks
The editor causes some trouble.
<div contenteditable="false">
<div contenteditable="true">123</div>
<div contenteditable="true">456</div>
</div>
So we can avoid using itContentEditable
, imagine that even if we do not implement the editor, we can also select the text content on the page, which is our ordinary selection implementation. Then if you implement it with the help of native selection and then implement the controller layer on this basis, you can implement a fully controlled editor.
But there is a big problem here, which is the input of content, because it is not enabledContentEditable
If the cursor cannot appear, and naturally the content cannot be entered. And if we want to wake up content input, we need to wake up especiallyIME
Input method, the routine given by the browserAPI
Just rely on the help<input>
To complete it, so we must implement hidden<input>
To implement input, in fact, many code editors such asCodeMirrorIt's a similar implementation.
But use hidden<input>
Other problems will arise because the focus is oninput
When on, the browser's text cannot be selected. Because in the same page, the focus will only exist one position, in this case, we must draw the implementation of the selection. For example, DingTalk Documents and Youdao Cloud Notes are self-drawn selections, open sourceMonaco EditorAlso self-painted selection,TextBusThe cursor is drawn, and the selection is implemented with the help of a browser.
Here you can summarize it, useContentEditable
Much needs to be processedDOM
, but obviously we don't need to deal with the awake input too much. If not usedContentEditable
, but useDOM
To present rich text content, additional hiding must be usedinput
Nodes implement input. In this case, the browser's selection behavior cannot be used, so the implementation of self-drawing selection is required.
Canvas
based onCanvas
Drawing what we need is quite a Renaissance feeling, and this way of implementation is completely unreliable.DOM
, thus allowing full control of the typography engine. Then Renaissance refers to, based onDOM
Any ecology that is compatible with implementation will be invalid, such as barrier-free,SEO
, development tool support, etc.
So why abandon the existing onesDOM
Ecology, insteadCanvas
To draw rich text content. In particular, rich texts can be very complex content, because in addition to text, there are also contents of pictures and many structured text formats, such as tables. All of these contents need to be implemented by themselves, thenCanvas
Implementing these contents is actually equivalent to re-implementing some of themskia
。
based onCanvas
The editors for drawing currently include Tencent Documents,Google Doc
etc., and open source editor implementations haveCanvas Editor. In addition to the document editor, the implementation of online tables is basicallyCanvas
Implementation, such as Tencent DocumentationSheet
, Feishu multidimensional tables, etc., open source implementations includeLuckySheet。
existGoogle Doc
PostedBlog
In, for useCanvas
There are two main reasons for drawing documents:
- Document consistency: The consistency here refers to the browser's compatibility for similar behaviors. For example:
Chrome
Double-click the content of a certain text, and the selection will automatically select the entire word, and in the early stageFireFox
In the middle, a sentence will be automatically selected. Inconsistency in behaviors like this can lead to inconsistency in user experience, and useCanvas
Drawing documents can ensure this consistency by yourself. - Efficient drawing performance: by
Canvas
Drawing documents allows you to better control the drawing timing without waiting for repainting and reflow, and you don't need to considerDOM
Complex compatibility considerations are used to cause performance losses. also,Canvas
Drawing replaces heavyDOM
Operation, frame-by-frame rendering and hardware acceleration can improve rendering performance, thereby improving the response speed of user operations and overall user experience.
In addition, the layout engine can also control the layout effect of documents and make various needs of rich text. We may face why the product cannot support it likeoffice word
That effect. For example, if the text we write is just a row, if another period is added here, the preceding words will be squeezed, so that this period does not require a line break. And if we type another word, the word will be line-breaking. This state will not appear in browser typesetting, so if you need to break through the browser typesetting restrictions, you need to implement your own typesetting capabilities.
<!-- word -->
Text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text
<!-- Browser -->
Text text text text text text text text text text text text text text text text text text text text text text text text
.
That is, inword
Usually there is no period at the beginning of the paragraph, but this will happen in the browser, especially in pureASCII
In the case of characters. If you want to avoid this difference in layout status, you must implement the layout engine yourself to control the layout effect of the document.
In addition, there are other features such as controlledRTL
Layout, paging, page number, header, footnote, font and fence control, etc. Especially the ability to paging, this effect is necessary in some cases when printing is required, butDOM
The height of the implementation cannot be known before drawing, so the effect of pagination cannot be achieved well. In addition, the pagination rendering effect of large tables, etc., have become difficult to control.
Therefore, these are aligned if desiredword
To achieve it, it must be usedCanvas
Make it from scratch. In addition to these additional features, the original browser is based onDOM
Basic functions implemented, such as support for input method, support for copy and paste, support for drag and drop, etc. And basicCanvas
These functions cannot be supported, especially input methodsIME
Support and implementation of text selections require very complex interactive implementation, so such costs will naturally not be easily accepted.
Summarize
In this article, we talked a lot about the implementation of basic capabilities of rich text editors, especially inDOM
Design between structural representation and data structure. And we also talked about the browser interaction solutionExecCommand
、ContentEditable
、Canvas
The characteristics of the implementation plan briefly summarizes current mature products and open source editors, and describes the advantages and disadvantages of related implementations.
Later we willContentEditable
To implement a basic rich text editor, first of all, the overall architecture design and data structure operations are overviewed. Then start to implement specific modules, such as input modules, clipboard modules, selection modules, etc. Implementing an editor is never an easy task. In addition to the basic framework design at the core level, there will also be many problems at the application level that need to be compatible with each other, so this will be a big project that needs to be accumulated slowly.
One question every day
- /WindRunnerMax/EveryDay
refer to
- /w3c/editing
- /p/226002936
- /p/407713779
- /p/425265438
- /p/259387658
- /p/145605161
- /question/38699645
- /question/404836496
- /post/6974609015602937870
- /yoyoyohamapi/book-slate-editor-design
- /grassator/canvas-text-editor-tutorial
- /question/459251463/answer/1890325108
- /translate/why-contenteditable-is-terrible
- /2010/05/
- /data structures/algorithms/benchmarking/text editors/c++/editor-data-structures/