英文:
javascript function to tell if caret is in first or last row of textarea
问题
我看到这个问题被提出并得到回答,但答案都不适用于我的问题,这意味着它们并没有真正解决问题,而且它们都比较旧,所以可能存在新的解决方案。
我有一些文本区域,它们都从1行开始。用户输入导致文本区域根据内容增大。我希望用户能够使用箭头键在文本区域之间移动(它们垂直堆叠)。问题是,我只希望上箭头键在插入点在第一行时才能移动到上一个文本区域。下箭头键也是如此。
首先要注意的是:查找\n不会有帮助,因为它不是新行,只是文本包裹以适应文本区域。
其次,我不知道是否相关,但文本区域的宽度是固定的,所以也许可以获取插入点位置并进行一些数学计算,以确定它是否在第一行,尽管我不确定是否会因为自动换行而失效(即,第一行可能包含x个字符,但被包裹后只剩下y个)。
你会认为这是内置的。当你按上箭头键时,如果你在第一行,它会将插入点移到内容的开头。是否有办法利用这个逻辑?我假设它是内置的。下箭头键也是如此。
再次强调,我看到这个问题被提出,但无法使用任何答案解决我的特定情况。我使用的文本区域没有样式,唯一有用的信息是它们从1行开始,然后使用以下函数动态调整大小:
const resizeTextarea = (textarea) => {
textarea.style.boxSizing = "border-box";
textarea.style.height = "auto";
textarea.style.height = `${textarea.scrollHeight}px`;
};
这些文本区域也作为对象保存在一个数组中,使用useState。也许我应该在文本区域动态增大以适应内容(调用此函数)时向对象添加某种增量,以便当用户按下箭头键时,我可以知道该文本区域中有多少“行”。
可能的问题是,即使我知道行数,我仍然不知道如何确定插入点是否在第一行/最后一行。我是说,我已经知道clientHeight,但那并不能带我到达目标...
这部分真的很让我感兴趣,但我不确定哪段代码(或谁的代码)正在执行这个操作。
英文:
I've seen this question asked and answered, but the answers are never applicable to my issue, meaning they don't really solve the issue, and they're old so it's possible new solutions exist.
I have textareas that all start out with 1 row. User input causes the tetxareas to grow to fit content. I want the user to be able to move between textareas (they're stacked vertically) using the arrow keys. The issue is, I only want the UP ARROW to move to previous textarea if caret is in the first line. Same goes for DOWN ARROW/bottom line.
First thing to note: looking for \n doesn't help because it isn't a new line, just wrapped text to fit into the textarea.
Second: I don't know if it's relevant info, but the width of textareas would be fixed, so maybe it's possible to get caret position and do some math to calculate if it's in the first row, although I'm not sure if that works because of word rap (i.e., the first row could contain x characters, but is wrapped and now only has y).
You would think that it's built in. When you hit UP ARROW when you're in the first line it moves caret to start of content. Is there a way to utilize that logic? I'm assuming it's built in. Same goes for the DOWN ARROW.
Again, I have seen this question asked but couldn't use any of the answers for my specific case. The textareas I'm using are not styled and the only info that's helpful is that they start with 1 row and are resized dynamically using this function:
const resizeTextarea = (textarea) => {
textarea.style.boxSizing = "border-box";
textarea.style.height = "auto";
textarea.style.height = `${textarea.scrollHeight}px`;
};
The textareas are also saved as objects in an array using useState. Should I maybe add some sort of increment to the object when it dynamically grows to fit content (calls this function) so that when a user hits an arrow key, I could tell how many "lines" are in that textarea.
Potential issue is that I still don't know how to figure out if the caret is in that first row/last row even if I know the amount of rows. I mean, I already know the clientHeight and that couldn't lead me to the promise land...
> You would think that it's built in. When you hit UP ARROW when you're in the first line it moves caret to start of content. Is there a way to utilize that logic? I'm assuming it's built in. Same goes for the DOWN ARROW.
This part really interests me, but I'm not sure what code (or whose) is even doing it.
答案1
得分: 4
这不是一个解决方案,而是一个建议。
不要自定义操作,而是使用已经存在且可访问的键盘导航。TAB
键用于跳转到下一个元素,SHIFT
+ TAB
键用于返回上一个元素。任何严肃使用键盘进行导航的人都应该了解这些操作。
在我看来,你所描述的行为对用户来说可能会非常出乎意料。
如果你愿意,在使用基于选项卡的导航时,可以通过tabindex
来控制元素的顺序。不过,根据这段描述
> 它们垂直堆叠
你可能不应该使用这个选项。
> 是什么代码(或谁的代码)实现了这个功能
你所描述的行为,以及诸如选项卡之类的键盘快捷键的使用,都是由浏览器实现的。例如,根据Firefox文档,
当前页面
命令 | 快捷键 |
---|---|
聚焦到下一个链接或输入字段 | Tab |
聚焦到上一个链接或输入字段 | Shift + Tab |
https://support.mozilla.org/en-US/kb/keyboard-shortcuts-perform-firefox-tasks-quickly#w_current-page
但是,值得信赖的浏览器都是基于Web的明确定义和编码的标准来实现的。这些标准是普遍适用的。
请参阅 https://www.w3.org/WAI/perspective-videos/keyboard/
<br>
以及与该页面链接的深入阅读材料 https://www.w3.org/WAI/WCAG21/quickref/?tags=keyboard
英文:
This is not a solution, but a suggestion.
Instead of doing something custom just use the already existing and accessible keyboard navigation. TAB
for next element and SHIFT
+ TAB
for previous. Anyone who seriously uses a keyboard to navigate must know these.
In my opinion what you are describing would be extremely unexpected behavior for users.
If you want, you can control the element ordering when using tab-based navigation via tabindex
. However, based on this
> they're stacked vertically
you probably shouldn't even use that.
> what code (or whose) is even doing it
The behavior you have described as well as the use of keyboard shortcuts such as tab is implemented by the browser. e.g. from the Firefox documentation,
Current Page
Command | Shortcut |
---|---|
Focus Next Link or Input Field | Tab |
Focus Previous Link or Input Field | Shift + Tab |
https://support.mozilla.org/en-US/kb/keyboard-shortcuts-perform-firefox-tasks-quickly#w_current-page
However, credible browsers base their implementations off of well-defined and codified standards for the web. These standards are ubiquitous.
See https://www.w3.org/WAI/perspective-videos/keyboard/
<br>
and heavy reading also linked to by that page https://www.w3.org/WAI/WCAG21/quickref/?tags=keyboard
答案2
得分: 1
-
使用
selectionStart
获取光标位置。 -
如果光标位置 = 0,移动到前一个
textarea
。 -
如果光标位置 = textLength,移动到下一个
textarea
。 -
在
keyup
事件上执行该函数。
const textarea = document.getElementsByTagName("textarea");
Array.prototype.forEach.call(textarea, (e) => e.addEventListener("keyup", (e) => caret(e)));
function caret(e) {
let focus = document.querySelector("textarea:focus");
let focus_i = [...focus.parentNode.children].indexOf(focus);
switch (e.keyCode) {
case 38:
if (focus.selectionStart === 0) focus_i = focus_i - 1 < 0 ? focus_i : focus_i - 1;
break;
case 40:
if (focus.selectionStart === focus.textLength) focus_i = focus_i + 1 >= textarea.length ? focus_i : focus_i + 1;
break;
}
textarea[focus_i].focus();
}
textarea {
display: block;
}
<textarea></textarea>
<textarea></textarea>
<textarea></textarea>
英文:
-
Get caret position by
selectionStart
-
If the caret position = 0, move to previous
textarea
. -
If the caret position = textLength, move to next
textarea
. -
Execute the function when
keyup
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const textarea = document.getElementsByTagName("textarea");
Array.prototype.forEach.call(textarea, (e) => e.addEventListener("keyup", (e) => caret(e)));
function caret(e) {
let focus = document.querySelector("textarea:focus");
let focus_i = [...focus.parentNode.children].indexOf(focus);
switch (e.keyCode) {
case 38:
if (focus.selectionStart === 0) focus_i = focus_i - 1 < 0 ? focus_i : focus_i - 1;
break;
case 40:
if (focus.selectionStart === focus.textLength) focus_i = focus_i + 1 >= textarea.length ? focus_i : focus_i + 1;
break;
}
textarea[focus_i].focus();
}
<!-- language: lang-css -->
textarea {
display: block;
}
<!-- language: lang-html -->
<textarea></textarea>
<textarea></textarea>
<textarea></textarea>
<!-- end snippet -->
答案3
得分: 0
这不容易实现,因为你需要面对多个问题,我认为最好的解决方案应该是重新思考用户界面。
不管怎样,如果你真的想要做这个,我建议使用一个库来获取光标在屏幕上的位置。这些库通常用于创建带有语法高亮或其他高级功能的web文本编辑器,所以你有很大机会找到一个稳定的库。
一旦你获得了光标的位置,你可以将其与将光标移到文本的开头或结尾时获得的位置进行比较。这样你就可以知道光标是否位于第一行或最后一行。
这是一个使用Textarea Caret Position的示例实现:
function get_caret_top(el, pos) {
// 返回文本框 'el' 中光标的垂直位置,如果:
// - selectionStart 和 selectionEnd 都设置为 'pos'
// - 文本框位于页面的左上角
// - 文本框的高度适应其内容
const clone = el.cloneNode(true);
clone.removeAttribute("id");
clone.selectionStart = pos;
clone.selectionEnd = pos;
clone.style.visibility = "hidden";
clone.style.position = "absolute";
clone.style.left = "0";
clone.style.top = "0";
document.body.appendChild(clone);
clone.style.height = "0";
clone.style.height = clone.scrollHeight + "px";
const caret = getCaretCoordinates(clone, clone.selectionStart);
clone.remove();
return caret.top;
}
function get_caret_line(el) {
// 检查光标是否位于文本框 'el' 的第一行和/或最后一行。
// 返回一个对象,格式如下:
// { is_first: true|false, is_last: true|false }
const first_line_h = get_caret_top(el, 0);
const last_line_h = get_caret_top(el, el.value.length);
const caret_h = get_caret_top(el, el.selectionStart);
const is_first = first_line_h === caret_h;
const is_last = last_line_h === caret_h;
return { is_first, is_last };
}
document.querySelectorAll('.entry').forEach(el => {
el.addEventListener('keydown', e => {
const entry = e.target;
const { is_first, is_last } = get_caret_line(entry);
if (e.keyCode === 40) { // 箭头向下
if (!is_last) return;
if (!entry.dataset.next) return;
document.querySelector(entry.dataset.next).focus();
}
if (e.keyCode === 38) { // 箭头向上
if (!is_first) return;
if (!entry.dataset.prev) return;
document.querySelector(entry.dataset.prev).focus();
}
});
});
.entry {
width: 300px;
height: 100px;
display: block;
}
<script src="https://rawgit.com/component/textarea-caret-position/master/index.js"></script>
<textarea class="entry" id="txt1" data-next="#txt2">Dolor repellendus ratione aut voluptatem dolore. Architecto aliquid quisquam temporibus iusto explicabo non nisi? Veritatis dignissimos ut consectetur repellendus doloribus. Vero qui neque sunt magnam nobis! Veniam aliquid iure eligendi.</textarea>
<textarea class="entry" id="txt2" data-prev="#txt1">Sit velit aut debitis tenetur ipsum incidunt et Mollitia sed unde aliquam consequuntur rem Reiciendis ducimus praesentium laborum magni quibusdam Culpa magnam quia debitis nemo ipsam ducimus. Qui nisi nam.</textarea>
<details>
<summary>英文:</summary>
This is not easy to achieve since you have to face several issues and I think the best solution should be to rethink the UI.
Anyway, if you really want to do this, I would recommend to use a library to get the position of the caret on the screen. Such libraries are used to create web text editors with syntax highlighting or other advanced features, so you have a good chance to find a solid one.
Once you have the position of the caret, you can compare it with the ones you get if you move the cursor to the beginning or to the end of the text.
In that way you would know whether the caret lies on the first or on the last line.
Here is an example implementation using [Textarea Caret Position][1]:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
function get_caret_top(el, pos) {
// Return the y position that the caret in the textarea 'el'
// would have if:
// - both selectionStart and selectionEnd would be set to 'pos'
// - the textarea would lie in the top left corner of the page
// - the height of the textarea would grow to fit its content
const clone = el.cloneNode(true)
clone.removeAttribute("id")
clone.selectionStart = pos
clone.selectionEnd = pos
clone.style.visibility = "hidden"
clone.style.position = "absolute"
clone.style.left = "0"
clone.style.top = "0"
document.body.appendChild(clone)
clone.style.height = "0"
clone.style.height = clone.scrollHeight + "px"
const caret = getCaretCoordinates(clone, clone.selectionStart)
clone.remove()
return caret.top
}
function get_caret_line(el) {
// Check if the caret is on the first and/or on the
// last line of the textarea 'el'.
// Return an object in the form:
// { is_first: true|false, is_last: true|false }
const first_line_h = get_caret_top(el, 0)
const last_line_h = get_caret_top(el, el.value.length)
const caret_h = get_caret_top(el, el.selectionStart)
const is_first = first_line_h == caret_h
const is_last = last_line_h == caret_h
return { is_first, is_last }
}
document.querySelectorAll('.entry').forEach(el => {
el.addEventListener('keydown', e => {
const entry = e.target
const { is_first, is_last } = get_caret_line(entry)
if (e.keyCode == 40) { // arrow down
if (!is_last) return
if (!entry.dataset.next) return
document.querySelector(entry.dataset.next).focus()
}
if (e.keyCode == 38) { // arrow up
if (!is_first) return
if (!entry.dataset.prev) return
document.querySelector(entry.dataset.prev).focus()
}
})
})
<!-- language: lang-css -->
.entry {
width: 300px;
height: 100px;
display: block;
}
<!-- language: lang-html -->
<script src="https://rawgit.com/component/textarea-caret-position/master/index.js"></script>
<textarea class="entry" id="txt1" data-next="#txt2">Dolor repellendus ratione aut voluptatem dolore. Architecto aliquid quisquam temporibus iusto explicabo non nisi? Veritatis dignissimos ut consectetur repellendus doloribus. Vero qui neque sunt magnam nobis! Veniam aliquid iure eligendi.</textarea>
<textarea class="entry" id="txt2" data-prev="#txt1">Sit velit aut debitis tenetur ipsum incidunt et Mollitia sed unde aliquam consequuntur rem Reiciendis ducimus praesentium laborum magni quibusdam Culpa magnam quia debitis nemo ipsam ducimus. Qui nisi nam.</textarea>
<!-- end snippet -->
[1]: https://github.com/component/textarea-caret-position
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论