1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        手把手帶你10分鐘手擼一個簡易的Markdown編輯器

        共 38228字,需瀏覽 77分鐘

         ·

        2021-06-11 04:00


        點擊上方“前端Sharing”,選擇“設為星標
        第一時間關注技術干貨!


        前言

        最近我在項目中需要實現(xiàn)一個 「markdown編輯器」 的需求,并且是以React框架為開發(fā)基礎的,類似掘金這樣的:

        img

        我的第一想法肯定是能用優(yōu)秀的開源就一定用開源的,畢竟不能老是重復造輪子。于是我在我的前端群里問了很多群友,他們都給了甩過來一堆開源的markdown編輯器項目,但我一看全是基于Vue使用的,不符合我的預期,逛了一下github,也沒看到我滿意的項目,所以就想自己實現(xiàn)一個啦

        需要實現(xiàn)的功能

        我們自己實現(xiàn)的話,看看需要支持哪些功能,因為做一個初版的簡易編輯器,所以功能實現(xiàn)得不會太多,但絕對夠用:

        • markdown語法解析,并實時渲染
        • markdown主題css樣式
        • 代碼塊高亮展示
        • 「編輯區(qū)」和「展示區(qū)」的頁面同步滾動
        • 編輯器工具欄中工具的實現(xiàn)

        這里先放上我最終實現(xiàn)好了的效果圖:

        最終效果圖

        我也將本文的代碼放在了 Github 倉庫 (opens new window)[1]上了,歡迎各位點個 ?? 「star」 支持一下

        同時,我也給大家提供了一個在線體驗的地址 (opens new window)[2],因為做的比較倉促,歡迎大家給我提意見和pr

        具體實現(xiàn)

        具體的實現(xiàn)也是按照我們上述列出來的功能的順序來一一實現(xiàn)的

        說明:本文通過循序漸進的方式講解,所以重復代碼可能有點多。并且每一部分的注釋是專門用于講解該部分的代碼的,所以在看每一部分功能代碼時,只需要看注釋部分就好~

        一、布局

        import React, {  } from 'react'


        export default function MarkdownEdit({


            return (
                <div className="markdownEditConainer">
                    <textarea className="edit" />
                    <div className="show" />
                </div>

            )
        }

        css樣式我就不一一列舉了,整體就是左邊是「編輯區(qū)」,右邊是「展示區(qū)」,具體樣式如下:

        布局圖

        二、markdown語法解析

        接下來就需要思考如何將 「「編輯區(qū)」」 輸入的markdown語法解析成html標簽并最終渲染在 「「展示區(qū)」」

        查找了一下目前比較優(yōu)秀的markdown解析的開源庫,常用的有三個,分別是Marked、Showdownmarkdown-it ,并借鑒了一下其它大佬的想法,了解了一下這三個庫的優(yōu)缺點,對比如下:

        庫名優(yōu)點缺點
        Marked性能好,正則解析(中文支持比較好)擴展性較差
        Showdown擴展性好、正則解析(中文支持好)性能較差
        markdown-it擴展性好、性能較好逐字符解析(中文支持不好)

        剛開始我選擇了showdown這個庫,因為這個庫使用起來特別方便,而且官方已經(jīng)在庫中提供了很多擴展功能,只需要配置一些字段即可。但是后來我又分析了一波,還是選用了markdown-it,因為之后可能需要做更多的語法擴展,showdown的官方文檔寫的比較生硬,而且markdown-it使用的人也多,生態(tài)比較好,雖然其官方?jīng)]有支持很多擴展的語法,但是已經(jīng)有很多基于makrdown-it的功能擴展插件了,最重要的是markdown-it的官方文檔寫得好啊(而且有中文文檔)!

        接下來寫一下markdown語法解析的代碼吧(其中步驟1、2、3表示的是markdown-it庫的用法)

        import React, { useState } from 'react'
        // 1. 引入markdown-it庫
        import markdownIt from 'markdown-it'

        // 2. 生成實例對象
        const md = new markdownIt()

        export default function MarkdownEdit({
            const [htmlString, setHtmlString] = useState('')  // 存儲解析后的html字符串

            // 3. 解析markdown語法
            const parse = (text: string) => setHtmlString(md.render(text));

            return (
                <div className="markdownEditConainer">
                    <textarea 
                        className="edit" 
                        onChange={(e) =>
         parse(e.target.value)} // 編輯區(qū)內(nèi)容每次修改就更新變量htmlString的值
                    />
                    <div 
                        className="show" 
                        dangerouslySetInnerHTML={{ __html: htmlString }} // 將html字符串解析成真正的html標簽
                    />

                </div>

            )
        }

        對于將 「html字符串」 轉化為 「真正的html標簽」 的操作,我們借助了React提供的dangerouslySetInnerHTML屬性,詳細的使用可以看React 官方文檔(opens new window)[3]

        此時一個簡單的markdown語法解析功能就實現(xiàn)了,來看看效果

        markdown語法解析效果展示圖

        兩邊確實正在同步更新,但是.....看起來好像哪里不太對!其實是沒問題的,被解析好的 html字符串 每個標簽都被附帶上了特定的類名,只是現(xiàn)在我們引入任何的樣式文件,例如下圖

        img

        我們可以打印解析出來的html字符串看看是什么樣的

        <h1 id="">大標題</h1>
        <blockquote>
          <p>本文來自公眾號:前端印象</p>
        </blockquote>
        <pre><code class="js language-js">let name = '零一'
        </code></pre>

        三、markdown主題樣式

        接下來我們可以去網(wǎng)上找一些markdown的主題樣式css文件,例如我用一個最簡單Github主題的markdown樣式。另外我還是很推薦Typora Theme (opens new window)[4],上面有很多很多的markdown主題

        因為我這個樣式主題是有一個前綴id write(Typora上的大部分主題前綴也是#write),所以我們給展示區(qū)的標簽加上該類id,并引入樣式文件

        import React, { useState } from 'react'
        import './theme/github-theme.css'  // 引入github的markdown主題樣式
        import markdownIt from 'markdown-it'

        const md = new markdownIt()

        export default function MarkdownEdit({
            const [htmlString, setHtmlString] = useState('')

            const parse = (text: string) => setHtmlString(md.render(text));

            return (
                <div className="markdownEditConainer">
                    <textarea 
                        className="edit" 
                        onChange={(e) =>
         parse(e.target.value)} 
                    />
                    <div 
                        className="show"
                        id="write"  // 新增writeID名 
                        dangerouslySetInnerHTML={{ __html: htmlString }}
                    />

                </div>

            )
        }

        再來看看加入樣式后的渲染結果圖

        帶樣式的markdown渲染效果圖

        四、代碼塊高亮

        markdown語法的解析已經(jīng)完成了,并且也有對應的樣式了,但是代碼塊好像還沒有高亮樣式

        這塊兒我們自己來從0到1的實現(xiàn)是不可能的,可以用現(xiàn)成的開源庫 highlight.js,highlight.js 官方文檔 (opens new window)[5],這個庫能幫你做的就是檢測「代碼塊標簽元素」,并為其加上特定的類名。這里放上這個庫的API文檔(opens new window)[6]

        highlight.js 默認是檢測它所支持的所有語言的語法的,我們就不需要關心了,并且其提供了很多的代碼高亮主題,我們可以在官網(wǎng)進行預覽,如下圖所示:

        img

        更大的好消息來了!markdown-it已經(jīng)將highlight.js集成進去了,直接設定一些配置即可,并且我們需要先將該庫下載下來。具體的可以看markdown-it中文官網(wǎng) - 高亮語法配置(opens new window)[7]

        同時在目錄highlight.js/styles/下有很多很多的主題,可以自行導入

        接下來就來實現(xiàn)一下代碼高亮的功能吧

        import React, { useState, useEffect } from 'react'
        import markdownIt from 'markdown-it'
        import './theme/github-theme.css'
        import hljs from 'highlight.js'  // 引入highlight.js庫
        import 'highlight.js/styles/github.css'  // 引入github風格的代碼高亮樣式

        const md = new markdownIt({
            // 設置代碼高亮的配置
            highlightfunction (code, language{      
                if (language && hljs.getLanguage(language)) {
                  try {
                    return `<pre><code class="hljs language-${language}">` +
                           hljs.highlight(code, { language  }).value +
                           '</code></pre>';
                  } catch (__) {}
                }
            
                return '<pre class="hljs"><code>' + md.utils.escapeHtml(code) + '</code></pre>';
            }
        })

        export default function MarkdownEdit({
            const [htmlString, setHtmlString] = useState('')

            const parse = (text: string) => setHtmlString(md.render(text));

            return (
                <div className="markdownEditConainer">
                    <textarea 
                        className="edit" 
                        onChange={(e) =>
         parse(e.target.value)} 
                    />
                    <div 
                        className="show"
                        id="write"
                        dangerouslySetInnerHTML={{ __html: htmlString }}
                    />

                </div>

            )
        }

        來看一下代碼高亮的效果圖:

        代碼高亮效果圖

        五、同步滾動

        markdown編輯器還有一個重要的功能就是在我們滾動一個區(qū)域的內(nèi)容時,另一塊區(qū)域也跟著同步的滾動,這樣才方便查看

        接下來我們來實現(xiàn)一下,我會將我實現(xiàn)時踩的坑也一并列出來,讓大家也印象深刻點,免得以后也犯同樣的錯誤

        剛開始主要實現(xiàn)思路就是當滾動其中一塊區(qū)域時,計算滾動比例(scrollTop / scrollHeight),然后使另一塊區(qū)域當前的滾動距離占總滾動高度的比例等于該滾動比例

        import React, { useState, useRef, useEffect } from 'react'
        import markdownIt from 'markdown-it'
        import './theme/github-theme.css'  
        import hljs from 'highlight.js'
        import 'highlight.js/styles/github.css' 

        const md = new markdownIt({
            highlightfunction (code, language{      
                if (language && hljs.getLanguage(language)) {
                  try {
                    return `<pre><code class="hljs language-${language}">` +
                           hljs.highlight(code, { language  }).value +
                           '</code></pre>';
                  } catch (__) {}
                }
            
                return '<pre class="hljs"><code>' + md.utils.escapeHtml(code) + '</code></pre>';
            }
        })

        export default function MarkdownEdit({
            const [htmlString, setHtmlString] = useState('')
            const edit = useRef(null)  // 編輯區(qū)元素
            const show = useRef(null)  // 展示區(qū)元素

            const parse = (text: string) => setHtmlString(md.render(text));

            // 處理區(qū)域的滾動事件
            const handleScroll = (block: number, event) => {
                let { scrollHeight, scrollTop } = event.target
                let scale = scrollTop / scrollHeight  // 滾動比例

                // 當前滾動的是編輯區(qū)
                if(block === 1) {
                    // 改變展示區(qū)的滾動距離
                    let { scrollHeight } = show.current
                    show.current.scrollTop = scrollHeight * scale
                } else if(block === 2) {  // 當前滾動的是展示區(qū)
                    // 改變編輯區(qū)的滾動距離
                    let { scrollHeight } = edit.current
                    edit.current.scrollTop = scrollHeight * scale
                }
            }

            return (
                <div className="markdownEditConainer">
                    <textarea 
                        className="edit" 
                        ref={edit}
                        onScroll={(e) =>
         handleScroll(1, e)}
                        onChange={(e) => parse(e.target.value)} 
                    />
                    <div 
                        className="show"
                        id="write"
                        ref={show}
                        onScroll={(e) =>
         handleScroll(2, e)}
                        dangerouslySetInnerHTML={{ __html: htmlString }}
                    />
                </div>

            )
        }

        這是我做的時候的第一版,確實是實現(xiàn)了兩塊區(qū)域的同步滾動,但是存在兩個bug,來看看是哪兩個

        「bug1:」

        這是一個很致命的bug,先埋個伏筆,先來看效果:

        初版同步滾動效果圖

        同步滾動的效果實現(xiàn)了,但能很明顯得看到,當我手動滾動完以后停止了任何操作,但是兩個區(qū)域仍然在不停的滾動,這是為什么呢?

        排查了一下代碼,發(fā)現(xiàn) handleScroll 這個方法會無限觸發(fā),假設當我們手動滾動一次編輯區(qū)后會觸發(fā)其 scroll方法,即會調(diào)用 handleScroll 方法,然后會去改變「展示區(qū)」的滾動距離,此時又會觸發(fā)展示區(qū)的 scroll方法,即調(diào)用 handleScroll 方法,然后會去改變「編輯區(qū)」的滾動距離 .... 就這樣一直循環(huán)往復,才會出現(xiàn)圖中的bug

        后來我想了個比較簡單的解決辦法,就是用一個變量記住你當前手動觸發(fā)的是哪個區(qū)域的滾動,這樣就可以在 handleScroll 方法里區(qū)分此次滾動是被動觸發(fā)的還是主動觸發(fā)的了

        import React, { useState, useRef, useEffect } from 'react'
        import markdownIt from 'markdown-it'
        import './theme/github-theme.css'  
        import hljs from 'highlight.js'
        import 'highlight.js/styles/github.css'

        const md = new markdownIt({
            highlightfunction (code, language{      
                if (language && hljs.getLanguage(language)) {
                  try {
                    return `<pre><code class="hljs language-${language}">` +
                           hljs.highlight(code, { language  }).value +
                           '</code></pre>';
                  } catch (__) {}
                }
            
                return '<pre class="hljs"><code>' + md.utils.escapeHtml(code) + '</code></pre>';
            }
        })

        let scrolling: 0 | 1 | 2 = 0  // 0: none; 1: 編輯區(qū)主動觸發(fā)滾動; 2: 展示區(qū)主動觸發(fā)滾動
        let scrollTimer;  // 結束滾動的定時器

        export default function MarkdownEdit({
            const [htmlString, setHtmlString] = useState('')
            const edit = useRef(null
            const show = useRef(null)  

            const parse = (text: string) => setHtmlString(md.render(text));

            const handleScroll = (block: number, event) => {
                let { scrollHeight, scrollTop } = event.target
                let scale = scrollTop / scrollHeight  

                if(block === 1) {
                    if(scrolling === 0) scrolling = 1;  // 記錄主動觸發(fā)滾動的區(qū)域
                    if(scrolling === 2return;    // 當前是「展示區(qū)」主動觸發(fā)的滾動,因此不需要再驅(qū)動展示區(qū)去滾動

                    driveScroll(scale, showRef.current)  // 驅(qū)動「展示區(qū)」的滾動
                } else if(block === 2) {  
                    if(scrolling === 0) scrolling = 2;
                    if(scrolling === 1return;    // 當前是「編輯區(qū)」主動觸發(fā)的滾動,因此不需要再驅(qū)動編輯區(qū)去滾動

                    driveScroll(scale, editRef.current)
                }
            }

            // 驅(qū)動一個元素進行滾動
            const driveScroll = (scale: number, el: HTMLElement) => {
                let { scrollHeight } = el
                el.scrollTop = scrollHeight * scale

                if(scrollTimer) clearTimeout(scrollTimer);
                scrollTimer = setTimeout(() => {
                    scrolling = 0    // 在滾動結束后,將scrolling設為0,表示滾動結束
                    clearTimeout(scrollTimer)
                }, 200)
            }

            return (
                <div className="markdownEditConainer">
                    <textarea 
                        className="edit" 
                        ref={edit}
                        onScroll={(e) =>
         handleScroll(1, e)}
                        onChange={(e) => parse(e.target.value)} 
                    />
                    <div 
                        className="show"
                        id="write"
                        ref={show}
                        onScroll={(e) =>
         handleScroll(2, e)}
                        dangerouslySetInnerHTML={{ __html: htmlString }}
                    />
                </div>

            )
        }

        這樣就解決了上述的bug了,同步滾動也算很不錯得實現(xiàn)了,現(xiàn)在的效果就跟文章開頭展示的圖片里效果一樣了

        「bug2:」

        這里還存在一個很小的問題,也不算是bug,應該算是設計上的思路問題,那就是兩個區(qū)域其實還沒完完全全實現(xiàn)同步滾動。先來看看原先的設計思想

        img

        編輯區(qū)和展示區(qū)的可視高度是一樣的,但一般編輯區(qū)的內(nèi)容經(jīng)過markdown渲染后,總的滾動高度是會高于編輯區(qū)總的滾動高度的,所以我們無法僅憑scrollTopscrollHeight使得兩個區(qū)域同步滾動,比較晦澀,用具體的數(shù)據(jù)來看一下

        屬性編輯區(qū)展示區(qū)
        clientHeight300300
        scrollHeight500600

        假設我們現(xiàn)在滾動編輯區(qū)到最底部,那么此時「編輯區(qū)」的 scrollTop 應為 scrollHeight - clientHeight = 500 - 300 = 200,按照我們原本計算滾動比例的方式得出 scale = scrollTop / scrollHeight = 200 / 500 = 0.4,那么「展示區(qū)」同步滾動后,scrollTop = scale * scrollHeight = 0.4 * 600 = 240 < 600 - 300 = 300。但事實就是編輯區(qū)滾動到最底部了,而展示區(qū)還沒有,顯然不是我們要的效果

        換一種思路,我們在計算滾動比例時,應計算的是當前的 scrollTopscrollTop最大值的比例,這樣就能實現(xiàn)同步滾動了,仍然用剛才那個例子來看:此時編輯區(qū)滾動到最底部,那么scale應為 scrollTop / (scrollHeight - clientHeight) = 200 / (500 - 300) = 100%,表示編輯區(qū)滾動到最底部了,那么在展示區(qū)同步滾動時,他的 scrollTop 就變成了 scale * (scrollHeight - clientHeight) = 100% * (600 - 300) = 300,此時的展示區(qū)也同步滾動到了最底部,這樣就實現(xiàn)了真正的同步滾動了

        來看一下改進后的代碼

        import React, { useState, useRef, useEffect } from 'react'
        import markdownIt from 'markdown-it'
        import './theme/github-theme.css'  
        import hljs from 'highlight.js'
        import 'highlight.js/styles/github.css'

        const md = new markdownIt({
            highlightfunction (code, language{      
                if (language && hljs.getLanguage(language)) {
                  try {
                    return `<pre><code class="hljs language-${language}">` +
                           hljs.highlight(code, { language  }).value +
                           '</code></pre>';
                  } catch (__) {}
                }
            
                return '<pre class="hljs"><code>' + md.utils.escapeHtml(code) + '</code></pre>';
            }
        })

        let scrolling: 0 | 1 | 2 = 0  
        let scrollTimer;  

        export default function MarkdownEdit({
            const [htmlString, setHtmlString] = useState('')
            const edit = useRef(null
            const show = useRef(null)  

            const parse = (text: string) => setHtmlString(md.render(text));

            const handleScroll = (block: number, event) => {
                let { scrollHeight, scrollTop, clientHeight } = event.target
                let scale = scrollTop / (scrollHeight - clientHeight)  // 改進后的計算滾動比例的方法

                if(block === 1) {
                    if(scrolling === 0) scrolling = 1;  
                    if(scrolling === 2return;    

                    driveScroll(scale, showRef.current)  
                } else if(block === 2) {  
                    if(scrolling === 0) scrolling = 2;
                    if(scrolling === 1return;    

                    driveScroll(scale, editRef.current)
                }
            }

            // 驅(qū)動一個元素進行滾動
            const driveScroll = (scale: number, el: HTMLElement) => {
                let { scrollHeight, clientHeight } = el
                el.scrollTop = (scrollHeight - clientHeight) * scale  // scrollTop的同比例滾動

                if(scrollTimer) clearTimeout(scrollTimer);
                scrollTimer = setTimeout(() => {
                    scrolling = 0   
                    clearTimeout(scrollTimer)
                }, 200)
            }

            return (
                <div className="markdownEditConainer">
                    <textarea 
                        className="edit" 
                        ref={edit}
                        onScroll={(e) =>
         handleScroll(1, e)}
                        onChange={(e) => parse(e.target.value)} 
                    />
                    <div 
                        className="show"
                        id="write"
                        ref={show}
                        onScroll={(e) =>
         handleScroll(2, e)}
                        dangerouslySetInnerHTML={{ __html: htmlString }}
                    />
                </div>

            )
        }

        兩個bug都已經(jīng)解決了,同步滾動的功能也算完美實現(xiàn)啦。但對于同步滾動這個功能,其實有兩種概念,一種是兩個區(qū)域在滾動高度上保持同步滾動;另一種就是右側的展示區(qū)域?qū)髠鹊木庉媴^(qū)的內(nèi)容進行滾動。我們現(xiàn)在實現(xiàn)的是前者,后者可以后續(xù)作為新功能實現(xiàn)一下~

        六、工具欄

        最后我們就再實現(xiàn)一下編輯器的工具欄部分的工具(加粗、斜體、有序列表等等),因為這幾個工具的實現(xiàn)思路都一致,我們就拿 「「加粗」」 這個工具舉例子,其余的就可以模仿著寫出來了

        加粗工具的實現(xiàn)思路:

        • 光標是否選中文字?
          • 是。將選中文字的兩側加上**
          • 否。在光標所在處添加文字**加粗文字**

        動圖效果演示:

        加粗工具動圖演示
        import React, { useState, useRef, useEffect } from 'react'
        import markdownIt from 'markdown-it'
        import './theme/github-theme.css'  
        import hljs from 'highlight.js'
        import 'highlight.js/styles/github.css'

        const md = new markdownIt({
            highlightfunction (code, language{      
                if (language && hljs.getLanguage(language)) {
                  try {
                    return `<pre><code class="hljs language-${language}">` +
                           hljs.highlight(code, { language  }).value +
                           '</code></pre>';
                  } catch (__) {}
                }
            
                return '<pre class="hljs"><code>' + md.utils.escapeHtml(code) + '</code></pre>';
            }
        })

        let scrolling: 0 | 1 | 2 = 0  
        let scrollTimer;  

        export default function MarkdownEdit({
            const [htmlString, setHtmlString] = useState('')
            const [value, setValue] = useState('')   // 編輯區(qū)的文字內(nèi)容
            const edit = useRef(null
            const show = useRef(null)  

            const handleScroll = (block: number, event) => {
                let { scrollHeight, scrollTop, clientHeight } = event.target
                let scale = scrollTop / (scrollHeight - clientHeight)  

                if(block === 1) {
                    if(scrolling === 0) scrolling = 1;  
                    if(scrolling === 2return;    

                    driveScroll(scale, showRef.current)  
                } else if(block === 2) {  
                    if(scrolling === 0) scrolling = 2;
                    if(scrolling === 1return;    

                    driveScroll(scale, editRef.current)
                }
            }

            // 驅(qū)動一個元素進行滾動
            const driveScroll = (scale: number, el: HTMLElement) => {
                let { scrollHeight, clientHeight } = el
                el.scrollTop = (scrollHeight - clientHeight) * scale  

                if(scrollTimer) clearTimeout(scrollTimer);
                scrollTimer = setTimeout(() => {
                    scrolling = 0   
                    clearTimeout(scrollTimer)
                }, 200)
            }

            // 加粗工具
            const addBlod = () => {
                // 獲取編輯區(qū)光標的位置。未選中文字時:selectionStart === selectionEnd ;選中文字時:selectionStart < selectionEnd
                let { selectionStart, selectionEnd } = edit.current
                let newValue = selectionStart === selectionEnd
                                ? value.slice(0, start) + '**加粗文字**' + value.slice(end)
                                : value.slice(0, start) + '**' + value.slice(start, end) + '**' + value.slice(end)
                setValue(newValue)
            }

            useEffect(() => {
                // 編輯區(qū)內(nèi)容改變,更新value的值,并同步渲染
                setHtmlString(md.render(value))
            }, [value])

            return (
                <div className="markdownEditConainer">
                    <button onClick={addBlod}>加粗</button>   {/* 假設一個加粗的按鈕 */}
                    <textarea 
                        className="edit" 
                        ref={edit}
                        onScroll={(e) =>
         handleScroll(1, e)}
                        onChange={(e) => setValue(e.target.value)}   // 直接修改value的值,useEffect會同步渲染展示區(qū)的內(nèi)容
                        value={value}
                    />
                    <div 
                        className="show"
                        id="write"
                        ref={show}
                        onScroll={(e) =>
         handleScroll(2, e)}
                        dangerouslySetInnerHTML={{ __html: htmlString }}
                    />
                </div>

            )
        }

        借助這樣的思路,就可以完成其它各種工具的實現(xiàn)了。

        在我已經(jīng)發(fā)布的markdown-editor-reactjs (opens new window)[8]中,已經(jīng)完成了其它工具的實現(xiàn),想要看代碼的可以去源碼里看

        七、補充

        為了保證包的體積足夠小,我將「第三方依賴庫」、「markdown主題」「代碼高亮主題」都通過外鏈的形式導入了

        八、最后

        一個簡易版的markdown編輯器就實現(xiàn)了,大家可以手動嘗試實現(xiàn)一下。后續(xù)我也會繼續(xù)發(fā)一些教程,對這個編輯器的功能進行擴展

        我將代碼都上傳到了 Github倉庫 (opens new window)[9](希望大家點個?? 「star」),后續(xù)擴展一下功能,并作為一個完整的組件發(fā)布到npm給大家使用,希望大家多多支持~(其實我已經(jīng)悄悄發(fā)布,但因功能還不是太完善,就不先拿出來給大家使用了,這里簡單放個npm包的地址 (opens new window)[10]

        參考資料

        [1]

        Github 倉庫 (opens new window): https://github.com/zero2one3/markdown-editor-reactjs

        [2]

        在線體驗的地址 (opens new window): http://lpyexplore.gitee.io/taobao_staticweb/markdown-editor-reactjs/

        [3]

        React 官方文檔(opens new window): https://zh-hans.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml

        [4]

        Typora Theme (opens new window): https://theme.typora.io/

        [5]

        highlight.js 官方文檔 (opens new window): https://highlightjs.org/

        [6]

        API文檔(opens new window): https://highlightjs.readthedocs.io/en/latest/api.html#highlightauto-code-languagesubset

        [7]

        markdown-it中文官網(wǎng) - 高亮語法配置(opens new window): https://markdown-it.docschina.org/#用法示例

        [8]

        markdown-editor-reactjs (opens new window): https://github.com/zero2one3/markdown-editor-reactjs

        [9]

        Github倉庫 (opens new window): https://github.com/zero2one3/markdown-editor-reactjs

        [10]

        npm包的地址 (opens new window): https://www.npmjs.com/package/markdown-editor-reactjs


        往期推薦

        CSS實現(xiàn)一個讓面試官眼前一亮的故障風格文字動畫

        一看就會的保姆級教程,10分鐘搭建個人博客

        一文帶你了解如何排查內(nèi)存泄漏導致的頁面卡頓現(xiàn)象


        - End -

        本文為公眾號【前端Sharing】

        關注我,閱讀更多精彩內(nèi)容

        ▽▽▽

        創(chuàng)作不易,請各位給加個星標,點贊、在看 支持哦!

        瀏覽 78
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            在线观看无码高清| 久久午夜无码鲁丝| 国产伊人久久| 超碰超爽| 老鸭窝成人视频| 中文字幕AV无码| 黄色成人网站在线观看| 欧美成人日韩| 91青青草视频| 伊人在线成人视频|