--- title: 技術:為什麼中文打字會裂掉? tags: zh, tech description: 當你想要打「筆記」兩個字的時候,卻變成「ㄅㄧˇㄐㄧˋ」這幾個注音符號赤裸裸地在網頁上,怎麼樣都組不成一個真正的中文字。 image: https://hackmd.io/_uploads/BJSAIdp6_.png date: "2021-7-19" author: enen summary: | ![](https://hackmd.io/_uploads/Sy0tsKTa_.gif) 中文打字的你,有沒有經歷過「中文裂掉」的窘境呢?例如說,當你想要打「筆記」兩個字的時候,卻變成「ㄅㄧˇㄐㄧˋ」這幾個注音符號赤裸裸地在網頁上,怎麼樣都組不成一個真正的中文字。 --- # 技術:為什麼中文打字會裂掉? 中文打字的你,有沒有經歷過「中文裂掉」的窘境呢?例如說,當你想要打「筆記」兩個字的時候,卻變成「ㄅㄧˇㄐㄧˋ」這幾個注音符號赤裸裸地在網頁上,怎麼樣都組不成一個真正的中文字。 這個問題,來自編輯器有沒有好好處理「組字事件」。~~Facebook Messenger 可能就是沒有好好處理組字事件的軟體吧每次更新都超痛的。~~ 這一篇,簡單帶大家認識「鍵盤事件」跟「組字事件」,以及這些事件與「協作軟體」的相關性。 這些認識,能夠幫助我們以後開啟 **HackMD 究竟蘊含了什麼樣的技術** 之大門。 :point_left: 回到 [HackMD 中文官方部落格](https://blog.hackmd.io/zh) ## 打字不就是按下去?認識鍵盤事件(Key Event) ![](https://hackmd.io/_uploads/Sy0tsKTa_.gif) 網頁瀏覽器裡面的文字,從鍵盤按下去的那一刻,會啟動一個「鍵盤事件」,這個看似簡單的動作,其實又分成三個操作:keypress, keydown, keyup. 這邊先簡化一點講 **keydown** 跟 **keyup** :==keydown==顧名思義是「按下去的那個瞬間」這件事,每一個按鍵都會有一個相對應的 keycode ,按鍵按下去就是取得了那個代碼;同樣顧名思義,==keyup==則是「放開手指頭的那個瞬間」所觸發的那件事。 舉例來說,當你打了「h」這個英文字,沒錯你在鍵盤看到它寫著「h」,在螢幕的網頁上也是「h」,事情相當單純。 ``` keydown: h keyup: h ``` 但是,如果你用中文注音輸入法,想要打出「筆」這個字,而且不幸地,笨笨的注音輸入法,在拼字過程中出現了「比」,你還要按往下的箭頭,去選到候選清單上的另外一個「筆」,再多按幾下。 那麼事情會變成怎麼樣呢? ``` keydown: ㄅ keyup: ㄅ keydown: ㄧ keyup: ㄧ keydown: ˇ keyup: ˇ keydown: ArrowDown keyup: ArrowDown keydown: ArrowDown keyup: ArrowDown keydown: ArrowDown keyup: ArrowDown keydown: Enter keyup: Enter keydown: Enter keyup: Enter ``` 看到這邊,就可以理解英文字跟中文字,在網頁輸入時候的差別了。 ### keydown 跟 keyup 有不一樣嗎? 接著你可能會想,上面的 down 跟 up 冒號後面的內容,不是看起來都一樣嗎? 剛剛說 down 是「按下去的那個瞬間」這件事,但如果你不只按一瞬間,而是**長按**,那麼可以「連續觸發」;相反地,因為 up 的抬起手指頭一定是一瞬間的事,所以沒有辦法連續觸發。 用「筆」來舉例,如果說你家的貓 :cat2: 踩到鍵盤,在你按完「ㄅㄧ」之後幫你按了那個三聲「ˇ」好幾秒,鍵盤事件會長什麼樣子呢? ``` keydown: ㄅ keyup: ㄅ keydown: ㄧ keyup: ㄧ keydown: ˇ keydown: ˇ keydown: ˇ keydown: ˇ keydown: ˇ keydown: ˇ keyup: ˇ ``` 你會發現有 6 個 keydown,但只有一個 keyup。這樣一來,兩者的關係應該就清楚了。用鍵盤玩過電腦遊戲的人,相信此時也有點心領神會了。~~射擊的時候,按下去可以連續觸發,不過如果是那種瞄準了放開按鍵才丟出去的炸彈,那只能丟一次,所以開槍跟丟炸彈分別就是以 keydown 跟 keyup 兩種時刻觸發的吧我想。~~(還是比較像「上膛」&「擊發」呢?) ### 那麼 keypress 是什麼? 再回來介紹, ==keypress== 又是什麼?顧名思義,那是一種「按壓」。欸那跟 keydown 不是一樣嗎?不,這的動作,在「文字按鍵」跟「非文字按鍵」之間會有差別。 例如說,你按「h」會出現的是一個英文字「h」,不過你按「enter」並不是「出現一個字」這件事,而只有會執行這個對應的 keycode。 **keypress 只會出現在「產生文字的按鍵」**,像是「Esc」、「Enter」這些按鍵是沒有觸發的。 例如「英文字的大小寫」這件事,雖然都是按鍵同一個「h/H」,正因為多了 keypress 這個按壓的操作,而能區辨要去拿不同的 keycode 。而這,就是無法透過 down 跟 up 這兩個瞬間就判斷的事情。 ## 所以為什麼中文打字會裂掉?認識組字事件(Composition Event) 介紹了 keydown, keypress, keyup 給大家認識,其實是為了凸顯另外一件事的重要性。 剛才說 keypress 是「產生文字的按鍵」才會發生,中文輸入的你,可能會想到下一個問題: >注音算是「有產生文字」嗎? 注音會有 keypress 的操作嗎?多數時候,答案是沒有。 :no_entry_sign: 剛剛中文的「筆」在輸入時,鍵盤事件裡拆解出了 keydown 跟 keyup 的每一個動作,不管 down 或 up ,其實都還只出現個別的注音符號和聲調,都==還沒有出現真正的中文字==,對吧? ``` keydown: ㄅ keyup: ㄅ keydown: ㄧ keyup: ㄧ keydown: ˇ keyup: ˇ keydown: ArrowDown keyup: ArrowDown keydown: ArrowDown keyup: ArrowDown keydown: ArrowDown keyup: ArrowDown keydown: Enter keyup: Enter keydown: Enter keyup: Enter ``` :eight_spoked_asterisk: 重點來了:如果要在編輯器裡面使用 CJK (中、日、韓文),像是中文輸入,我們需要的是就不只有鍵盤事件(Key Event),還需要**組字事件(Composition Event)**。 以剛剛我們用注音輸入法打的「筆」為例: 「ㄅㄧˇ」之後還要從候選字庫裡選出對的選項,組字事件是長這樣的: ``` Composition Events: compositionstart: compositionupdate: ㄅ compositionupdate: ㄅㄧ compositionupdate: 筆 compositionupdate: 筆 compositionupdate: 筆 compositionupdate: 筆 compositionupdate: 筆 compositionupdate: 筆 compositionend: 筆 compositionstart: 筆 compositionupdate: ㄅ compositionupdate: ㄅㄧ compositionupdate: 比 compositionupdate: 比 compositionupdate: 🆚 compositionupdate: 筆 compositionupdate: 筆 compositionupdate: 筆 compositionend: 筆 ``` 看到組字事件的過程,是不是彷彿就看見從 Key event 的「ArrowDown、ArrowDown、Enter」同時地轉換成 Composition Event 的「比、🆚、筆」呢? 這就是組字事件,有別於鍵盤事件。唯有出現在「需要組字的語言」情境。 >註:每個瀏覽器的桌面、手機行為不同,也有可能有不同結果。可以用[我們的開發者 Max 寫的 codepen](https://codepen.io/jackycute/pen/EvNmWv) 來測試看看。 ## 有沒有處理「組字」,會差很多 為什麼有些軟體服務很好用,但改版時,中文輸入就是有那麼一點卡卡的?經常「裂掉」而無法順利的組字呢?~~像是經常改版更新的 Facebook Messenger(到底多創傷要一直提)。~~ 其實,就是在接收各種文字輸入者的「組字」過程時,沒有考慮到「組的過程出現的『字』」這個元素,要如何在編輯器裡面處理。很可能,因為也是在軟體更新時,忘記測試到這些語言啦! >在撰寫這篇部落格的同時,也發現到我們的成員 [Yukai](https://hackmd.io/@Yukai) 曾經對 VSCode 發過一個組字事件的 PR: [Fix backspace in IME composition on iOS Safari](https://github.com/microsoft/vscode/pull/40546)。 ## 何況還要「協作」! 如果一個編輯器,沒有想過它要接收的語言輸入裡面,會遇到「需要組字的語言輸入內容」,在 keypress 這端就漏掉了「組字過程中會產生的『準文字』」。 當然,換個角度想,如果沒有這些組字語言的使用者,這樣的需求與問題,也就不存在。 更重要的是,如果一個「協作」編輯器,沒想到要處理的不只是鍵盤事件,也還要處理組字事件,那麼在即時協作(Realtime Collaboration)這件事情上,恐怕就會遇到很多難關了。 每個軟體的產品定位不同,像是 Notion 就對於同一區塊的協作處理著墨較少。多人、同時編輯時,不容易找到對方在哪裡(沒有顯示游標),同時編輯一個區塊,甚至會出現某一方被蓋掉。 >測驗「協作」最嚴峻的考驗,就是找人一起,在編輯器同一行,打字,看看會發生什麼事。 介紹鍵盤事件、組字事件,是因為未來在談論「協作」這個技術時,能夠幫助認識解法的各種可能。 ## 怎麼看得出來,編輯器有處理「組字過程」? 文章開頭,提到了「中文打字裂掉」的情境,這是許多人在使用如 Google Doc、Dropbox Paper **同時打字**經驗時曾經遇過的狀況。 干擾組字事件的可能因素有很多,端視軟體的演算法,想要優先帶給使用者什麼樣的體驗。 HackMD 在 keypress 這端,將「組字過程」(以注音輸入法來說,就是注音符號拼起來選取中文)也當成一個事件傳送出去。所以你跟夥伴在協作的時候,是可以看對方的「組字過程」的唷! 有時開會,共筆編輯時,也難免有點赤裸感,按鍵輸入過程都即時呈現出來了。~~說不定可以衍生用法藉此來迂迴委婉表達一種想說又不敢說得太明白的感覺呢。~~ ## 測試看看你有在用的軟體,有沒有把組字事件傳送出去? 不同的編輯器和筆記軟體,有不同作法,你可以回想看看—— >你曾經看過「組字過程」出現在共筆文件上面嗎? 假設你的共筆對象,要打出「筆記軟體」這四個字—— - 你曾經在文件上,看到 ==「ㄅㄧˇ 筆 ㄐㄧˋ記」== 的這個過程嗎? 還是說,你會直接看到對方打出了「筆」—— - 然後隔了零點幾秒,游標顏色、底線閃爍,再看見對方打出了「記」、「軟」、「體」? 還是說,是等了多秒之後,對方已經打完好一陣子了, - 你才看見「筆記軟體」這四個字? 各種做法有其優劣。以追求寫作流暢度、文字快速產出為優先的 HackMD 而言,將組字過程納入協作的一環是必要的,同時也會帶來較大的伺服器負擔。這就要回到產品的核心價值了。 ## 還以為是筆記軟體,原來是線上遊戲 HackMD 的開發者 Max 曾經說過一句話:「這就像**大型多人線上即時遊戲**!」 單一個人輸入者,在編輯器上打字時,文字輸入即顯示的快慢,已經是一個重要的體驗了。 何況是兩個人以上,三個人、三十個人,同個時間在同一個編輯器上瀏覽、編輯時,就像多人即時的線上遊戲。有些玩家給出了一個動作,另外一個玩家反映了另外一個回饋,遊戲內的角色又要怎麼去處理多重拋接球及在互動過程的衝突干擾,而能順利闖關。 嵌入一個什麼,就像突然飛來一隻巨獸;玩家逃跑的時候需要一個傳送門,從戰場快速回到安全的堡壘;對了,還要跟隊友溝通聯繫戰術等等的事情。這種腳本,也通用在協作軟體的使用情境裡。 **鍵盤、組字;多使用者;即時輸入到文件裡——而能夠流暢——可能是個比你想像更複雜的事。** 從遊戲體驗的共鳴出發,以「讓大家快樂做筆記」為核心去開發產品,是我們的核心價值。 這些技術,還有許多可以深挖的有趣內容,就留待未來,慢慢分享了。 ### #### 參考資料 - [現代瀏覽器的拼字處理 Max Wu @ COSCUP 2017](https://hackmd.io/@MaxWu/HackMD-COSCUP2017#/) - [透過 Composition Events 增強非拉丁語系輸入法對輸入框的支援](https://kuro.tw/posts/2016/10/11/%E7%AD%86%E8%A8%98-%E9%80%8F%E9%81%8E-Composition-Events-%E5%A2%9E%E5%BC%B7%E9%9D%9E%E6%8B%89%E4%B8%81%E8%AA%9E%E7%B3%BB%E8%BC%B8%E5%85%A5%E6%B3%95%E5%B0%8D%E8%BC%B8%E5%85%A5%E6%A1%86%E7%9A%84%E6%94%AF%E6%8F%B4/) - [keydown, keypress, keyup 的差異](https://github.com/jijigo/notes/issues/30) - [比較 keydown, keypress, keyup 的差異](https://medium.com/@yitailin/%E6%AF%94%E8%BC%83-keydown-keypress-keyup-%E7%9A%84%E5%B7%AE%E7%95%B0-4e873ba17e81)