Chào mừng bạn đến với Cafedev! Trong bài viết này, chúng ta sẽ khám phá hai đối tượng quan trọng trong JavaScript là `Selection` và `Range`, từ những kiến thức cơ bản đến nâng cao. Những công cụ này cho phép bạn quản lý và thao tác với các vùng chọn trong tài liệu web một cách hiệu quả. Hãy cùng Cafedev tìm hiểu cách sử dụng chúng để nâng cao kỹ năng lập trình JavaScript của bạn!
Trong chương này, chúng ta sẽ khám phá cách chọn(Selection) trong tài liệu, cũng như chọn(Selection) trong các trường biểu mẫu, chẳng hạn như <input>
.
JavaScript có thể truy cập một lựa chọn hiện có, chọn/bỏ chọn các nút DOM toàn bộ hoặc một phần, loại bỏ nội dung đã chọn khỏi tài liệu, bọc nó vào một thẻ, và nhiều thứ khác.
Bạn có thể tìm thấy một số công thức cho các nhiệm vụ thông thường ở cuối chương, trong phần “Tóm tắt”. Có thể điều đó đáp ứng nhu cầu hiện tại của bạn, nhưng bạn sẽ nhận được nhiều hơn nếu đọc toàn bộ văn bản.
Các đối tượng Range
và Selection
cơ bản rất dễ nắm bắt, và sau đó bạn sẽ không cần công thức để làm cho chúng hoạt động như mong muốn.
Nội dung chính
1. Range
Khái niệm cơ bản của lựa chọn là Range, về cơ bản là một cặp “điểm biên giới”: điểm bắt đầu của phạm vi và điểm kết thúc của phạm vi.
Một đối tượng Range
được tạo ra mà không cần tham số:
let range = new Range();
Sau đó, chúng ta có thể đặt ranh giới của lựa chọn bằng cách sử dụng range.setStart(node, offset)
và range.setEnd(node, offset)
.
Như bạn có thể đoán, sau này chúng ta sẽ sử dụng các đối tượng Range
để chọn, nhưng trước tiên hãy tạo vài đối tượng như vậy.
Chọn một phần văn bản
Điều thú vị là đối số đầu tiên node
trong cả hai phương thức có thể là một nút văn bản hoặc một nút phần tử, và ý nghĩa của đối số thứ hai phụ thuộc vào điều đó.
Nếu node
là một nút văn bản, thì offset
phải là vị trí trong văn bản của nó.
Ví dụ, với phần tử <p>Hello</p>
, chúng ta có thể tạo phạm vi chứa các chữ cái “ll” như sau:
<p id="p">Hello</p>
<script>
let range = new Range();
range.setStart(p.firstChild, 2);
range.setEnd(p.firstChild, 4);
// toString of a range returns its content as text
console.log(range); // ll
</script>
Ở đây chúng ta lấy phần tử con đầu tiên của <p>
(đó là nút văn bản) và chỉ định các vị trí văn bản bên trong nó:
Chọn các nút phần tử
Ngược lại, nếu node
là một nút phần tử, thì offset
phải là số thứ tự của nút con.
Điều này rất tiện lợi để tạo các phạm vi chứa các nút như một khối, không dừng lại đâu đó bên trong văn bản của chúng.
Ví dụ, chúng ta có một đoạn tài liệu phức tạp hơn:
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
Đây là cấu trúc DOM của nó với cả nút phần tử và nút văn bản:
Chúng ta sẽ tạo một phạm vi cho "Example: italic"
.
Như chúng ta có thể thấy, cụm từ này bao gồm chính xác hai nút con của <p>
, với chỉ số 0
và 1
:
– Điểm bắt đầu có <p>
là nút cha node
, và 0
là offset.
Vì vậy, chúng ta có thể đặt nó là range.setStart(p, 0)
.
– Điểm kết thúc cũng có <p>
là nút cha node
, nhưng 2
là offset (nó chỉ định phạm vi đến, nhưng không bao gồm offset
).
Vì vậy, chúng ta có thể đặt nó là range.setEnd(p, 2)
.
Đây là bản demo. Nếu bạn chạy nó, bạn có thể thấy rằng văn bản được chọn:
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
<script>
let range = new Range();
range.setStart(p, 0);
range.setEnd(p, 2);
// toString of a range returns its content as text, without tags
console.log(range); // Example: italic
// apply this range for document selection (explained later below)
document.getSelection().addRange(range);
</script>
Đây là một bàn thử nghiệm linh hoạt hơn nơi bạn có thể đặt số bắt đầu/kết thúc của phạm vi và khám phá các biến thể khác:
run autorun
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
From <input id="start" type="number" value=1> – To <input id="end" type="number" value=4>
<button id="button">Click to select</button>
<script>
button.onclick = () => {
let range = new Range();
range.setStart(p, start.value);
range.setEnd(p, end.value);
// apply the selection, explained later below
document.getSelection().removeAllRanges();
document.getSelection().addRange(range);
};
</script>
Ví dụ: chọn trong cùng một thẻ <p>
từ vị trí 1 đến 4 sẽ cung cấp cho chúng ta khoảng bao gồm thẻ <i>
in nghiêng và thẻ <b>
in đậm:
Chúng ta không cần phải sử dụng cùng một nút trong setStart
và setEnd
. Một phạm vi có thể trải dài qua nhiều nút không liên quan. Điều quan trọng là điểm kết thúc phải nằm sau điểm bắt đầu trong tài liệu.
Chọn một đoạn lớn hơn
Hãy thực hiện một lựa chọn lớn hơn trong ví dụ của chúng ta, như thế này:
Chúng ta đã biết cách làm điều đó. Chúng ta chỉ cần đặt điểm bắt đầu và điểm kết thúc dưới dạng offset tương đối trong các nút văn bản.
Chúng ta cần tạo một phạm vi, mà:
- bắt đầu từ vị trí 2 trong nút con đầu tiên của
<p>
(lấy tất cả trừ hai chữ cái đầu tiên của “Example: “) - kết thúc tại vị trí 3 trong nút con đầu tiên của
<b>
(lấy ba chữ cái đầu tiên của “bold”, nhưng không nhiều hơn):
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
<script>
let range = new Range();
range.setStart(p.firstChild, 2);
range.setEnd(p.querySelector('b').firstChild, 3);
console.log(range); // ample: italic and bol
// use this range for selection (explained later)
window.getSelection().addRange(range);
</script>
Như bạn có thể thấy, việc tạo ra một phạm vi theo ý muốn là khá dễ dàng.
Nếu chúng ta muốn lấy các nút như một khối, chúng ta có thể truyền các phần tử vào setStart/setEnd
. Ngược lại, chúng ta có thể làm việc ở mức văn bản.
2. Các thuộc tính của Range
Đối tượng phạm vi mà chúng ta tạo ra trong ví dụ trên có các thuộc tính sau:
– startContainer
, startOffset
— nút và offset của điểm bắt đầu,
– trong ví dụ trên: nút văn bản đầu tiên bên trong <p>
và 2
.
– endContainer
, endOffset
— nút và offset của điểm kết thúc,
– trong ví dụ trên: nút văn bản đầu tiên bên trong <p>
và 3
.
– collapsed
— kiểu boolean, true
nếu phạm vi bắt đầu và kết thúc ở cùng một điểm (vì vậy không có nội dung bên trong phạm vi),
– trong ví dụ trên: false
– commonAncestorContainer
— tổ tiên chung gần nhất của tất cả các nút trong phạm vi,
– trong ví dụ trên: <p>
`
3. Các phương thức chọn phạm vi
Có nhiều phương thức tiện lợi để thao tác với các phạm vi.
Chúng ta đã thấy setStart
và setEnd
, dưới đây là các phương thức tương tự khác.
Đặt điểm bắt đầu của phạm vi:
setStart(node, offset)
đặt điểm bắt đầu tại: vị tríoffset
trongnode
setStartBefore(node)
đặt điểm bắt đầu tại: ngay trướcnode
setStartAfter(node)
đặt điểm bắt đầu tại: ngay saunode
Đặt điểm kết thúc của phạm vi (các phương thức tương tự):setEnd(node, offset)
đặt điểm kết thúc tại: vị tríoffset
trongnode
setEndBefore(node)
đặt điểm kết thúc tại: ngay trướcnode
setEndAfter(node)
đặt điểm kết thúc tại: ngay saunode
Về mặt kỹ thuật,setStart/setEnd
có thể làm mọi thứ, nhưng nhiều phương thức hơn sẽ cung cấp sự tiện lợi hơn.
Trong tất cả các phương thức này,node
có thể là nút văn bản hoặc nút phần tử: đối với nút văn bản,offset
bỏ qua số lượng ký tự đó, trong khi đối với nút phần tử là số lượng nút con đó.
Còn nhiều phương thức khác để tạo phạm vi:
selectNode(node)
đặt phạm vi để chọn toàn bộnode
selectNodeContents(node)
đặt phạm vi để chọn toàn bộ nội dung củanode
collapse(toStart)
nếutoStart=true
đặt điểm kết thúc=điểm bắt đầu, nếu không đặt điểm bắt đầu=điểm kết thúc, do đó thu gọn phạm vicloneRange()
tạo một phạm vi mới với cùng điểm bắt đầu/điểm kết thúc
4. Các phương thức chỉnh sửa phạm vi
Khi phạm vi đã được tạo, chúng ta có thể thao tác với nội dung của nó bằng các phương thức sau:
– deleteContents()
— loại bỏ nội dung phạm vi khỏi tài liệu
– extractContents()
— loại bỏ nội dung phạm vi khỏi tài liệu và trả về dưới dạng DocumentFragment
– cloneContents()
— sao chép nội dung phạm vi và trả về dưới dạng DocumentFragment
– insertNode(node)
— chèn node
vào tài liệu tại đầu phạm vi
– surroundContents(node)
— bọc node
quanh nội dung phạm vi. Để điều này hoạt động, phạm vi phải chứa cả thẻ mở và thẻ đóng cho tất cả các phần tử bên trong: không có phạm vi một phần như
.<i>abc
.
Với các phương thức này, chúng ta có thể làm cơ bản mọi thứ với các nút đã chọn.
Đây là bàn thử nghiệm để xem chúng hoạt động:
Click buttons to run methods on the selection, "resetExample" to reset it.
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
<p id="result"></p>
<script>
let range = new Range();
// Each demonstrated method is represented here:
let methods = {
deleteContents() {
range.deleteContents()
},
extractContents() {
let content = range.extractContents();
result.innerHTML = "";
result.append("extracted: ", content);
},
cloneContents() {
let content = range.cloneContents();
result.innerHTML = "";
result.append("cloned: ", content);
},
insertNode() {
let newNode = document.createElement('u');
newNode.innerHTML = "NEW NODE";
range.insertNode(newNode);
},
surroundContents() {
let newNode = document.createElement('u');
try {
range.surroundContents(newNode);
} catch(e) { console.log(e) }
},
resetExample() {
p.innerHTML = `Example: <i>italic</i> and <b>bold</b>`;
result.innerHTML = "";
range.setStart(p.firstChild, 2);
range.setEnd(p.querySelector('b').firstChild, 3);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
};
for(let method in methods) {
document.write(`<div><button onclick="methods.${method}()">${method}</button></div>`);
}
methods.resetExample();
</script>
Cũng có các phương thức để so sánh các phạm vi, nhưng chúng ít được sử dụng. Khi cần, vui lòng tham khảo spec hoặc hướng dẫn MDN.
5. Selection
Range
là một đối tượng chung để quản lý các phạm vi lựa chọn. Mặc dù việc tạo ra một Range
không có nghĩa là chúng ta thấy sự lựa chọn trên màn hình.
Chúng ta có thể tạo các đối tượng Range
, truyền chúng xung quanh — chúng không tự động chọn bất kỳ thứ gì trên màn hình.
Sự lựa chọn trong tài liệu được đại diện bởi đối tượng Selection
, có thể được lấy qua window.getSelection()
hoặc document.getSelection()
. Một sự lựa chọn có thể bao gồm không có hoặc nhiều phạm vi. Ít nhất, Tài liệu API Selection nói như vậy. Trong thực tế, chỉ có Firefox cho phép chọn nhiều phạm vi trong tài liệu bằng cách sử dụng key:Ctrl+click
(key:Cmd+click
cho Mac).
Đây là ảnh chụp màn hình của một sự lựa chọn với 3 phạm vi, thực hiện trên Firefox:
Các trình duyệt khác hỗ trợ tối đa 1 phạm vi. Như chúng ta sẽ thấy, một số phương thức của Selection
giả định rằng có thể có nhiều phạm vi, nhưng một lần nữa, trong tất cả các trình duyệt ngoài Firefox, chỉ có tối đa 1 phạm vi.
Đây là bản demo nhỏ cho thấy sự lựa chọn hiện tại (chọn một cái gì đó và nhấp chuột) dưới dạng văn bản:
6. Các thuộc tính của Selection
Như đã nói, một sự lựa chọn lý thuyết có thể chứa nhiều phạm vi. Chúng ta có thể lấy các đối tượng phạm vi này bằng phương thức:
– getRangeAt(i)
— lấy phạm vi thứ i, bắt đầu từ 0
. Trong tất cả các trình duyệt ngoại trừ Firefox, chỉ 0
được sử dụng.
Ngoài ra, còn có các thuộc tính thường cung cấp sự tiện lợi tốt hơn.
Tương tự như một phạm vi, một đối tượng lựa chọn có điểm bắt đầu, gọi là “anchor”, và điểm kết thúc, gọi là “focus”.
Các thuộc tính chính của sự lựa chọn là:
anchorNode
— nút nơi sự lựa chọn bắt đầu,anchorOffset
— offset tronganchorNode
nơi sự lựa chọn bắt đầu,focusNode
— nút nơi sự lựa chọn kết thúc,focusOffset
— offset trongfocusNode
nơi sự lựa chọn kết thúc,isCollapsed
—true
nếu sự lựa chọn không chọn gì (phạm vi rỗng), hoặc không tồn tại.rangeCount
— số lượng phạm vi trong sự lựa chọn, tối đa1
trong tất cả các trình duyệt ngoại trừ Firefox.
Có một sự khác biệt quan trọng giữa điểm bắt đầu/kết thúc của sự lựa chọn so với điểm bắt đầu/kết thúc củaRange
.
Như chúng ta đã biết, các đối tượngRange
luôn có điểm bắt đầu trước điểm kết thúc.
Đối với sự lựa chọn, điều đó không phải lúc nào cũng đúng.
Việc chọn một cái gì đó bằng chuột có thể được thực hiện theo cả hai hướng: từ “trái sang phải” hoặc “phải sang trái”.
Nói cách khác, khi nút chuột được nhấn và sau đó di chuyển về phía trước trong tài liệu, thì điểm kết thúc (focus) của nó sẽ ở sau điểm bắt đầu (anchor).
Ví dụ, nếu người dùng bắt đầu chọn bằng chuột và đi từ “Example” đến “italic”:
…Nhưng sự lựa chọn tương tự có thể được thực hiện ngược lại: bắt đầu từ “italic” đến “Example” (hướng ngược), thì điểm kết thúc (focus) của nó sẽ ở trước điểm bắt đầu (anchor):
7. Các sự kiện chọn
Có các sự kiện để theo dõi sự lựa chọn:
– elem.onselectstart
— khi sự lựa chọn bắt đầu cụ thể trên phần tử elem
(hoặc bên trong nó). Ví dụ, khi người dùng nhấn nút chuột trên đó và bắt đầu di chuyển con trỏ.
– Việc ngăn chặn hành động mặc định hủy bỏ sự bắt đầu lựa chọn. Do đó, việc bắt đầu một sự lựa chọn từ phần tử này trở nên không thể, nhưng phần tử vẫn có thể được chọn. Người dùng chỉ cần bắt đầu sự lựa chọn từ nơi khác.
– document.onselectionchange
— bất cứ khi nào sự lựa chọn thay đổi hoặc bắt đầu.
– Xin lưu ý: trình xử lý này chỉ có thể được đặt trên document
, nó theo dõi tất cả các sự lựa chọn trong đó.
Demo theo dõi sự lựa chọn
Đây là bản demo nhỏ. Nó theo dõi sự lựa chọn hiện tại trên document
và hiển thị các ranh giới của nó:
<p id="p">Select me: <i>italic</i> and <b>bold</b></p>
From <input id="from" disabled> – To <input id="to" disabled>
<script>
document.onselectionchange = function() {
let selection = document.getSelection();
let {anchorNode, anchorOffset, focusNode, focusOffset} = selection;
// anchorNode and focusNode are text nodes usually
from.value = `${anchorNode?.data}, offset ${anchorOffset}`;
to.value = `${focusNode?.data}, offset ${focusOffset}`;
};
</script>
Demo sao chép sự lựa chọn
Có hai cách để sao chép nội dung đã chọn:
1. Chúng ta có thể sử dụng document.getSelection().toString()
để lấy nội dung dưới dạng văn bản.
2. Hoặc, để sao chép toàn bộ DOM, ví dụ nếu chúng ta cần giữ định dạng, chúng ta có thể lấy các phạm vi cơ bản bằng getRangeAt(...)
. Một đối tượng Range
, lần lượt, có phương thức cloneContents()
sao chép nội dung của nó và trả về dưới dạng đối tượng DocumentFragment
, mà chúng ta có thể chèn vào nơi khác.
Đây là bản demo sao chép nội dung đã chọn cả dưới dạng văn bản và dưới dạng các nút DOM:
<p id="p">Select me: <i>italic</i> and <b>bold</b></p>
Cloned: <span id="cloned"></span>
<br>
As text: <span id="astext"></span>
<script>
document.onselectionchange = function() {
let selection = document.getSelection();
cloned.innerHTML = astext.innerHTML = "";
// Clone DOM nodes from ranges (we support multiselect here)
for (let i = 0; i < selection.rangeCount; i++) {
cloned.append(selection.getRangeAt(i).cloneContents());
}
// Get as text
astext.innerHTML += selection;
};
</script>
8. Các phương thức của Selection
Chúng ta có thể làm việc với sự lựa chọn bằng cách thêm/xóa các phạm vi:
– getRangeAt(i)
— lấy phạm vi thứ i, bắt đầu từ 0
. Trong tất cả các trình duyệt ngoại trừ Firefox, chỉ 0
được sử dụng.
– addRange(range)
— thêm range
vào sự lựa chọn. Tất cả các trình duyệt ngoại trừ Firefox đều bỏ qua lệnh gọi nếu sự lựa chọn đã có một phạm vi liên kết.
– removeRange(range)
— xóa range
khỏi sự lựa chọn.
– removeAllRanges()
— xóa tất cả các phạm vi.
– empty()
— tên gọi khác của removeAllRanges
.
Cũng có các phương thức tiện lợi để thao tác trực tiếp với phạm vi sự lựa chọn, mà không cần các lệnh gọi Range
trung gian:
– collapse(node, offset)
— thay thế phạm vi đã chọn bằng một phạm vi mới bắt đầu và kết thúc tại node
đã cho, ở vị trí offset
.
– setPosition(node, offset)
— tên gọi khác của collapse
.
– collapseToStart()
– thu gọn (thay thế bằng một phạm vi rỗng) về điểm bắt đầu của sự lựa chọn,
– collapseToEnd()
– thu gọn về điểm kết thúc của sự lựa chọn,
– extend(node, offset)
– di chuyển điểm focus của sự lựa chọn đến node
đã cho, vị trí offset
,
– setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)
– thay thế phạm vi sự lựa chọn bằng điểm bắt đầu anchorNode/anchorOffset
và điểm kết thúc focusNode/focusOffset
. Tất cả nội dung ở giữa chúng được chọn.
– selectAllChildren(node)
— chọn tất cả các nút con của node
.
– deleteFromDocument()
— xóa nội dung đã chọn khỏi tài liệu.
– containsNode(node, allowPartialContainment = false)
— kiểm tra xem sự lựa chọn có chứa node
không (một phần nếu đối số thứ hai là true
).
Đối với hầu hết các tác vụ, các phương thức này là đủ, không cần phải truy cập vào đối tượng Range
cơ bản.
Ví dụ, chọn toàn bộ nội dung của đoạn văn <p>
:
<p id="p">Select me: <i>italic</i> and <b>bold</b></p>
<script>
// select from 0th child of <p> to the last child
document.getSelection().setBaseAndExtent(p, 0, p, p.childNodes.length);
</script>
Làm điều tương tự bằng cách sử dụng phạm vi:
<p id="p">Select me: <i>italic</i> and <b>bold</b></p>
<script>
let range = new Range();
range.selectNodeContents(p); // or selectNode(p) to select the <p> tag too
document.getSelection().removeAllRanges(); // clear existing selection if any
document.getSelection().addRange(range);
</script>
Nếu sự lựa chọn tài liệu đã tồn tại, hãy xóa nó trước bằng removeAllRanges()
. Sau đó thêm các phạm vi mới. Nếu không, tất cả các trình duyệt ngoại trừ Firefox đều bỏ qua các phạm vi mới.
Sự ngoại lệ là một số phương thức sự lựa chọn, thay thế sự lựa chọn hiện có, chẳng hạn như setBaseAndExtent
.
9. Sự lựa chọn trong các phần tử của biểu mẫu
Các phần tử biểu mẫu, chẳng hạn như input
và textarea
, cung cấp API đặc biệt cho sự lựa chọn, không cần các đối tượng Selection
hoặc Range
. Vì giá trị đầu vào là văn bản thuần túy, không phải HTML, không cần các đối tượng như vậy, mọi thứ đơn giản hơn nhiều.
Các thuộc tính:
input.selectionStart
— vị trí bắt đầu của sự lựa chọn (có thể ghi),input.selectionEnd
— vị trí kết thúc của sự lựa chọn (có thể ghi),input.selectionDirection
— hướng của sự lựa chọn, một trong các giá trị: “forward”, “backward” hoặc “none” (nếu ví dụ, được chọn bằng cách nhấp chuột đúp),
Các sự kiện:input.onselect
— kích hoạt khi có gì đó được chọn.
Các phương thức:input.select()
— chọn tất cả nội dung trong điều khiển văn bản (có thể làtextarea
thay vìinput
),input.setSelectionRange(start, end, [direction])
— thay đổi sự lựa chọn để trải dài từ vị trístart
đếnend
, theo hướng đã cho (tùy chọn).-
input.setRangeText(replacement, [start], [end], [selectionMode])
— thay thế một phạm vi văn bản bằng văn bản mới.Các đối số tùy chọn
start
vàend
, nếu được cung cấp, thiết lập điểm bắt đầu và kết thúc của phạm vi, nếu không, sự lựa chọn của người dùng sẽ được sử dụng.
Đối số cuối cùng,selectionMode
, xác định cách sự lựa chọn sẽ được thiết lập sau khi văn bản đã được thay thế. Các giá trị có thể có là: -
"select"
— đoạn văn bản mới chèn sẽ được chọn. –"start"
— vùng lựa chọn sẽ thu gọn ngay trước văn bản mới chèn (con trỏ sẽ nằm ngay trước nó). –"end"
— vùng lựa chọn sẽ thu gọn ngay sau văn bản mới chèn (con trỏ sẽ nằm ngay sau nó). –"preserve"
— cố gắng giữ nguyên lựa chọn. Đây là giá trị mặc định.Bây giờ hãy cùng xem các phương thức này hoạt động như thế nào.
Ví dụ: theo dõi lựa chọn
Ví dụ, đoạn mã này sử dụng sự kiện onselect
để theo dõi lựa chọn:
<textarea id="area" style="width:80%;height:60px">
Selecting in this text updates values below.
</textarea>
<br>
From <input id="from" disabled> – To <input id="to" disabled>
<script>
area.onselect = function() {
from.value = area.selectionStart;
to.value = area.selectionEnd;
};
</script>
Xin lưu ý:
– onselect
kích hoạt khi có gì đó được chọn, nhưng không kích hoạt khi lựa chọn bị xóa.
– Sự kiện document.onselectionchange
không nên kích hoạt cho các lựa chọn bên trong một điều khiển biểu mẫu, theo spec, vì nó không liên quan đến việc lựa chọn và phạm vi của document
. Một số trình duyệt có thể phát sinh sự kiện này, nhưng chúng ta không nên dựa vào nó.
Ví dụ: di chuyển con trỏ
Chúng ta có thể thay đổi selectionStart
và selectionEnd
, điều này sẽ thiết lập lựa chọn.
Một trường hợp đặc biệt quan trọng là khi selectionStart
và selectionEnd
bằng nhau. Khi đó, đó chính xác là vị trí của con trỏ. Hoặc, để diễn đạt lại, khi không có gì được chọn, lựa chọn sẽ thu gọn tại vị trí của con trỏ.
Vì vậy, bằng cách đặt selectionStart
và selectionEnd
thành cùng một giá trị, chúng ta di chuyển con trỏ.
Ví dụ:
<textarea id="area" style="width:80%;height:60px">
Focus on me, the cursor will be at position 10.
</textarea>
<script>
area.onfocus = () => {
// zero delay setTimeout to run after browser "focus" action finishes
setTimeout(() => {
// we can set any selection
// if start=end, the cursor is exactly at that place
area.selectionStart = area.selectionEnd = 10;
});
};
</script>
Ví dụ: sửa đổi lựa chọn
Để sửa đổi nội dung của lựa chọn, chúng ta có thể sử dụng phương thức input.setRangeText()
. Tất nhiên, chúng ta có thể đọc selectionStart/End
và, với kiến thức về lựa chọn, thay đổi chuỗi con tương ứng của value
, nhưng setRangeText
mạnh mẽ hơn và thường tiện lợi hơn.
Đây là một phương thức khá phức tạp. Ở dạng đơn giản nhất với một đối số, nó sẽ thay thế vùng lựa chọn của người dùng và loại bỏ lựa chọn.
Ví dụ, ở đây lựa chọn của người dùng sẽ được bao quanh bởi *...*
:
run autorun
<input id="input" style="width:200px" value="Select here and click the button">
<button id="button">Wrap selection in stars *...*</button>
<script>
button.onclick = () => {
if (input.selectionStart == input.selectionEnd) {
return; // nothing is selected
}
let selected = input.value.slice(input.selectionStart, input.selectionEnd);
input.setRangeText(`*${selected}*`);
};
</script>
Với nhiều tham số hơn, chúng ta có thể thiết lập phạm vi start
và end
.
Trong ví dụ này, chúng ta tìm "THIS"
trong văn bản đầu vào, thay thế nó và giữ phần thay thế được chọn:
<input id="input" style="width:200px" value="Replace THIS in text">
<button id="button">Replace THIS</button>
<script>
button.onclick = () => {
let pos = input.value.indexOf("THIS");
if (pos >= 0) {
input.setRangeText("*THIS*", pos, pos + 4, "select");
input.focus(); // focus to make selection visible
}
};
</script>
Ví dụ: chèn tại vị trí con trỏ
Nếu không có gì được chọn, hoặc chúng ta sử dụng start
và end
bằng nhau trong setRangeText
, thì văn bản mới sẽ chỉ được chèn vào, không có gì bị xóa.
Chúng ta cũng có thể chèn một cái gì đó “tại vị trí con trỏ” bằng cách sử dụng setRangeText
.
Dưới đây là một nút chèn "HELLO"
tại vị trí con trỏ và đặt con trỏ ngay sau nó. Nếu vùng lựa chọn không trống, nó sẽ bị thay thế (chúng ta có thể phát hiện điều này bằng cách so sánh selectionStart!=selectionEnd
và thực hiện một hành động khác thay thế):
<input id="input" style="width:200px" value="Text Text Text Text Text">
<button id="button">Insert "HELLO" at cursor</button>
<script>
button.onclick = () => {
input.setRangeText("HELLO", input.selectionStart, input.selectionEnd, "end");
input.focus();
};
</script>
10. Làm cho không thể chọn
Để làm cho một phần tử không thể chọn, có ba cách:
1. Sử dụng thuộc tính CSS user-select: none
.
<style>
#elem {
user-select: none;
}
</style>
<div>Selectable <div id="elem">Unselectable</div> Selectable</div>
Điều này không cho phép bắt đầu lựa chọn tại elem
. Nhưng người dùng có thể bắt đầu lựa chọn ở nơi khác và bao gồm elem
vào đó.
Khi đó, elem
sẽ trở thành một phần của document.getSelection()
, vì vậy lựa chọn thực tế vẫn xảy ra, nhưng nội dung của nó thường bị bỏ qua khi sao chép-dán.
2.Ngăn chặn hành động mặc định trong các sự kiện onselectstart
hoặc mousedown
.
<div>Selectable <div id="elem">Unselectable</div> Selectable</div>
<script>
elem.onselectstart = () => false;
</script>
Điều này ngăn chặn việc bắt đầu lựa chọn trên elem
, nhưng người dùng có thể bắt đầu nó tại một phần tử khác, sau đó mở rộng đến elem
.
Điều này tiện lợi khi có một bộ xử lý sự kiện khác trên cùng một hành động kích hoạt lựa chọn (ví dụ: mousedown
). Vì vậy, chúng ta vô hiệu hóa lựa chọn để tránh xung đột, nhưng vẫn cho phép nội dung của elem
được sao chép.
3.Chúng ta cũng có thể xóa lựa chọn sau khi nó xảy ra bằng document.getSelection().empty()
. Cách này ít khi được sử dụng vì nó gây ra hiện tượng nhấp nháy không mong muốn khi lựa chọn xuất hiện rồi biến mất.
11. Tài liệu tham khảo
12. Tóm tắt
Chúng ta đã xem xét hai API khác nhau cho việc chọn lựa:
1. Đối với tài liệu: các đối tượng Selection
và Range
.
2. Đối với input
, textarea
: các phương pháp và thuộc tính bổ sung.
API thứ hai rất đơn giản, vì nó làm việc với văn bản.
Các công thức thường dùng nhất có thể là:
Dưới đây là đoạn mở bài cho chủ đề:
Lấy lựa chọn:
let selection = document.getSelection();
let cloned = /* element to clone the selected nodes to */;
// then apply Range methods to selection.getRangeAt(0)
// or, like here, to all ranges to support multi-select
for (let i = 0; i < selection.rangeCount; i++) {
cloned.append(selection.getRangeAt(i).cloneContents());
}
Đặt lựa chọn:
let selection = document.getSelection();
// directly:
selection.setBaseAndExtent(...from...to...);
// or we can create a range and:
selection.removeAllRanges();
selection.addRange(range);
Cuối cùng, về con trỏ. Vị trí của con trỏ trong các phần tử có thể chỉnh sửa, như , luôn nằm ở đầu hoặc cuối của lựa chọn. Chúng ta có thể sử dụng nó để lấy vị trí con trỏ hoặc để di chuyển con trỏ bằng cách thiết lập elem.selectionStart
và elem.selectionEnd
.
Chào mừng bạn đến với Cafedev! Trong bài viết này, chúng ta sẽ khám phá hai đối tượng quan trọng trong JavaScript là `Selection` và `Range`, từ những kiến thức cơ bản đến nâng cao. Những công cụ này cho phép bạn quản lý và thao tác với các vùng chọn trong tài liệu web một cách hiệu quả. Hãy cùng Cafedev tìm hiểu cách sử dụng chúng để nâng cao kỹ năng lập trình JavaScript của bạn!
Tham khảo thêm: MIỄN PHÍ 100% | Series tự học Javascrypt chi tiết, dễ hiểu từ cơ bản tới nâng cao + Full Bài Tập thực hành nâng cao trình dev
Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây
Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của CafeDev để nhận được nhiều hơn nữa:
Chào thân ái và quyết thắng!