使用 textarea 當輸入框時的各種問題

只是單純想要實現使用 <textarea></textarea> 當作輸入框,並以 Shift+Enter 換行、EnterCtrl+Enter 送出,最後把送出的訊息自動顯示在畫面上。
但希望能避免在訊息框中只有空白或換行的時候被送出。

我沒想到只是這麼單純的事情也會搞得困難重重QQ(一部分是我忘記有很好用的方法可以用……)
牽涉到畫面處理的部分好像都特別複雜……越來越覺得前端好厲害好辛苦了。

為了怕自己下次又遇到同樣需求卻找不到參考資料(甚至關鍵字都不確定要怎麼下比較好),趕緊趁還沒把參考網頁都關掉的時候先整理一篇文。

今天會提到的內容:

  1. 使用 Ctrl + Enter 送出
  2. 使用 Ctrl + Enter 或 Enter 送出
  3. 避免送出空白內容
  4. 呈現輸入內容的方法

使用 <textarea> 代替 <input> 當作文本輸入框的理由,通常是因為有字數較多或換行的需求。常用於表單的描述欄位或是通訊軟體的輸入框上。
在輸入時,預設在按下 EnterShift+Enter 時可以換行,而今天我們希望讓 Ctrl+Enter 可以送出訊息,該怎麼做呢?

使用 Ctrl + Enter 送出

1
2
3
4
5
6
7
const textarea = document.querySelector(#textarea)

textarea.addEventListener('keyDown', function onKeyDown(e) {
if(e.ctrlKey && e.keyCode === 13) {
submitForm()
}
})
  1. 給 textara 的 DOM 加上事件監聽器,監聽 keyDown 事件
    這會在按鍵按下時觸發,若按住不放,會持續觸發
  2. 當按住 Ctrl 鍵時,回傳的事件中 ctrlKey = true
    按下 Enter 時,keyCode = 13
  3. 呼叫傳送表單的函式

在開頭的時候有提到,除了 Ctrl+Enter 也希望能按下 Enter 就能發送。
於是我做了這樣的改寫:

1
2
3
4
5
6
7
const textarea = document.querySelector('#textarea')

textarea.addEventListener('keyDown', function onKeyDown(e) {
if(!e.shiftKey && e.keyCode === 13) {
submitForm()
}
})
e.ctrlKey 改成 !e.shiftKey

前面加上 ! 是 not 的意思,這一段是指在不按下 Shift 鍵的情況下按下 Enter 就會觸發事件。

雖然邏輯上,限定 Ctrl+Enter 才能觸發,和除了 Shift+Enter 以外的狀況按下 Enter 會觸發是不同意義。
但我主要是想保留 Shift+Enter 的換行功能,所以我就這樣寫了。

如果想要寫成 if( (e.ctrlKey && e.keyCode === 13) || e.keyCode === 13 ) 也是可以的。

而按照上面那段程式碼寫後,發生了其他問題:

我的需求是按下送出後,頁面不會跳轉,填入內容出現在同頁上並清空輸入框。
我在程式碼中加上了 textarea.value = '' 的內容, Ctrl+Enter 有正常的送出並清空,但按下 Enter 的時候,輸入框卻自動換行了。
這是原本按下 Enter 或按下 Shift+Enter 就有的效果,所以我加上 preventDefault(),這個能取消事件預設行為的語法,就解決這件事了。

使用 Ctrl + Enter 或 Enter 送出

1
2
3
4
5
6
7
8
9
const textarea = document.querySelector('#textarea')

textarea.addEventListener('keyDown', function onKeyDown(e) {
if(!e.shiftKey && e.keyCode === 13) {
e.preventDefault()
textarea.value = ''
submitForm()
}
})

現在能正常送出了。
接下來想要完成的需求是希望能避免內容空白的狀況下被送出。

要如何得知輸入框裡頭只有空白和空行呢?

一個簡單的判斷方法如下:

1
if (textarea.value.trim() === '') return

trim() 是 JavaScript 字串的一個方法,可以移除字串開頭和結尾的空白字元,包含空格、換行等。
在移除空白後如果剩下空字串,就不做任何事。

避免送出空白內容

1
2
3
4
5
6
7
8
9
10
const textarea = document.querySelector('#textarea')

textarea.addEventListener('keyDown', function onKeyDown(e) {
if (textarea.value.trim() === '') return
if(!e.shiftKey && e.keyCode === 13) {
e.preventDefault()
textarea.value = ''
submitForm()
}
})

除了移除前後空白外,如果希望取得的內容保留文字前的空白只移除最後的空行,可以使用 trimEnd()
相對的,想移除開頭空白,可以使用 trimStart()

我們現在能順利送出訊息,也能避免送出空白內容。
但當我們想顯示送出的內文˙的時候,卻又發現了新的問題。

明明在 textarea 中有輸入換行的,怎麼送出後變成一個個空白了呢?
這是因為 textarea 中的換行是 \r\n,而 HTML 中的換行為 <br>

保留 textarea 中的空白與換行

1. 將 \r\n 取代為 <br>

1
textarea.value = textarea.value.replace(/\r\n/g, '<br>')

/\r\n/g 為正則表達式,正則表達式會用兩個 / 包住條件,尾巴的 g 代表的是所有搜尋到的該條件內容(不加的話找到第一項就會停止了),在這裡就是將符合 \r\n 的字符通通取代為 <br>
在這之後,使用 textarea.innerHtml 來將內容顯示在網頁中。

這個方法看似最直覺方便,但這會有 XSS 漏洞的問題。
被輸入在輸入框的字串如果被當作 html 文本直接傳入網頁會相當危險。
會需要先把可能造成危險的字串轉義成對應的字符(如將 < 轉換為 &lt; 等),最後再替換 \r\n

可以搜尋關鍵字「htmlEscape XSS」看看。

2. 加上 <pre></pre>

推薦此做法!

把要顯示內容的地方用 pre 包起來,便能完整顯示原先在輸入框中的空格與排版等。
需要注意的部分是,沒有做任何處理的情況下,字串長度超過版面寬度也不會自動顯示換行,而是直接突破他的框框長到視窗外面去。

想要讓他符合版面寬度換行的話,要修改他的 CSS ,新增 white-space: pre-wrap;white-space: pre-line;,前者會同時保留空格和換行,後者則會把多餘的空白合併,並保留換行。
而 pre 本身也有預設的一些樣式(如上下 margin 等),會需要進行額外的 CSS 調整。

如果覺得還要特地為 pre 調整樣式太麻煩,還有最後一個方法:

3. 在渲染標籤的 style 中加上 white-space

其實和 2. 的效果相同。
被加上 white-space 屬性的元素會決定如何顯示空白與換行符:

1
2
3
white-space: pre-wrap; //保留所有連續的空白字元,換行會發生在有換行符、<br> 或是被文字空間限制的時候
white-space: pre-line; //連續的空白字元會被合併,換行規則同上
white-space: pre; //跟 <pre> 標籤預設相同,會保留空白字元,在有換行符、<br> 時換行,但長字串會不售空間限制長得很長

所以其實如果希望顯示的文字能配合版面的話,只要加上 white-space 樣式就可以了,並不一定要使用 <pre>


參考資料