輸入功能
簡單的原理
輸入功能的話嘉裤,利用一個不可見的 <textarea>( 這里叫它inputer
)來接受鍵盤事件栖博,當(dāng)用戶將內(nèi)容輸入到inputer
中笛匙,通過監(jiān)聽事件oninput
的回調(diào)函數(shù)將inputer
中的內(nèi)容($inputer.value
)獲取到犀变,然后復(fù)制給當(dāng)前行的文本節(jié)點中(Line.$ref.textContent = $inputer.value
)获枝,最后清空inputer
中的內(nèi)容($inputer.value = ''
)省店。
另外為了能讓inputer
一直有效保持聚焦?fàn)顟B(tài)笨触,每次鼠標(biāo)點擊在編輯器的內(nèi)部時芦劣,都要去進行一次聚焦操作...吧?!
現(xiàn)在可能有個問題就是:假如有多個光標(biāo)(之后的每一個功能都會優(yōu)先考慮多光標(biāo)的情況哦~),對每個光標(biāo)所在的那一行需要輸入一些文字寸认,但是Line
所管理的當(dāng)前行只有一個偏塞,代碼寫起來會有點別扭..
就像一桌人在飯店吃飯邦鲫,但是筷子卻只有一雙庆捺,只有一個人吃完才把筷子留給下個人用的感覺...希望的是,每個人都有一雙筷子沧卢,會比較爽...~
對于計算姬們來說但狭,不是不行撬即,甚至可能是更好的方法剥槐,畢竟節(jié)省了的資源。但是在開發(fā)初期颅崩,只管開發(fā)者爽會讓項目進展的更快吧..?(
所以這里更改下Line
與Cursor
的代碼沿后,讓每一個Cursor實例
去維護一個記錄當(dāng)前行的Line實例
尖滚。
code
文件位置 serval/script/harusame-line.js
由于 Line 需要被實例化,而且考慮到方便與擴展起見睦裳,幾乎改了整個harusame-line.js廉邑,所以這里會貼出完整的代碼
;
;
/**
* 1. 行號 的元素節(jié)點的 id前綴
* 2. 行內(nèi)容 的元素節(jié)點的 id前綴
* 3. 初始行號
* 4. 行 的高度倒谷,同樣恨锚,這里先約(寫)定(死)猴伶,暴露給外面使用
*/
(function (config) {
var Line = function () {
this.$line_content = null
}
var self = Line
self.line_number_sign = 'LNS' /* 1 */
self.line_content_sign = 'LCS' /* 2 */
self.start_line = 1 /* 3 */
self.line_height = 20 /* 4 */
/**
* 獲得該行的行號DOM
*/
self.getLineNumberByLogicalY = function (v_line_number) {
return document.getElementById(self.line_number_sign + v_line_number)
}
/**
* 獲得該行的行內(nèi)容的DOM
*/
self.getLineContentByLogicalY = function (v_line_number) {
return document.getElementById(self.line_content_sign + v_line_number)
}
/**
* 生成一行
* @param content {string} 初始內(nèi)容
*/
self.generateLine = function (v_content) {
var line_number = self.max_line_number
var initial_content = v_content || ''
return Template.line({
line_number: line_number,
initial_content: initial_content,
line_content_sign: self.line_content_sign,
line_number_sign: self.line_number_sign,
start_line: self.start_line
})
}
/**
* 生成最大行號
*/
var PROXY_max_line_number = 0
Object.defineProperty(self, 'max_line_number', {
set: function (v_max_line_number) {
PROXY_max_line_number = v_max_line_number
},
get: function () {
return PROXY_max_line_number++
}
})
/**
* set:
* 1. 記錄當(dāng)前行
* 2. 記錄當(dāng)前行的 DOM
* get:
* 1. 返回當(dāng)前行
*/
var PROXY_line = 0
Object.defineProperty(self, 'line', {
set: function (v_logicalY) {
PROXY_line = v_logicalY /* 1 */
self.$ref = document.getElementById(self.line_content_sign + v_logicalY) /* 2 */
},
get: function () {
return PROXY_line
}
})
window.Line = Line
})()
文件位置 serval/script/harusame-template.js
同樣是 line 處
/**
* 行
* @param line_number {string} 行號
* @param initial_content {string} 該行初始內(nèi)容
*/
line: function (params) {
var line_number = params.line_number
return SatoriDom.compile(
e('div', {'class': 'line'}, [
e('div', {'class': 'line-number-wrap'}, [
e('span', {'id': params.line_number_sign + line_number, 'class': 'line-number'}, line_number + params.start_line + '')
]),
e('div', {'class': 'code-wrap'}, [
e('code', {'id': params.line_content_sign + line_number, 'class': 'code-content'}, params.initial_content || '')
])
])
)
},
文件位置 serval/script/harusame-serval.js
部分改動
var Serval = function (config) {
// ...
this._bindMouseEvent()
this._bindKeyboardEvent() /* 新增 */
}
Serval.prototype = {
// ...
/**
* 綁定各種鼠標(biāo)事件
*/
_bindMouseEvent: function () {
var self = this
/**
* addEventListener 是指自己寫的方法,見最下面
* 當(dāng) mousedown 時办桨,就對光標(biāo)位置進行計算
* 1. 取消鼠標(biāo)默認的行為呢撞,否則 2 不會生效
* 2. 讓編輯器總是能夠接受鍵盤事件
* 3. 定位鼠標(biāo)
*/
addEventListener(self.$serval_container, 'mousedown', function (event) {
event.preventDefault() /* 1 */
self.$inputer.focus() /* 2 */
self.allocTask(function (v_cursor) {
v_cursor.psysicalY = event.layerY
v_cursor.psysicalX = event.layerX
})
})
},
/**
* 綁定各種鍵盤事件
*/
_bindKeyboardEvent: function () {
var self = this
/**
* 當(dāng)對 $inputer 進行輸入的時候
* 1. 統(tǒng)一使用 insertContent 進行內(nèi)容的插入
* 2. 清除 $inputer 中的文本內(nèi)容
*/
addEventListener(self.$inputer, 'input', function (event) {
var content = self.$inputer.value
self.allocTask(function (v_cursor) {
self.insertContent(v_cursor, content)
})
self.$inputer.value = ''
})
},
/**
* 插入內(nèi)容
* 1. 緩存該光標(biāo)所在的行的DOM
* 2. 緩存該行的文本內(nèi)容
* 3. 取得光標(biāo)之前的字符串
* 4. 取得光標(biāo)之后的字符串
* 5. 拼接出完整的插入內(nèi)容后的字符串
* 6. 移動游標(biāo)
*/
_insertContent: function (v_cursor, v_content) {
var $line = v_cursor.line.$line_content /* 1 */
var textContent = $line.textContent /* 2 */
var logicalX = v_cursor.logicalX
var content_before = textContent.substring(0, logicalX) /* 3 */
var content_after = textContent.substring(logicalX, textContent.length) /* 4 */
$line.textContent = content_before + v_content + content_after /* 5 */
v_cursor.logicalX += v_content.length /* 6 */
},
// ...
}
現(xiàn)在就可以進行輸入啦~(如果出現(xiàn)錯誤殊霞,可能是因為之前的harusame-cursor.js
中的calcX
中的偷偷做了修改_(:3」∠)... i
改為 i + 1
i - 1
改為 i
以及 calcPsysicalX
中的 <=
改為 <
)绷蹲,效果見 圖3-1祝钢。
嗯嗯...看上去很美好,很有成就感蜒什,但是還不夠吃谣!
中文的輸入 與 瀏覽器事件行為的差異
理論上來說做裙,當(dāng)然實踐上也是锚贱,輸入法會從邏輯上被禁用...還無法輸入中文等需要拼寫的文字哦。畢竟準備變成中文字符的字母全被'偷'走了监徘。在input
的回調(diào)函數(shù)中加入
console.info('emit input')
來看看發(fā)生了什么...
在 火狐 中見 圖3-2凰盔。
在 Chrome 中見 圖3-3户敬。
可以看到在打開輸入法的情況下尿庐,要拼寫的字母直接就被拖進行里面了呢堰,并且在火狐中會連續(xù)觸發(fā)三次oninput
枉疼,而在 Chrome 中只會正常點地觸發(fā)一次。
雖然這個不同瀏覽器對事件作出行為的差異與之后的解決方案沒有什么直接關(guān)系钞翔,但是預(yù)先記錄并提醒一下布轿,在之后也與會遇到類似的不同瀏覽器之間事件行為的差異,并且會導(dǎo)致編輯器出問題稠肘。很幸運项阴,這里不會就是了~
要想使用拼寫的能力笆包,這時候需要compositionstart
與compositionend
的兩個事件來配合使用解決問題啦庵佣。
compositionstart
與compositionend
往往用在輸入法的處理方面。
在 MDN 中有相關(guān)解釋通今。
這里作簡單地解釋辫塌,就像在鍵盤上按下一個鍵派哲,會依次觸發(fā)keydown
keyup
一樣芭届,當(dāng)輸入(拼寫)文字的時候,也會依次觸發(fā)compositionstart
compositionend
。拿敲入nihao
為例的話树叽,在敲n
的時候题诵,compositionstart
會觸發(fā)层皱,期間每次敲入一個字母都會觸發(fā)compositionupdate
(這個事件的意思聽名字就能猜出來了叫胖,雖然這里沒有用到),在敲完nihao
,按下空格鍵哩俭、或者回車鍵凡资、或者鼠標(biāo)選擇文字等把拼寫后的內(nèi)容(你好
谬运、尼壕
梆暖、你號
什么的)進行輸出的時候式廷,才會觸發(fā)compositionend
事件。常理是這樣哦~
但是做的時候就遇到問題了蝗肪,這里就直接說了薛闪,在火狐中會有迷の行為俺陋。
先把代碼改成這樣腊状,然后見圖 3-4
文件位置 serval/script/harusame-serval.js
_bindKeyboardEvent: function () {
var self = this
var typewriting_switch = false /* 用來標(biāo)識是否正在使用輸入法,一般都會這么用 */
addEventListener(self.$inputer, 'compositionstart', function (event) {
console.info('emit compositionstart', event)
typewriting_switch = true
})
addEventListener(self.$inputer, 'compositionend', function (event) {
console.info('emit compositionend', event)
typewriting_switch = false
})
/**
* 當(dāng)對 $inputer 進行輸入的時候
* 1. 統(tǒng)一使用 _insertContent 進行內(nèi)容的插入
* 2. 清除 $inputer 中的文本內(nèi)容
*/
addEventListener(self.$inputer, 'input', function (event) {
console.info('emit input')
if (!typewriting_switch) {
var content = self.$inputer.value
self.allocTask(function (v_cursor) {
self._insertContent(v_cursor, content)
})
self.$inputer.value = ''
}
})
},
可以看到在火狐中,利用輸入法敲入nihao
后苟鸯,會依次
- 首先觸發(fā)
compositionstart
- 觸發(fā)五次
input
(因為nihao
有五個字母)早处,并且這五個字母不算作$inputer.value
中砌梆。 - 選擇
你好
進行輸出,觸發(fā)compositionend
傻丝,并且在data
中可以獲取葡缰。 - 觸發(fā)一次
input
- 再次觸發(fā)
compositionstart
- 觸發(fā)一次
input
- 再次觸發(fā)
compositionend
忱反,但此時data
中是空的 - 觸發(fā)一次
input
這方面的話温算,我也不是很懂啦...突然觸發(fā)那么多事件...!?
不過也沒關(guān)系注竿,再看看 Chrome 中的行為巩割。
這就很正常了宣谈,并且會發(fā)現(xiàn)編輯器中的第一行沒有你好
輸出,這才是正常啊~漩怎!因為輸出文字是利用input
的勋锤,Chrome 最后并沒有觸發(fā) input
,而在火狐中肯定是觸發(fā)了input
再輸出的你好
。這里可以看到火狐跟 Chrome 都能夠使用compositionend.data
來獲取到輸出的內(nèi)容,如果此時停止執(zhí)行input
回調(diào)函數(shù)中的邏輯的話,這樣就能獲得完整的輸入法體驗了猖吴。
code
文件位置 serval/script/harusame-serval.js
只改部分哦
/**
* 綁定各種鍵盤事件
*/
_bindKeyboardEvent: function () {
var self = this
var typewriting_switch = false /* 用來標(biāo)識是否正在使用輸入法嘹履,一般都會這么用 */
/**
* 當(dāng)準備使用輸入法進行輸入時
* 1. 開啟輸入法標(biāo)識
*/
addEventListener(self.$inputer, 'compositionstart', function (event) {
typewriting_switch = true
})
/**
* 當(dāng)準備使用輸入法進行輸出時
* 1. 輸出內(nèi)容
* 2. 清空 $inputer 中的內(nèi)容
* 3. 做完這些事后障陶,關(guān)閉輸入法標(biāo)識
*/
addEventListener(self.$inputer, 'compositionend', function (event) {
var content = event.data
/* 因為火狐會觸發(fā)兩次 compositionend抱究,而第二次的 data 是沒有數(shù)據(jù)的,所以只需要取有數(shù)據(jù)的那次 */
if (content.length !== 0) {
/* 1 */
self.allocTask(function (v_cursor) {
self._insertContent(v_cursor, content)
})
self.$inputer.value = '' /* 2 */
}
typewriting_switch = false /* 3 */
})
/**
* 當(dāng)對 $inputer 進行輸入的時候
* 1. 只有輸入法未開啟時,才使用 input 事件 進行輸出
* 1. 統(tǒng)一使用 _insertContent 進行內(nèi)容的插入
* 2. 清除 $inputer 中的文本內(nèi)容
*/
addEventListener(self.$inputer, 'input', function (event) {
/* 1 */
if (!typewriting_switch) {
var content = self.$inputer.value
self.allocTask(function (v_cursor) {
self._insertContent(v_cursor, content) /* 1 */
})
self.$inputer.value = '' /* 2 */
}
})
},
來看看效果吧敢靡,文字就順手敲得...圖3-6 ~
下一篇可能是回車
CHANGELOG
2017年7月20日 22:56
D
刪除了 不小心粘貼上來的 劇透內(nèi)容