Line
harusame-line.js
在serval/script/
下创建 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} 初始内容
*/
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++
}
})
这时候刷新了下浏览器发现报错了,嗯嗯...别忘记引入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
以下...
继续 #1 中计算 Cursor
位置的逻辑
嘛,要计算 psysicalX logicalX
,要比计算Y
复杂一点...而且有各种各样的因素会影响这个,比如letter-spacing
窗口resize
不知道是啥宽度的内容
什么的,不管那么多~先只管正常情况下的英文,中文。
大致的计算思路。
先确定一下最终目的是为了让点击时,光标会自动偏移到一个最适合的它位置
,防止写着写着就写到其他地方去了的这种事情......_(:3」∠)...
拿 图2-1 为例,这是 Sublime Text 3
中的放大了很多倍的第一行的内容,当点击到红色圆圈处,触发一个方法calcX
,让它去尝试寻找一个可能是最适合的位置,这里是约定寻找一个总是大于且最靠近点击处的位置M1
,然后比较M1
与M1之前的一个字符处的位置
,光标靠近哪个就返回那个位置的字符位置
(psysicalX
)与它的索引
(logicalX
)。
逻辑步骤
在初始化的时候,计算并存储英文字母和中文字母的宽度
single_byte_width double_byte_width
。由于之前就设置了等宽字体
,所以按理来说,字符间都是宽度相等的,但是一眼就能看出英文字母
的宽度是一类,中文字母
的宽度是一类。因为字符方面的知识并不充足,只能下意识地怀疑是单字节
字符是一类宽度,双字节
字符是一类宽度...于是去找了相关的正则,做出来发现这么分类竟然没什么问题..!?(之后再补习._(:3」∠)...用户点击某处,得到
event.layerX
,作为参数v_psysicalX
传入 能够计算偏差后的psysicalX
的方法calcX
中(因为要返回二个结果,所以不叫他calcPsysicalX
orcalcLogicalX
)得到该行的字符串(textContent),转化为数组
content_array
声明一个保存字符累加长度的变量
current_width
,创建一个循环体在循环体中,当
current_width < v_psysicalX
的时候(也就是尝试寻找在点击处右边 && 离点击处最近光标位置),执行5
,否则执行6
声明一个变量
char_width
用来存储当前字符content_array[index]
的宽度,它通过计算得到。最终即char_width = calcCharWidth(content_array[index])
如果
current_width >= v_psysicalX
(找到了4
中所说的该位置),这时候去判断:点击位置离左边的光标处更近一点还是右边的光标处更近一点,返回更近一点的光标位置。(也就是让光标进行偏移到两个字符之间的位置。不然点哪光标在哪,光标会遮挡文字,而且可能会让用户觉得懵逼..?!..
不过说到这里,突然想到了一个没什么用的模拟修改液的功能.XD...)
接下来又能写代码了...
各个地方的代码
文件位置 serval/script/harusame-cursor.js
加的挺多的,直接贴完全了~
;
(function () {
/**
* 1. 光标本身的元素节点
*/
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 隐式转化为数字类型,并去除'px'单位,且保证精准度
*/
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,
/**
* 创建一个游标对象
*/
_generateCursor: function () {
this.$ref = SatoriDom.compile(
e('i', {'class': 'fake-cursor'})
)
},
/**
* 绑定 逻辑位置 与 物理位置 之间的关系
*/
_setObserver: function () {
/**
* 这里的 self 由于也是 js关键字,所以会高亮
* self 原本指向 window,一般用不到
*/
var self = this
/**
* 1. 这里赋值的是 _logicalY 哦,下面也是
* 2. 更新 psysicalY 的值
* 3. 更新 DOM 位置
* 4. 写到这里发现有点问题......
*/
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
/**
* 如果点击的位置大于该行长度,直接将光标放在该行末尾
*/
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. 行号 的元素节点的 id前缀
* 2. 行内容 的元素节点的 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} 初始内容
*/
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. 记录当前行
* 2. 记录当前行的 DOM
* get:
* 1. 返回当前行
*/
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} 该行初始内容
*/
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:
目的是达到了,理论上来说不想有的也都有:
- 点在
1
所在的元素节点上,也会有光标的定位效果; - 点在没有行的部分,光标也会定位过去。而希望光标能定位到最后一行,最后一列
- 点在 h 的前面的时候,不怎么容易点到,体验比较差...关于体验的话,感觉能在后面单独修正...
嘛..也想提供一些选项,让用户自定义行为,但是感觉反正我自己是不会用的...就不做了。
因为主要目标已经达到了..(可能)...其他的细节留给以后再说..
接下来是可能是输入内容与换行~
CHANGELOG
2017年7月12日 18:11
U
修改 calcPsysicalX 中的 <= 为 <
U
修改 calcX 中的 i 与 i - 1 为 i + 1 与 i