日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
提升Web輸入體驗!JS如何自動配對標點符號?

在寫作編輯中,有很多需要成對出現(xiàn)的標點符號,比如引號、括號、書名號等,如下所示:

在遵義等地區(qū),都構建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務理念,為客戶提供成都做網(wǎng)站、成都網(wǎng)站設計 網(wǎng)站設計制作按需策劃設計,公司網(wǎng)站建設,企業(yè)網(wǎng)站建設,成都品牌網(wǎng)站建設,營銷型網(wǎng)站建設,外貿(mào)網(wǎng)站制作,遵義網(wǎng)站建設費用合理。

為了方便輸入,某些輸入法自帶了標點自動配對功能。什么意思呢?比如輸入一個前括號,自動補全后括號,然后光標位于中間。下面是小米手機自帶輸入法的演示:

標點自動配對

不僅僅是輸入法,大部分編輯器也實現(xiàn)了類似的功能,比如 vscode:

vscode標點自動配對

那么,這么好用的特性,如何讓 web 中的輸入框也能支持呢?

一、實現(xiàn)原理

原理其實非常簡單,可以分為以下幾個步驟:

  • 檢測輸入的內(nèi)容,如果是以上標點符號就下一步
  • 根據(jù)輸入的標點,自動補全與之對應的后半部分
  • 將光標移到兩個標點之間

是不是非常好理解呢?但是,里面的細節(jié)遠不止這些,涉及到非常多的比較生僻的原生方法,一起看看如何實現(xiàn)的吧

二、檢測輸入的內(nèi)容

這里檢測的是在鍵盤按下的時候,需要知道當前按下的是什么字符,所以一開始我想到了用keydown方法

editor.addEventListener("keydown", (ev) => {
console.log(ev.key, ev.code)
})

在keydown方法中,與鍵值相關的屬性有ev.key和ev.code,如下:

看似好像沒啥問題,可以通過ev.key區(qū)分具體輸入的是什么字符。其實還有很多問題,比如無法區(qū)分中英文標點輸入。

舉個例子:在中英文下分別輸入方括號。

可以看到,兩者的ev.key和ev.code是完全一樣的!

還有更離譜的,在中文輸入法下,某些標點是依次出現(xiàn)的,比如中文的單雙引號,按一次是上引號“,再按一次是下引號”,還有半括號,按一次是「,再按一次是『等等,像這類輸入就更加沒法判斷了。

為啥會這樣呢?因為這些標點都在一個按鍵上,keydown事件反應的是和鍵盤相關的屬性,如下:

  • 一個按鍵上密密麻麻的塞下了4個標點符號。

所以,我們需要用別的方式來檢測輸入的內(nèi)容。

在這里,可以用input事件來監(jiān)聽,ev.data表示當前輸入的字符。

editor.addEventListener("input", (ev) => {
console.log(ev.data)
})

注意,這里是字符,也就是真正輸入到頁面的文字,如下:

需要注意的是,在windows中文輸入法下,input 會觸發(fā)兩次,如下:

這是由于在 windows 中文輸入法下,標點輸入也和普通拼音輸入一樣,有候選詞的過程,就像這樣:

所以解決這個問題也很簡單,用compositionend事件就可以了,表示候選結束之后

editor.addEventListener("compositionend", (ev) => {
console.log(ev.data)
})

因此,兼容 windows 和 Mac OS的完整寫法應該是這樣

const input = function(ev){
if (ev.inputType === "insertText" || ev.type === 'compositionend') {
console.log(ev)
}
}

editor.addEventListener('compositionend', input)
editor.addEventListener('input', input)

因為我們只檢測標點符號,所以也無需擔心重復觸發(fā)的問題。

三、兩種輸入框

接下來就是具體的匹配實現(xiàn)了,在此之前先搞清楚兩種類型的輸入框。

一種是原生默認的表單輸入框input和textarea。


還有一種是手動給元素添加屬性contenteditable="true",或者 CSS 屬性 -webkit-user-modify。

yux閱文前端

或者

div{
-webkit-user-modify: read-write;
}

為啥要分這兩種呢?因為這兩種類型的光標處理方式完全不一樣。

四、表單輸入框

先來看表單輸入框,這里以textarea為例:

首先我們需要羅列一下需要匹配的標點符號,包含中英文。

const quotes = {
"'": "'",
'"': '"',
"(": ")",
"(": ")",
"【": "】",
"[": "]",
"《": "》",
"「": "」",
"『": "』",
"{": "}",
"“": "”",
"‘": "’",
};

接下來,根據(jù)前面提到的檢測輸入內(nèi)容的方法來自動補全標點,在原生輸入框中,可以用setRangeText方法來手動插入內(nèi)容。

  • HTMLInputElement.setRangeText() - Web APIs | MDN (mozilla.org)[3]
const input = function(ev){
const quote = quotes[ev.data];
if (quote && (ev.inputType === "insertText" || ev.type === 'compositionend')) {
this.setRangeText(quote)
}
}

效果如下:

是不是非常容易呢?不過還有些問題,比如中文的引號,就有些奇怪。

為啥會這樣呢?原因在于,中文的上引號和下引號是依次出現(xiàn)的,也就是說第一次按是上引號,第二次按就是下引號了,完全是由系統(tǒng)輸入法決定的,無法修改(英文不存在這個問題,因為上引號和下引號是相同的)。

中文的上引號和下引號依次出現(xiàn)。

那么,如何解決這個問題呢?我想到的方式是這樣的,對上引號和下引號分別進行處理。如果是上引號,就按照前面的思路進行處理;如果是下引號,就將光標往前移動一位,然后補全上引號,示意如下

具體實現(xiàn)就是,在羅列的標點符號添加下引號,并且添加標識,標識這些符號需要特殊處理。

const quotes = {
// 添加中文下引號映射
"”": "“",
"’": "‘",
};
const quotes_reverse = ["”", "’"];

然后如果是下引號,需要將光標往左移動一位,可以用到setSelectionRange方法,這個方法可以手動設置選區(qū)的位置,當前光標的位置可以通過兩個屬性selectionStart、selectionEnd來獲取。

  • HTMLInputElement.setSelectionRange() - Web APIs | MDN (mozilla.org)[4]

補全標點之后還需要將光標移動到兩者之間,具體實現(xiàn)如下:

const input = function(ev){
const quote = quotes[ev.data];
if (quote && (ev.inputType === "insertText" || ev.type === 'compositionend')) {
const reverse = quotes_reverse.includes(ev.data);
if (reverse) {
this.setSelectionRange(this.selectionStart - 1, this.selectionEnd - 1)
}
this.setRangeText(quote)
if (reverse) {
this.setSelectionRange(this.selectionStart + 1, this.selectionEnd + 1)
}
}
}

這樣就完美支持中文標點符號了。

完整代碼可以訪問:textarea-auto-quotes(codepen.io)[5]或者textarea-auto-quotes (juejin.cn)[6]

五、富文本輸入框

下面來看一種更普遍的輸入框,富文本編輯器。

yux閱文前端

思路其實和前面純文本一致,只是光標的處理方式不同。

首先,向光標處加入內(nèi)容,需要在range對象下處理,用到一個insertNode的方法,注意,這個方法需要傳入一個 node 節(jié)點,純字符需要用createTextNode創(chuàng)建。

  • Range.insertNode() - Web APIs | MDN (mozilla.org)[7]

具體實現(xiàn)如下:

const selection = document.getSelection();
const input = function(ev){
const quote = quotes[ev.data];
if (quote && (ev.inputType === "insertText" || ev.type === 'compositionend')) {
const newQuote = document.createTextNode(quote);
const range = selection.getRangeAt(0);
range.insertNode(newQuote);
}
}

效果如下:

可以看到,插入的標點符號被自動選中了,這是默認行為。那么,如何讓光標定位到兩者之間呢?這里可以用到setEndBefore方法,可以設置選區(qū)的結束點位置。

  • Range.setEndBefore() - Web APIs | MDN (mozilla.org)[8]
const selection = document.getSelection();
const input = function(ev){
const quote = quotes[ev.data];
if (quote && (ev.inputType === "insertText" || ev.type === 'compositionend')) {
const newQuote = document.createTextNode(quote);
const range = selection.getRangeAt(0);
range.insertNode(newQuote);
range.setEndBefore(newQuote); // 將光標移動到newQuote之前
}
}

Before表示“之前”,所以選區(qū)的結束點在新生成的字符之前,光標自然就移到兩者之間了。

然后來處理中文引號的問題,同樣是需要特殊處理,將光標往左移動一位,可以用到setStart和setEnd方法,表示設置選區(qū)的起始點。

  • Range.setStart() - Web APIs | MDN (mozilla.org)[9]
  • Range.setEnd() - Web APIs | MDN (mozilla.org)[10]

具體實現(xiàn)如下

const input = function(ev){
const quote = quotes[ev.data];
if (quote && ev.inputType === "insertText") {
const newQuote = document.createTextNode(quote);
const range = selection.getRangeAt(0);
const reverse = quotes_reverse.includes(ev.data);
if (reverse) {
const { startContainer, startOffset, endContainer, endOffset } = range;
range.setStart(startContainer, startOffset - 1);
range.setEnd(endContainer, endOffset - 1);
}
range.insertNode(newQuote);
if (reverse) {
range.setStartAfter(newQuote);
} else {
range.setEndBefore(newQuote);
}
}
}

這樣富文本也支持中英文標點自動配對了。

還有一點小細節(jié)可以優(yōu)化,在開發(fā)者工具中可以看到,新添加的標點都是一個個獨立的#text,導致把整個文本分割成立很多的小片段,如下:

打印一下子節(jié)點。

這里都是純文本,有辦法合并一下嗎?當然也有,用到的方法是normalize,可以將子節(jié)點“規(guī)范化”。

  • Node.normalize() - Web APIs | MDN (mozilla.org)[11]
const input = function(ev){
const quote = quotes[ev.data];
if (quote && ev.inputType === "insertText") {
// 規(guī)范化子節(jié)點
range.commonAncestorContainer.normalize();
}
}

現(xiàn)在看下效果(注意觀察控制臺的字符)。

打印子節(jié)點也只有一個了。

完整代碼可以查看:contenteditable-auto-quotes(codepen.io)[12] 或者 contenteditable-auto-quotes(juejin.cn)[13]。

六、整合成公共方法

以上案例是針對具體某一個元素實現(xiàn),如果有多個輸入框,可能會有點麻煩,所以有必要整合一下,實現(xiàn)一個更為通用的方法。

首先,我們可以把事件監(jiān)聽放在document上,而不是具體的某個輸入框。

document.addEventListener('compositionend', commonInput)
document.addEventListener('input', commonInput)

這里用了一個commonInput來處理表單輸入框和富文本的情況。

function commonInput(ev) {
const tagName = ev.target.tagName;
if (tagName === 'TEXTAREA' || tagName === 'INPUT') {
inputTextArea.call(ev.target, ev)
} else {
input.call(ev.target, ev)
}
}

注意,這里的this指向問題,使用call  指向了當前編輯的輸入框ev.target。

然后inputTextArea和input分別表示前面表單輸入和富文本的具體處理。

下面是完整代碼,你可以直接粘貼到任意控制臺進行試用,相當于一個polyfill。

(function(){
/*
* @desc: 自動匹配標點符號
* @email: yanwenbin1991@live.com
* @author: XboxYan
*/
const quotes = {
"'": "'",
'"': '"',
"(": ")",
"(": ")",
"【": "】",
"[": "]",
"《": "》",
"「": "」",
"『": "』",
"{": "}",
"“": "”",
"‘": "’",
"”": "“",
"’": "‘",
};

const quotes_reverse = ["”", "’"];
const selection = document.getSelection();
function commonInput(ev) {
const tagName = ev.target.tagName;
if (tagName === 'TEXTAREA' || tagName === 'INPUT') {
inputTextArea.call(ev.target, ev)
} else {
input.call(ev.target, ev)
}
}
document.addEventListener('compositionend', commonInput)
document.addEventListener('input', commonInput)

function inputTextArea(ev){
const quote = quotes[ev.data];
if (quote && (ev.inputType === "insertText" || ev.type === 'compositionend')) {
const reverse = quotes_reverse.includes(ev.data);
if (reverse) {
this.setSelectionRange(this.selectionStart - 1, this.selectionEnd - 1)
}
this.setRangeText(quote)
if (reverse) {
this.setSelectionRange(this.selectionStart + 1, this.selectionEnd + 1)
}
}
}
function input(ev){
const quote = quotes[ev.data];
if (quote && ev.inputType === "insertText") {
const newQuote = document.createTextNode(quote);
const range = selection.getRangeAt(0);
const reverse = quotes_reverse.includes(ev.data);
if (reverse) {
const { startContainer, startOffset, endContainer, endOffset } = range;
range.setStart(startContainer, startOffset - 1);
range.setEnd(endContainer, endOffset - 1);
}
range.insertNode(newQuote);
if (reverse) {
range.setStartAfter(newQuote);
} else {
range.setEndBefore(newQuote);
}
range.commonAncestorContainer.normalize();
}
}
})()

實戰(zhàn)一下,以下是某網(wǎng)站的一個評論輸入框,在控制臺注入以上代碼后,也能夠完美支持自動匹配標點。

七、總結和說明

想不到一個小小的功能居然包含了這么多不常見的API,下面總結一下:

  • 自動配對標點符號可以很好的提升輸入體驗;
  • keydown事件無法區(qū)分中英文輸入法,也無法區(qū)分同一按鍵對應的多個標點符號;
  • input事件可以通過ev.data檢測當前輸入的字符;
  • windows 操作系統(tǒng)下輸入中文標點符號會觸發(fā)兩次input,原因是和普通中文一樣,觸發(fā)了候選框;
  • windows 操作系統(tǒng)下可以通過compositionend事件來實現(xiàn),有效避免了兩次觸發(fā)的情況;
  • 原生表單輸入框和contenteditable可編輯元素的光標處理方式完全不一樣,需要分開處理;
  • 中文標點有點特殊,中文的上引號和下引號在同一按鍵上,輸入的時候是依次出現(xiàn)的,無法修改;
  • 如果是上引號,就在光標處插入下引號;如果是下引號,就將光標往前移動一位,然后補全上引號;
  • 在原生輸入框中,可以用setRangeText方法來手動插入內(nèi)容;
  • 在富文本輸入框中,可以用insertNode方法來手動插入內(nèi)容,文本需要用createTextNode創(chuàng)建;
  • 在原生輸入框中,可以用到setSelectionRange方法手動設置選區(qū)的位置;
  • 在富文本輸入框中,可以用setStart和setEnd方法手動設置選區(qū)的位置;

整體實現(xiàn)從代碼量看,其實并不多,主要是一些和 DOM相關的API,看著好像有些陌生。為啥會覺得陌生呢?當然是平時沒有用到過,這和現(xiàn)在的大環(huán)境是密接相關的,vue和react這些框架雖然給開發(fā)者提供了很多便利,不過也使得離原生,離DOM越來越遠,這樣就導致很多原生API壓根就沒見過,這何嘗不是一種損失呢?

參考資料

[1]Web 中的“選區(qū)”和“光標”: https://juejin.cn/post/7068232010304585741

[2]Web 中的“選區(qū)”和“光標”: https://juejin.cn/post/7068232010304585741#heading-1

[3]HTMLInputElement.setRangeText() - Web APIs | MDN (mozilla.org): https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setRangeText

[4]HTMLInputElement.setSelectionRange() - Web APIs | MDN (mozilla.org): https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange

[5]textarea-auto-quotes(codepen.io): https://codepen.io/xboxyan/pen/zYWzVGB

[6]textarea-auto-quotes (juejin.cn): https://code.juejin.cn/pen/7123852466059378702

[7]Range.insertNode() - Web APIs | MDN (mozilla.org): https://developer.mozilla.org/en-US/docs/Web/API/Range/insertNode

[8]Range.setEndBefore() - Web APIs | MDN (mozilla.org): https://developer.mozilla.org/en-US/docs/Web/API/Range/setEndBefore

[9]Range.setStart() - Web APIs | MDN (mozilla.org): https://developer.mozilla.org/en-US/docs/Web/API/Range/setStart

[10]Range.setEnd() - Web APIs | MDN (mozilla.org): https://developer.mozilla.org/en-US/docs/Web/API/Range/setEnd

[11]Node.normalize() - Web APIs | MDN (mozilla.org): https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize

[12]contenteditable-auto-quotes(codepen.io): https://codepen.io/xboxyan/pen/QWmgXML

[13]contenteditable-auto-quotes(juejin.cn): https://code.juejin.cn/pen/7123851982644707336


分享文章:提升Web輸入體驗!JS如何自動配對標點符號?
文章來源:http://m.5511xx.com/article/djdjodd.html