Location>code7788 >text

Feeling like nothing, I'm ready to try to write a rich text editor from scratch

Popularity:986 ℃/2025-04-09 10:49:46

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, combinedReactView layer'sDraft, pure editor engineSlate, highly modularProseMirror, out of the boxTinyMCE/TipTap, integrated collaborative solutionsEtherPadetc.

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 CodeA kind of implementation. In essence, both low code and rich text are based onDSLDescription to operateDOMStructure, 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 from20I 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 in24I 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 agoslateRich text editor-related implementations and alsoslateSome warehouses have been mentionedPR. I also wrote someslateRelated articles, and also based onslateImplementing 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 asDOMCommon zero-width characters after structure,MentionThe 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.

24I have written a lot of business stuff since 2019.25I 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 onimmerCooperateOT-JSONThe 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 compatibleChromeJust 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 exampledocumentOncaretPositionFromPointMethod 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 exampleSelectionRangeWait, 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&ZeroWidthSpace;Right nowU+200BSimilar 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">&ZeroWidthSpace;</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 toContentEditableThis 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 sameslatestillquillNone 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 iswordMore 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>&#8203;</span></div>
   <div><span>Zero-width characters at the end Line 2</span><span>&#8203;</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 have3Line text, if at this time from1Select the line at the end2When running, pressTabkey, 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.2OK, but actually selected1/2Two 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 oncontenteditableIn the implemented editor, Feishu Documentation, earlyEtherPadThere is this interactive implementation; in the editor of the self-drawing selection, this implementation exists for DingTalk Document;CanvasIn the editor implemented by the engine, Tencent Documents,Google DocThis 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 isDOMThe 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>&#8203;</span></div>

Similar toNotionThere 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>&#8203;</span>
</div>

Structurally, zero-width characters have a very important implementation. In the editorcontenteditable=falseThere will be special manifestations in nodes similar toinline-blockIn nodes, for exampleMentionIn 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-1It is impossible to place the cursor on@xxxAfter the content, although we can place the cursor before, the cursor position is atline nodeOn, it does not meet our expected text nodes. Then we must add zero-width characters after that,line-2/3We can see the correct cursor placement effect in this. Here0.1pxIt's also for the purpose of compatible cursor placementmagic, there is no suchhackIn other words, the cursor of the node of the same level cannot be placed in theinline-blockAfter 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>&#8203;</span>
    <span data-leaf><span contenteditable="false" style="margin: 0 0.1px;">@xxx</span></span>
    <span data-leaf>&#8203;</span>
  </div>
  <div data-line-node="3">
    <span data-leaf>&#8203;<span contenteditable="false">@xxx</span>&#8203;</span>
  </div>
</div>

In addition, the editor naturally needs to deal with characters, sojsShownUnicodeCoding is being implemented,emojiIt is the most common and prone to problems. Except for its single length2In addition to this situation, the combinationemojiAlso uses unique zero-width hyphen\u200dTo 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.diffAlgorithms, 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 onJSONNested data structures to express the editorModelIt is very common, for exampleSlateProseMirrorLexicaletc. byslateAs an example, whether it is data structure or selection design, it is as inclined as possible toHTMLTherefore, 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 exampleQuillEtherPadGoogle Docetc. byquillAs an example, the data structure expression on its content will not be nested, of course, essentiallyJSONstructure, 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/monacoofpiece tableData structure. Isn't a code editor a rich text editor? After all, it can support code highlighting functions, but it's similar topiece tableI 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. byslateFor example, in0.50Previous versionAPIThe 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 thatslateIt is necessary to complete the operation of the document content9Types 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 similarMoveThe operation requires additional selectionsRangeComputation 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 structurenormalizeIt 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 followsOpdeal with. There are also user operations that cannot be controlled very well, sonormalizeStandardize data during the process, otherwise, the following is for example pastingHTMLThere 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 examplequoteandlistNested in two formats. If the data structure of our document is a nested structure at this time, the operation content will exist.ul > quoteorquote > ulIn two situations, under normal circumstances, we must design rules to donormalize; and under the flat structure, all attributes are written inattrsWithin, 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 DocThe 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, andJSONNested 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 onOTThe collaborative algorithm orOp-Based CRDTThe collaborative algorithms all need to transmit the aboveopType and data, then it is obvious9Such operationopTypes will be better3Such operationopThe 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, soquillofdeltaIt's a very good choice. butquillIt is a self-implemented view layer structure, not a combinationreactIn 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 onquillThe data structure to implement the core layer of rich text editors from zero, and likeslateThis way, the basic view layer is combined.

Program selection

Actually, there is an interesting question here, why can't you use it?1mbThe amount of code can be implemented in some casesoffice wordThe ability of editors is because the browser has done a lot of things for us and throughAPIProvided 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-providedContentEditableImplement 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 providedContentEditableImplement 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 onCanvasImplement 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 ofDOMIt 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.L1Type implementation. And this is differentiated and does not depend on itContentEditableBut it is not a completely self-drawing engine, but a dependencyDOMThe implementation of presenting content plus self-drawing selection can actually be considered asL1.5level.

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 standardsMVCThe 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 useexecCommandTo achieve it. Even directly based onexecCommandThe advantage is that it will be very small, for examplepellThe implementation only requires3.54KBcode volume, in additionreact-contenteditableSo far, it is realized.

We can also achieve the minimum that can be boldedDEMOexecCommandThe command can becontenteditableThe 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 displayedfalseMozillaNot 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 alignexecCommandThe 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,execCommandThe 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 aircontenteditableIn the case of the editor, press Enter directly,ChromeThe 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>IEThe 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,existChromeThe content in123<div>123</div>, and inFireFoxThe 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 example123|123->123123,existChromeThe content in the performance will return to the original123123, and inFireFoxThe 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, inChromeThe performance in this is that two lines of content will be wrapped in the same<p>In, and inFireFoxThe 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 asIEUsed in the browser<strong></strong>To achieve boldness,ChromeIt is used<b></b>To achieve boldness,IEandSafariNot supported to passheadingCommands 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 pureHTMLeditor. After all, if it is based onMVCThe editor implementation of the mode will be processedModelInvalid data content, which leads to the originalHTMLContent 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 aboutXSSThe process has been handled.

ContentEditable

ContentEditableyesHTML5An attribute in which the element can be editable and combined with the built-inexecCommandIt'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 modificationHTMLAlthough the solution is simple, we have also talked about its poor controllability. In addition to the aboveexecCommandIn addition to the compatibility issues of command execution, there are many moreDOMThe 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.DOMStructural 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 toModelWhen you go, it will naturally be a troublesome thing. In addition, the expression of the constituent area is also a complex problem.DOMStructure as an example:

<span>123</span><b><em>456</em></b><span>789</span>

If we want to express the selection fold in4When 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.L1The rich text editor uses the concept of custom data models. That's rightDOMThe data structure extracted from the tree, the same data structure can ensure renderingHTMLThe same is true. The data model is directly controlled with custom commands to ultimately ensure rendering.HTMLDocument consistency. For the expression of the selection, it is necessary toDOMContinuous constituenciesnormalizeSelectionModel

Actually, this is what we often seeMVCModel, 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 customexecCommandModify 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 toslateThe implementation is connected through the adapterReactAfter that, more complex compatibility processing is needed. existReactJoin in the nodeContentEditableAfter 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.

thiswarningIt means,ReactCan't guaranteeContentEditableIn-housechildrenIt will not be modified or copied unexpectedly, which may not be expected. That is to say, exceptReactIt will need to be executedDOMOperation, usedContentEditableAfter 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 from123Character selected456superior. That is, there is a leap hereContentEditableOnce 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.blocksThe 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 enabledContentEditableIf 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 especiallyIMEInput method, the routine given by the browserAPIJust 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 oninputWhen 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, useContentEditableMuch needs to be processedDOM, but obviously we don't need to deal with the awake input too much. If not usedContentEditable, but useDOMTo present rich text content, additional hiding must be usedinputNodes implement input. In this case, the browser's selection behavior cannot be used, so the implementation of self-drawing selection is required.

Canvas

based onCanvasDrawing 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 onDOMAny ecology that is compatible with implementation will be invalid, such as barrier-free,SEO, development tool support, etc.

So why abandon the existing onesDOMEcology, insteadCanvasTo 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, thenCanvasImplementing these contents is actually equivalent to re-implementing some of themskia

based onCanvasThe editors for drawing currently include Tencent Documents,Google Docetc., and open source editor implementations haveCanvas Editor. In addition to the document editor, the implementation of online tables is basicallyCanvasImplementation, such as Tencent DocumentationSheet, Feishu multidimensional tables, etc., open source implementations includeLuckySheet

existGoogle DocPostedBlogIn, for useCanvasThere are two main reasons for drawing documents:

  • Document consistency: The consistency here refers to the browser's compatibility for similar behaviors. For example:ChromeDouble-click the content of a certain text, and the selection will automatically select the entire word, and in the early stageFireFoxIn the middle, a sentence will be automatically selected. Inconsistency in behaviors like this can lead to inconsistency in user experience, and useCanvasDrawing documents can ensure this consistency by yourself.
  • Efficient drawing performance: byCanvasDrawing documents allows you to better control the drawing timing without waiting for repainting and reflow, and you don't need to considerDOMComplex compatibility considerations are used to cause performance losses. also,CanvasDrawing replaces heavyDOMOperation, 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 wordThat 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, inwordUsually there is no period at the beginning of the paragraph, but this will happen in the browser, especially in pureASCIIIn 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 controlledRTLLayout, 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, butDOMThe 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 desiredwordTo achieve it, it must be usedCanvasMake it from scratch. In addition to these additional features, the original browser is based onDOMBasic functions implemented, such as support for input method, support for copy and paste, support for drag and drop, etc. And basicCanvasThese functions cannot be supported, especially input methodsIMESupport 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 inDOMDesign between structural representation and data structure. And we also talked about the browser interaction solutionExecCommandContentEditableCanvasThe 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 willContentEditableTo 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/