DIV 限制行數:一個難題,以及一個不完整的解法

前陣子遇到一個問題一直沒有辦法有一個完美的解法,困擾了一陣子。這個問題是要在一個 contenteditable=true 的 div 上面限制輸入的行數,並且要符合以下需求:

  • 盡量保留 command queue stack 以使 undo/redo 正常 work
  • 中文輸入也要可以 work
  • Paste 也要 work

攔 OnKeyUp

一開始直覺的想法是當行數已經到了邊界值的時候就把 keyup event 攔掉,但這個解法立刻就被中文輸入打掛了。因為中間每一個注音符號都會產生 keyup event,所以這會造成注音打到一半就被卡住。再來是這樣就要自己去攔 onPaste 然後逐步逼近看看放了多少 partial content 可以符合行數限制,但這麼一來 undo/redo command queue 就完全被破壞了。

從 OnInput 下手

於是下一個想法是攔 onInput (onInput 是 post event 也就是內容輸入後才會發出 event)然後去看目前的行數是不是超過限制,是的話就用 range selection 選取限制行數內的內容並且使用 execCommand 做全選再插入的動作,這麼一來的話 undo 兩步後還是可以回到原本的狀態。但是中文限制還是會有問題。另外,還有一個問題是如果把 cursor 移到中間而不是在文字最後的話,那這個解法會導致最後面的文字被慢慢吃掉,這樣跟一般預期會卡住 input 的行為(比如 input element 設定 maxlength 的行為)差距還滿大。

實作

總之,我還是快速的的寫了一個簡單的版本放在 GitHub 上,已知問題就如上所提。如果有大大有祕技解法可以幫忙 improve 的話,還請指教!

在這個不是完全解法的實作中有以下幾點是可以討論的:

行高計算

CSS line-height 有可能是 default 的 “normal” 或是 120% 百分比設定,或是 1.2 倍數設定,或是 px/pt。每個 case 都做好像很累。 而且 “normal” 到底是多少,不同 browser 說不定還不一樣。 所以我用了一個方法,clone 一個要被限制行高的 div element 然後算出內容分別包含 &nbsp 以及 &nbsp<br>&nbsp 的 div 高度差,就是行高了。但缺點是會觸發 re-layout。

文字選取


這個實作中保留原本行數限制內容的方式是透過座標獲得 range 物件去選取一個範圍(就是行數乘上行高內的內容)。主要在 Chrome 上測試。不確定其它瀏覽器有沒有相容性問題。

Recursive issue

在 onInput event handler 中去對 element 做 input 的動作實在不是明智之舉,會造成 recursive stack overflow。因此加個 flag 或是先 unbind event 是必須的。

jQuery chainable

寫  jQuery 最重要的是要保留 Chainable 的特性,所以即使是寫一個不完整的 solution 還是要記得做到 chainable 噢。

Textare solution

目前這個解法暫時針對 div,如果 target 是 textarea 的話處理方式會有點不同,第一個關於 lineheight 取得的方式就要改變。由於 textarea 不會自動 resize 但是可以透過設定 rows 屬性然後取得 textarea.scrollHeight 獲得行高。另外,range selection 的方式在 textarea 也失效,可能的方式應該是在每次輸入前記下當前合法的字串,然後用來做回復的動作。

結論

小弟這邊提供的不是完整解法,Google 爬文許久還是沒有看到優雅的解法。寫出這文章希望可以有大大給個提示。目標還是希望做到如設定 maxlength 之後的 input 一樣的優雅的限制輸入。



Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *