Line
harusame-line.js
在serval/script/
下創(chuàng)建 harusame-line.js
文件路徑 serval/script/harusame-line.js
;
/**
* 1. 行 的高度顾瞻,同樣料身,這里先約(寫)定(死)
*/
(function () {
var Line = {}
var self = Line
self.LINE_HEIGHT = 20 /* 1 */
window.Line = Line
})()
同時修改掉serval/script/harusame-cursor.js
中的 LINE_HEIGHT
為 Line.LINE_HEIGHT
,既然是 Line類
,順便把 serval/script/harusame-serval.js
中的 _generateLine
改造為由 Line
生成翻翩,這樣會比較合適吧颤枪,也順手把生成行號的方法寫一下~
文件路徑 serval/script/harusame-serval.js
/**
* 渲染一行
*/
_generateLine: function (v_content) {
var $line = Line.generateLine(v_content)
this.$line_container.appendChild($line)
}
文件路徑 serval/script/harusame-line.js
/**
* 生成一行
* @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})
},
/**
* 生成最大行號
*/
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++
}
})
這時候刷新了下瀏覽器發(fā)現(xiàn)報錯了慢宗,嗯嗯...別忘記引入harusame-line.js
愁铺。
文件路徑 serval/index.html
...
<script src="script/harusame-dom.js"></script>
<script src="script/harusame-template.js"></script>
<script src="script/harusame-line.js"></script>
<script src="script/harusame-cursor.js"></script>
<script src="script/harusame-serval.js"></script>
...
引入位置別忘了只能放在 harusame-cursor.js && harusame-serval.js
以上,harusame-template
以下...
繼續(xù) #1 中計算 Cursor
位置的邏輯
嘛床绪,要計算 psysicalX logicalX
客情,要比計算Y
復(fù)雜一點...而且有各種各樣的因素會影響這個,比如letter-spacing
窗口resize
不知道是啥寬度的內(nèi)容
什么的癞己,不管那么多~先只管正常情況下的英文膀斋,中文。
大致的計算思路痹雅。
先確定一下最終目的是為了讓點擊時仰担,光標(biāo)會自動偏移到一個最適合的它位置
,防止寫著寫著就寫到其他地方去了的這種事情......_(:3」∠)...
拿 圖2-1 為例绩社,這是 Sublime Text 3
中的放大了很多倍的第一行的內(nèi)容摔蓝,當(dāng)點擊到紅色圓圈處,觸發(fā)一個方法calcX
愉耙,讓它去嘗試尋找一個可能是最適合的位置贮尉,這里是約定尋找一個總是大于且最靠近點擊處的位置M1
,然后比較M1
與M1之前的一個字符處的位置
朴沿,光標(biāo)靠近哪個就返回那個位置的字符位置
(psysicalX
)與它的索引
(logicalX
)猜谚。
邏輯步驟
在初始化的時候败砂,計算并存儲英文字母和中文字母的寬度
single_byte_width double_byte_width
。由于之前就設(shè)置了等寬字體
魏铅,所以按理來說昌犹,字符間都是寬度相等的,但是一眼就能看出英文字母
的寬度是一類览芳,中文字母
的寬度是一類斜姥。因為字符方面的知識并不充足,只能下意識地懷疑是單字節(jié)
字符是一類寬度沧竟,雙字節(jié)
字符是一類寬度...于是去找了相關(guān)的正則铸敏,做出來發(fā)現(xiàn)這么分類竟然沒什么問題..!?(之后再補習(xí)._(:3」∠)...用戶點擊某處,得到
event.layerX
屯仗,作為參數(shù)v_psysicalX
傳入 能夠計算偏差后的psysicalX
的方法calcX
中(因為要返回二個結(jié)果搞坝,所以不叫他calcPsysicalX
orcalcLogicalX
)得到該行的字符串(textContent),轉(zhuǎn)化為數(shù)組
content_array
聲明一個保存字符累加長度的變量
current_width
魁袜,創(chuàng)建一個循環(huán)體在循環(huán)體中,當(dāng)
current_width < v_psysicalX
的時候(也就是嘗試尋找在點擊處右邊 && 離點擊處最近光標(biāo)位置)敦第,執(zhí)行5
峰弹,否則執(zhí)行6
聲明一個變量
char_width
用來存儲當(dāng)前字符content_array[index]
的寬度,它通過計算得到芜果。最終即char_width = calcCharWidth(content_array[index])
如果
current_width >= v_psysicalX
(找到了4
中所說的該位置)鞠呈,這時候去判斷:點擊位置離左邊的光標(biāo)處更近一點還是右邊的光標(biāo)處更近一點,返回更近一點的光標(biāo)位置右钾。(也就是讓光標(biāo)進(jìn)行偏移到兩個字符之間的位置蚁吝。不然點哪光標(biāo)在哪,光標(biāo)會遮擋文字舀射,而且可能會讓用戶覺得懵逼..?!..
不過說到這里窘茁,突然想到了一個沒什么用的模擬修改液的功能.XD...)
接下來又能寫代碼了...
各個地方的代碼
文件位置 serval/script/harusame-cursor.js
加的挺多的,直接貼完全了~
;
(function () {
/**
* 1. 光標(biāo)本身的元素節(jié)點
*/
var Cursor = function (config) {
this.$ref = null /* 1 */
this._logicalY = 0
this._logicalX = 0
this._psysicalY = 0
this._psysicalX = 0
this.selection_start = null
Cursor.preCheck()
this._generateCursor()
this._setObserver()
}
/**
* 得到瀏覽器計算后的寬度(width)
* getComputedStyle(v_node).width 是一個帶單位的字符串脆烟,用 parseFloat 隱式轉(zhuǎn)化為數(shù)字類型山林,并去除'px'單位,且保證精準(zhǔn)度
*/
function getComputedWidth (v_node) {
return parseFloat(getComputedStyle(v_node).width)
}
/**
* 檢測字符寬度
* a && 雨是隨便打的字符
*/
Cursor.preCheck = function () {
Line.line = 0
Line.$ref.textContent = 'a'
this.single_byte_width = getComputedWidth(Line.$ref)
Line.$ref.textContent = '雨'
this.double_byte_width = getComputedWidth(Line.$ref)
// 這句話是測試用的邢羔,等能夠輸入了的時候驼抹,記得刪除
Line.$ref.textContent = 'hello 你好'
console.info('single_byte_width', this.single_byte_width)
console.info('double_byte_width', this.double_byte_width)
}
/**
* 判斷字符寬度
* /[\x00-\xff]/ ASCII 編碼在 0-255 的字符哦
*/
Cursor.calcCharWidth = function (v_char) {
if (/[\x00-\xff]/.test(v_char)) {
return this.single_byte_width
} else {
return this.double_byte_width
}
}
Cursor.prototype = {
constructor: Cursor,
/**
* 創(chuàng)建一個游標(biāo)對象
*/
_generateCursor: function () {
this.$ref = SatoriDom.compile(
e('i', {'class': 'fake-cursor'})
)
},
/**
* 綁定 邏輯位置 與 物理位置 之間的關(guān)系
*/
_setObserver: function () {
/**
* 這里的 self 由于也是 js關(guān)鍵字,所以會高亮
* self 原本指向 window拜鹤,一般用不到
*/
var self = this
/**
* 1. 這里賦值的是 _logicalY 哦框冀,下面也是
* 2. 更新 psysicalY 的值
* 3. 更新 DOM 位置
* 4. 寫到這里發(fā)現(xiàn)有點問題......
*/
Object.defineProperty(self, 'logicalY', {
set: function (v_logicalY) {
self._logicalY = v_logicalY /* 1 */
self._psysicalY = self.calcPsysicalY(v_logicalY) /* 2 */
self._setY(self._psysicalY) /* 3 */
// self.$line = document.getElementById(LINE) /* 4 */
},
get: function () {
return self._logicalY
}
})
Object.defineProperty(self, 'psysicalY', {
set: function (v_psysicalY) {
self.logicalY = self.calcLogicalY(v_psysicalY)
},
get: function () {
return self._psysicalY
}
})
Object.defineProperty(self, 'logicalX', {
set: function (v_logicalX) {
self._logicalX = v_logicalX
self._psysicalX = self.calcPsysicalX(v_logicalX)
self._setX(self._psysicalX)
},
get: function () {
return self._logicalX
}
})
Object.defineProperty(self, 'psysicalX', {
set: function (v_psysicalX) {
var _proxy = self.calcX(v_psysicalX)
self._psysicalX = _proxy.psysicalX
self._logicalX = _proxy.logicalX
self._setX(self._psysicalX)
},
get: function () {
return self._psysicalX
}
})
},
_setX: function (v_psysicalX) {
this.$ref.style.left = v_psysicalX + 'px'
},
_setY: function (v_psysicalY) {
this.$ref.style.top = v_psysicalY + 'px'
},
/**
* 計算 物理 Y
*/
calcPsysicalY: function (v_logicalY) {
return v_logicalY * Line.LINE_HEIGHT
},
/**
* 計算 邏輯 Y
*/
calcLogicalY: function (v_psysicalY) {
return parseInt(v_psysicalY / Line.LINE_HEIGHT)
},
/**
* 計算 物理 X
*/
calcPsysicalX: function (v_logicalX) {
var content_array = Line.$ref.textContent.split('')
var current_width = 0
for (var i = 0; i < v_logicalX; i++) {
current_width += Cursor.calcCharWidth(content_array[i])
}
return current_width
},
/**
* 用于計算 邏輯 X
*/
calcX: function (v_psysicalX) {
var psysicalX = getComputedWidth(Line.$ref)
var textContent = Line.$ref.textContent
/**
* 如果點擊的位置大于該行長度,直接將光標(biāo)放在該行末尾
*/
if (psysicalX <= v_psysicalX) {
return {
psysicalX: psysicalX,
logicalX: textContent.length
}
}
var content_array = textContent.split('')
var current_width = 0
for (var i = 0; i < content_array.length; i++) {
var char_width = Cursor.calcCharWidth(content_array[i])
current_width += char_width
if (current_width >= v_psysicalX) {
var point_right = current_width
var point_left = current_width - char_width
var offset_right = point_right - v_psysicalX
var offset_left = v_psysicalX - point_left
if (offset_right < offset_left) {
return {
psysicalX: point_right,
logicalX: i + 1
}
} else {
return {
psysicalX: point_left,
logicalX: i
}
}
}
}
}
}
window.Cursor = Cursor
})()
文件位置 serval/script/harusame-line.js
加的挺多的敏簿,直接貼完全了~
;
/**
* 1. 行號 的元素節(jié)點的 id前綴
* 2. 行內(nèi)容 的元素節(jié)點的 id前綴
* 3. 初始行號
* 4. 行 的高度明也,同樣,這里先約(寫)定(死),暴露給外面使用
*/
(function (config) {
var Line = {}
var self = Line
self.LINE_HEIGHT = 20 /* 4 */
var LINE_NUMBER_SIGN = 'LNS' /* 1 */
var LINE_CONTENT_SIGN = 'LCS' /* 2 */
var START_LINE = 1 /* 3 */
/**
* 生成一行
* @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: LINE_CONTENT_SIGN,
LINE_NUMBER_SIGN: LINE_NUMBER_SIGN,
START_LINE: 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(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) {
console.info(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 || '')
])
])
)
},
來看看瀏覽器中的效果安岂,圖 2-2:
目的是達(dá)到了,理論上來說不想有的也都有:
- 點在
1
所在的元素節(jié)點上帆吻,也會有光標(biāo)的定位效果域那; - 點在沒有行的部分,光標(biāo)也會定位過去猜煮。而希望光標(biāo)能定位到最后一行次员,最后一列
- 點在 h 的前面的時候,不怎么容易點到王带,體驗比較差...關(guān)于體驗的話淑蔚,感覺能在后面單獨修正...
嘛..也想提供一些選項,讓用戶自定義行為愕撰,但是感覺反正我自己是不會用的...就不做了刹衫。
因為主要目標(biāo)已經(jīng)達(dá)到了..(可能)...其他的細(xì)節(jié)留給以后再說..
接下來是可能是輸入內(nèi)容與換行~
CHANGELOG
2017年7月12日 18:11
U
修改 calcPsysicalX 中的 <= 為 <
U
修改 calcX 中的 i 與 i - 1 為 i + 1 與 i