马黑黑 发表于 2022-6-28 20:51

Textarea - 双击获取行内容

[提示]能读懂本文,表明你即将可以用JS进行编程。本文阅读难度:★★★☆☆


JS 很容易获取多行文本框 textarea 输入光标(下称光标)所在的位置,就一个语句:

      let pos = ele.selectionStart;

这就将操作句柄为 ele 的 textarea 中光标的位置赋值给了变量 pos,pos 得到的数值实际上是 ele 对象即多行文本框从开头到光标位置的所有字符的长度的总和。但 selectionStart 实际上并不是指光标位置,而是指选中文本的开始处,对应的选中文本的结束处则是 selectionEnd。当没有被选中的文本,selectionStart 的意义等同于光标位置,故此我们可以借用;未选中文本的情况下,selectionEnd 的值和 selectionStart 的值相等,我们若通过 selectionEnd 来获取光标位置,效果也是一样的。

有了光标位置,下一步的思路是,先将 ele 的整体文本赋值给 str,再以光标所在处为界,将 str 一分为二:str1 是 str 的前面部分,str2 是 str 的后面部分:

      let str = ele.value;
      let str1 = str.substr(0, pos);
      let str2 = str.substr(pos);

这里,用到了 substr 内置函数,用于截取某个字符串中从开始到指定长度的字符串,它的语法是 text.substr(参数一,[参数二]),text必须,指要在其上截取指定字符串的字符串对象,参数一是必选参数,指截取的起始位置,参数二是截取的字符串长度,可以省略,省略时将取出从开始截取处到末尾的所有字符串。str1 是 str 的前面部分,所以从 0 开始截取,截取的长度就是光标所在位置 pos;str2 是 str 的后面部分,从光标位置 pos 处开始截取直到末尾。

接下来我们先获得光标所在的行数。实现思路:以换行符 \n 为拆分边界拆分 str1 即光标前面的文本,再计算拆分结果的数组长度,数组长度就是光标所在的行数即段落。原理则是这样,现代浏览器(绝对不包含IE)视换行符为 \n ,文本框中有多少个回车符就有多少个换行符,而光标所在位置前面的所有文本,有多少行等同于有多少个 \n ,所以,拆分后数组的大小即长度就是光标所在的行数(实际上是段落数),代码如下:

      let ar = str1.split('\n');
      let line = ar.length;

split是一个JS内置的拆分函数,语法为 text.plit(参数),text必须,是指要拆分的字符串对象,参数可选,也可以是空值,指拆分的依据(边界),省略时拆分结果和不拆分一样(例:'abc'.split() → abc),空值时以空值拆分,结果是拆分出了每一个字串个体(例: 'abc'.split('') → a,b,c)。split 得出的结果以数组存在,数组的长度就是拆分出来的单元的总个数,数组长度很容易获取,语法是 array.length,数组长度则表示是数组元素的总个数。

我们不需要特别处理拆分的数组,只需要获得数组的 length,因此上面两句简化为一句:

let line = str1.split('\n').length;

还有两个步骤我们就可以大功告成了。倒数第二个步骤是选中行(段落)文本:

textarea 有一个选中文本的方法,textarea.setSelectionRange(start,end),setSelectionRange 需要两个参数,选中文本的起始数值 start 和 结束数值 end ,我们又如何计算这两个数值呢?

我们可以从前面的变量 str1 即光标之前的字符串入手,查找最后一个 \n 的位置,该位置就是光标所在行的起始位置。很巧,JS中有一个函数叫 lastIndexOf(),用于查找指定字符串在原字符串中出现的最后的位置,例如查找换行符在 str1 中最后一次出现的位置:

      let pos1 = str1.lastIndexOf('\n');

但这个位置事实上不属于本行的起始部分,而是上一行的收尾部分,所以,pos1 的声明与赋值要改一下:

      let pos1 = str1.lastIndexOf('\n') + 1;

加上 1 pos1就能离开上一行的边界,加入到自己的领头边界中来,这样就不会出现选中是串到上一行的末尾处。加个 1 还能解决文本首行的问题:首行时,str1 没有回车符(\n)存在,lastIndexOf()的返回值是 -1,加上 1 之后正好是 0 。

有了 start 参数,还需 end 参数。这个,JS也提供了一个查找指定字符串在原字符串中首次出现的位置,indexOf(),我们将在光标后面部分的文本串 str2 中查找:

      let pos2 =str2.indexOf('\n') ;

这时,pos2 的值指的是在 str2 中首次出现 \n 的位置,等同于从光标到右边第一个换行符的字符串长度,但这还不是 setSelectionRange(start,end) 所需要的 end 值,我们还要做处理。首先处理 str2 没有换行符的情况,如若这样,str2.indexOf('\n') 也是返回 -1 的,所以:

if(pos2 == -1) pos2 = str.length - pos;

如果 str2 没有换行符,那么,它代表光标所在位置的剩余的文本在文本框的最后一行。最后一行可以以换行符结束,也可以没有换行符,没有换行符时就是上面语句要处理的,pos2 的值此时是用全部文本的长度减去光标位置所得的结果,即光标到末尾的字符串长度。

pos2 此时只是光标都行尾的字符串长度,还不是 end 的参数值,这是我们接下来要处理的:

      pos2 += pos;

这句代码意为,pos2 升级为 pos2 + pos,pos 是光标位置,加上光标到行尾的长度,pos2 才是 setSelectionRange(start,end) 函数所需的 end 参数。选择行文本工作于是完成:

      ele.setSelectionRange(pos1,pos2);

最后一个步骤,获取选中的文本。实现方法多,可以用 substr 和 substring,截取参数可考虑 pos1 和 pos2,也可考虑textarea控件提供的参数 selectionStart 和 selectionEnd(等同于本例变量 pos1 和 pos2);而最简洁的方法则是利用行数(line变量)直接从文本变量 str 中获取,当然要把 str 按行拆分成数组,再用处理后的 line 变量作为读取数组的 key :

      showbox.innerText = '[行' + line + ']' + str.split('\n');

红色部分,我们把拆分字符串、读取数组指定元素放在一起写,工作效率实在是太高了。line是读取数组元素的依据,为什么要减去 1 呢?因为,我们前面获取的 line 依据的是数组的总元素,从 1 开始,但数组元素的下标要从 0 开始,0 代表一个数组中的第一个元素标识。

以上分析的代码,可以封装成函数,然后通过textarea双击事件调用,即可完成本文的命题。全部代码分享在后续回复中,效果也发布在后面的跟帖内。

马黑黑 发表于 2022-6-28 20:54

<style>
.mama { margin: auto; width: fit-content; position: relative; }
.mama p, .mama h2 { margin: 0; padding: 6px 0; }
#txtbox { padding: 10px; width: 700px; height: 420px; font: normal 16px / 20px sans-serif; outline: none; }
#getbtn { outline: none; color: red; }
#getbtn:hover { color: blue; cursor: pointer; }
#showbox { width: 700px; }
</style>

<div class="mama">
        <h2>Textarea - 双击获取行内容</h2>
        <p><textarea id="txtbox">
//选中textarea一段文本+取得选中内容
function selectText(ele) {
        let pos = ele.selectionStart;
        let str = ele.value;
        let str1 = str.substr(0, pos);
        let str2 = str.substr(pos);
        let line = str1.split('\n').length;
        let pos1 = str1.lastIndexOf('\n') + 1;
        let pos2 =str2.indexOf('\n') ;
        if(pos2 == -1) pos2 = str.length - pos;
        pos2 += pos;
        ele.setSelectionRange(pos1,pos2);
        //showbox.innerText = '[行' + line + '] ' + str.substring(ele.selectionStart,ele.selectionEnd);
        showbox.innerText = '[行' + line + ']' + str.split('\n');
}
</textarea></p>
        <p id="showbox">双击文本框任意行获取内容</p>
</div>

<script>

let txtbox = document.querySelector('#txtbox'), showbox = document.querySelector('#showbox');

txtbox.ondblclick = () => selectText(txtbox);

//选中textarea一段文本+取得选中内容
function selectText(ele) {
        let pos = ele.selectionStart;
        let str = ele.value;
        let str1 = str.substr(0, pos);
        let str2 = str.substr(pos);
        let line = str1.split('\n').length;
        let pos1 = str1.lastIndexOf('\n') + 1;
        let pos2 =str2.indexOf('\n') ;
        if(pos2 == -1) pos2 = str.length - pos;
        pos2 += pos;
        ele.setSelectionRange(pos1,pos2);
        showbox.innerText = '[行' + line + ']' + str.split('\n');
}

</script>

马黑黑 发表于 2022-6-28 20:55

示例全部代码:

<style>
.mama { margin: auto; width: fit-content; position: relative; }
.mama p, .mama h2 { margin: 0; padding: 6px 0; }
#txtbox { padding: 10px; width: 700px; height: 420px; font: normal 16px / 20px sans-serif; outline: none; }
#getbtn { outline: none; color: red; }
#getbtn:hover { color: blue; cursor: pointer; }
#showbox { width: 700px; }
</style>

<div class="mama">
        <h2>Textarea - 双击获取行内容</h2>
        <p><textarea id="txtbox">
//选中textarea一段文本+取得选中内容
function selectText(ele) {
        let pos = ele.selectionStart;
        let str = ele.value;
        let str1 = str.substr(0, pos);
        let str2 = str.substr(pos);
        let line = str1.split('\n').length;
        let pos1 = str1.lastIndexOf('\n') + 1;
        let pos2 =str2.indexOf('\n') ;
        if(pos2 == -1) pos2 = str.length - pos;
        pos2 += pos;
        ele.setSelectionRange(pos1,pos2);
        //showbox.innerText = '[行' + line + '] ' + str.substring(ele.selectionStart,ele.selectionEnd);
        showbox.innerText = '[行' + line + ']' + str.split('\n');
}
</textarea></p>
        <p id="showbox">双击文本框任意行获取内容</p>
</div>

<script>

let txtbox = document.querySelector('#txtbox'), showbox = document.querySelector('#showbox');

txtbox.ondblclick = () => selectText(txtbox);

//选中textarea一段文本+取得选中内容
function selectText(ele) {
        let pos = ele.selectionStart;
        let str = ele.value;
        let str1 = str.substr(0, pos);
        let str2 = str.substr(pos);
        let line = str1.split('\n').length;
        let pos1 = str1.lastIndexOf('\n') + 1;
        let pos2 =str2.indexOf('\n') ;
        if(pos2 == -1) pos2 = str.length - pos;
        pos2 += pos;
        ele.setSelectionRange(pos1,pos2);
        showbox.innerText = '[行' + line + ']' + str.split('\n');
}

</script>

小辣椒 发表于 2022-6-28 21:37

黑黑 高难度的出来了,小辣椒看不懂{:4_201:}

红影 发表于 2022-6-28 21:40

2楼的试过,的确在下面显示第几行和行内容。这个是派什么用途的?

红影 发表于 2022-6-28 21:40

这个难度3星啊,我觉得5星都不止{:4_173:}

马黑黑 发表于 2022-6-28 22:28

红影 发表于 2022-6-28 21:40
这个难度3星啊,我觉得5星都不止

按理有一点点难度,针对没有JS基础的人来说,可能是5星难度

马黑黑 发表于 2022-6-28 22:29

小辣椒 发表于 2022-6-28 21:37
黑黑 高难度的出来了,小辣椒看不懂

这个估计这里能理解的人少

马黑黑 发表于 2022-6-28 22:34

红影 发表于 2022-6-28 21:40
2楼的试过,的确在下面显示第几行和行内容。这个是派什么用途的?

这用途很大的。想一想Word,它的行选择(这里指真正的一行)、段落选择,操作方式很友好。HTML的textarea控件,双击默认只选一个词或字(可以在回复帖子的小窗口测试)。

选中一行的应用场景很多,不同的应用可能都用得上。但目前纯JS解决的方法确实不多也不简练,我这个是自研的,觉得核心实现代码还是很干练的。

梦油 发表于 2022-6-29 10:30

俺读不懂本文,但我为黑黑朋友的热心所感动。希望我们花潮论坛在黑黑朋友的帮助下百花齐放满园春。

红影 发表于 2022-6-29 10:38

马黑黑 发表于 2022-6-28 22:29
这个估计这里能理解的人少

刚开始还能挺认真地在看,然后慢慢地就把自己看糊涂了{:4_173:}

红影 发表于 2022-6-29 10:40

马黑黑 发表于 2022-6-28 22:34
这用途很大的。想一想Word,它的行选择(这里指真正的一行)、段落选择,操作方式很友好。HTML的textarea ...

黑黑厉害,能有这么简洁的核心代码,也是比较了许多语句才实现的吧{:4_204:}

马黑黑 发表于 2022-6-29 13:05

红影 发表于 2022-6-29 10:40
黑黑厉害,能有这么简洁的核心代码,也是比较了许多语句才实现的吧

没有。完全自己想的。

马黑黑 发表于 2022-6-29 13:05

红影 发表于 2022-6-29 10:38
刚开始还能挺认真地在看,然后慢慢地就把自己看糊涂了

估计会这样

马黑黑 发表于 2022-6-29 13:06

梦油 发表于 2022-6-29 10:30
俺读不懂本文,但我为黑黑朋友的热心所感动。希望我们花潮论坛在黑黑朋友的帮助下百花齐放满园春。

{:4_190:}

梦油 发表于 2022-6-29 15:35

马黑黑 发表于 2022-6-29 13:06


黑黑朋友辛苦啦!

    {:4_191:}

红影 发表于 2022-6-29 16:35

马黑黑 发表于 2022-6-29 13:05
没有。完全自己想的。

厉害,很伟大{:4_173:}

红影 发表于 2022-6-29 16:36

马黑黑 发表于 2022-6-29 13:05
估计会这样

一个套一个的,我觉得脑袋不够用了{:4_173:}

马黑黑 发表于 2022-6-29 18:15

红影 发表于 2022-6-29 16:36
一个套一个的,我觉得脑袋不够用了

我开始学的时候,也是这样,后来从简单的例子开始,一个例子一个例子消化,然后自己尝试写几个,然后就弄复杂的

马黑黑 发表于 2022-6-29 18:15

红影 发表于 2022-6-29 16:35
厉害,很伟大

说说就算了
页: [1] 2
查看完整版本: Textarea - 双击获取行内容