سنتناول في هذا المقال التحديد داخل المستند، وكذلك التحديد داخل حقول الاستمارات، مثل <input>
.
تستطيع جافاسكربت الوصول إلى تحديد موجود، أو تحديد عقد من DOM وإزالة التحديد عنها سواء كليّا أو جزئيّا، بالإضافة إلى حذف المحتوى المحدّد من المستند، أو وضعه داخل وسم، إلى غير ذلك.
ستجد بعض الوصفات لمهامّ شائعة في نهاية المقال، في قسم "الملخّص". قد تلبّي تلك الوصفات احتياجاتك الحاليّة، لكنّك ستستفيد أكثر بكثير إذا قرأت النصّ كلّه إذ ستتعرف على الكائنين الأساسيين Range
وSelection
، ولن تحتاج بفهمها أي مقال آخر لاستعمالهما فيما تريد.
المدى
يُعدّ المدى هو المفهوم اﻷساسيّ في موضوع التحديد، وهو عبارة عن زوج من "النقط الحدّية": بداية المدى ونهاية المدى.
يُنشأ كائن Range
دون وسائط:
let range = new Range();
يمكننا بعدها ضبط حدود التحديد باستخدام range.setStart(node, offset)
وrange.setEnd(node, offset)
.
يمكن للوسيط الأوّل node
أن يكون إمّا عقدة نصيّة أو عقدة عنصريّة، ويكون معنى الوسيط الثاني معتمدا على ذلك:
-
إذا كان
node
عقدة نصيّة، فلابدّ أن يكونoffset
هو الموضع داخل النصّ. -
إذا كان
node
عقدة عنصريّة، فلابدّ أن يكونoffset
هو رقم الابن.
على سبيل المثال، لننشئ مدى في هذا المقطع
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
هذه هي بنية DOM الخاصّة به:
لننشئ مدى من أجل "Example: <i>italic</i>"
. كما يمكن أن نلاحظ، تتشكّل هذه الجملة من الابنين الأوّل والثاني لـ <p>
:
-
لنقطة البداية العقدة الأب
<p>
، والانحراف offset هو0
. -
لنقطة النهاية العقدة الأب
<p>
أيضا، لكن لها الانحراف2
(تضبط المدى إلى غايةoffset
، لكن دونه).
لو أجرينا الشيفرة أدناه، يمكن أن نرى بأنّ النصّ يُحدّد.
<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 تعيد console.log(range); // Example: italic // لنطبّق هذا المدى على تحديد المستند (سيُوضَّح هذا لاحقا) document.getSelection().addRange(range); </script>
هذه منصّة اختبار أكثر مرونة حيث يمكنك تجربة المزيد من التحديدات المختلفة:
<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); // طبّق التحديد، سيُوضَّح لاحقا document.getSelection().removeAllRanges(); document.getSelection().addRange(range); }; </script>
على سبيل المثال، يعطي التحديد من 1 إلى 4 المدى التالي:
لا يجب علينا استخدام العقدة نفسها في setStart
وsetEnd
فقد يمتدّ المدى عبر العديد من العقد غير المترابطة. ما يهمّ فقط هو أن تكون النهاية موجودة بعد البداية.
تحديد أجزاء من العقد النصية
ذلك ممكن أيضا، نحتاج فقط إلى ضبط البداية والنهاية على شكل انحراف نسبيّ في العقد النصّيّة.
نحتاج إلى أن ننشئ مدى بحيث:
-
يبدأ من الموضع 2 في الابن اﻷوّل لـ
<p>
(آخذًا جميع الحروف ما عدا الحرفين اﻷوّلين لـ "Example: "). -
ينتهي عند الموضع 3 في الابن اﻷوّل لـ
<b>
(آخذا الحروف الثلاثة اﻷولى لكلمة "bold"، لا أكثر):
<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 // استخدم هذا المدى للتحديد (سيُوضَّح هذا لاحقا) window.getSelection().addRange(range); </script>
لكائن المدى الخاصّيّات التالية:
-
startContainer
وstartOffset
-- العقدة والانحراف عن البداية.-
في المثال أعلاه: العقدة النصّيّة اﻷولى داخل
<p>
و2
.
-
في المثال أعلاه: العقدة النصّيّة اﻷولى داخل
-
endContainer
وendOffset
-- العقدة والانحراف عن النهاية.-
في المثال أعلاه: العقدة النصّيّة اﻷولى داخل
<b>
و3
.
-
في المثال أعلاه: العقدة النصّيّة اﻷولى داخل
-
collapsed
-- قيمة بوليانية، تكونtrue
عندما يبدأ المدى وينتهي في النقطة نفسها (وبذلك لا يوجد محتوى داخل المدى)،-
في المثال أعلاه:
false
-
في المثال أعلاه:
-
commonAncestorContainer
-- أقرب سلف مشترك بين جميع العقد داخل المدى،-
في المثال أعلاه:
<p>
-
في المثال أعلاه:
توابع المدى
هناك العديد من التوابع الملائمة للتحكّم في اﻷمداء سنعرضها كلها، ونبدأ بتلك التي تضبط بداية المدى:
-
setStart(node, offset)
- يضع البداية عند موضع الانحرافoffset
في العقدةnode
-
setStartBefore(node)
- يضع البداية قبلnode
مباشرة -
setStartAfter(node)
- يضع البداية بعدnode
مباشرة
ضبط نهاية المدى (توابع مماثلة):
-
setEnd(node, offset)
- يضع النهاية عند موضع الانحرافoffset
في العقدةnode
-
setEndBefore(node)
- يضع النهاية قبلnode
مباشرة -
setEndAfter(node)
- يضع النهاية بعدnode
مباشرة
كما سبق بيانه، يمكن أن تكون node
عقدة نصّيّة أو عنصريّة، بالنسبة للعقد العنصريّة تتخطّى offset
ذلك العدد من الحروف، بينما للعقد العنصريّة تتخطّى ذلك العدد من العقد اﻷبناء.
توابع أخرى:
-
selectNode(node)
- يضبط المدى بحيث تُحدَّد كامل العقدةnode
-
selectNodeContents(node)
- يضبط المدى بحيث تُحدَّد جميع محتويات العقدةnode
-
collapse(toStart)
إذا كانتtoStart=true
- يضع end=start، وإلّا يضع start=end، فينطوي بذلك المدى. -
cloneRange()
- ينشئ مدى جديدا بنفس البداية/النهاية
للتحكّم في المحتوى الموجود ضمن المدى:
-
deleteContents()
- يزيل محتوى المدى من المستند -
extractContents()
- يزيل محتوى المدى من المستند ويعيده على شكل قطعة مستند DocumentFragment -
cloneContents()
- يستنسخ محتوى المستند ويعيده على شكل قطعة مستند DocumentFragment -
insertNode(node)
- يدرج العقدةnode
في المستند عند بداية المدى -
surroundContents(node)
- يحيط العقدة بمحتوى المدى. لكي يعمل هذا، يجب أن يحتوي المدى على وسوم فتح وإغلاق لجميع العناصر التي في داخله: لا وجود ﻷمداء جزئيّة مثل<i>abc
.
بهذه التوابع يمكننا فعل أيّ شيء بالعقد المُحدّدة.
هذه منصّة الاختبار لرؤيتها كيف تعمل:
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(); // جميع التوابع المعروضة ممثّلة هنا 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>
توجد أيضا توابع للمقارنة بين اﻷمداء. لكن يندر استخدامها. إن احتجت لها، يُرجى الرجوع إلى المواصفة أو دليل MDN.
التحديد
Range
هو كائن عامّ لإدارة أمداء التحديد. يمكننا إنشاء مثل هذه الكائنات، وتمريرها إلى هنا وهناك، لكنّها لا تُحدِّد ظاهريّا أيّ شيء بمفردها.
يُمثَّل تحديد المستند بواسطة الكائن Selection
، الذي يمكن الحصول عليه من window.getSelection()
أو document.getSelection()
. قد يتضمّن التحديد صفرا فأكثر من اﻷمداء. على الأقلّ، كما تنصّ على ذلك مواصفة الواجهة البرمجيّة للتحديد. لكن عمليّا، فايرفوكس فقط هو الذي يمكّن من تحديد أمداء متعدّدة في المستند باستخدام المفاتيح Ctrl+click
(Cmd+click
بالنسبة لـ Mac).
هذه لقطة شاشة فيها تحديد بثلاثة أمداء، تمّ عملها في فايرفوكس:
تدعم المتصفّحات اﻷخرى تحديد مدى واحدا على اﻷكثر. كما سنرى، تلمّح بعض توابع Selection
أنّه من الممكن أن يكون هناك عدّة أمداء، لكن ثانية، في جميع المتصفّحات ما عدا فايرفوكس، هناك واحد فقط على اﻷكثر.
خاصيات التحديد
بشكل مشابه للمدى، يملك التحديد بداية، تُسمّى "المرساة (anchor)"، ونهاية تُسمّى "التركيز (focus)".
خاصّيّات التحديد الرئيسيّة هي:
-
anchorNode
-- العقدة التي يبدأ فيها التحديد -
anchorOffset
-- الانحراف الذي يبدأ منه التحديد فيanchorNode
-
focusNode
-- العقدة التي ينتهي فيها التحديد -
focusOffset
-- الانحراف الذي ينتهي عنده التحديد فيfocusNode
-
isCollapsed
--true
إذا لم يكن التحديد يحدّد أيّ شيء (مدى فارغ)، أو لم يكن موجودا أصلا -
rangeCount
-- عدد اﻷمداء في التحديد،1
على اﻷكثر في جميع المتصفّحات باستثناء فايرفوكس
ملاحظة: قد تكون نهاية التحديد في المستند قبل البداية
هناك عدة طرق لتحديد المحتوى، حسب وكيل المستخدم (user agent): الفأرة والمفاتيح المختصرة والضغط على الهاتف، إلى غير ذلك. يمكّن بعضها، مثل الفأرة، من عمل نفس التحديد في الاتجاهين: "من اليمين إلى اليسار" و"من اليسار إلى اليمين".
إذا كانت البداية (المرساة) تأتي في المستند قبل النهاية (التركيز)، فيقال أنّ اتجاه هذا التحديد "إلى اﻷمام".
إذا بدأ المستخدم مثلا التحديد بالفأرة وذهب من "Example" إلى "italic":
على خلاف ذلك، إذا ذهب من نهاية "italic" إلى "Example"، فإنّ التحديد متّجه "إلى الخلف"، ويكون التركيز فيه قبل المرساة:
هذا مختلف عن كائنات Range
التي تتجه دائما إلى اﻷمام: لا يمكن أن تكون بداية المدى قبل نهايته.
أحداث التحديد
هناك أحداث تمكّننا من متابعة التحديد:
-
elem.onselectstart
-- عندما يبدأ التحديد فيelem
، كأن يبدأ المستخدم مثلا بتحريك الفأرة والزر مضغوط.- يؤدي منع الفعل الافتراضيّ إلى عدم ابتداء التحديد.
-
document.onselectionchange
-- كلّما تغيّر التحديد.-
يُرجى التنبّه: يمكن أن يُسند هذا المعالج إلى
document
فقط.
-
يُرجى التنبّه: يمكن أن يُسند هذا المعالج إلى
مثال لتتبع التحديد
إليك مثالا صغيرا يظهر حدود التحديد ديناميكيّا حال تغيّرها:
<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 {anchorNode, anchorOffset, focusNode, focusOffset} = document.getSelection(); from.value = `${anchorNode && anchorNode.data}:${anchorOffset}`; to.value = `${focusNode && focusNode.data}:${focusOffset}`; }; </script>
مثال للحصول على التحديد
للحصول على كامل التحديد:
-
على شكل نص: استدعي فقط
document.getSelection().toString()
. -
على شكل عقد DOM: احصل على الأمداء المنشأة واستدعي توابع
cloneContents()
الخاصّة بها (المدى الأوّل فقط إذا لم نكن ندعم التحديد المتعدّد لفايرفوكس).
هذا مثال للحصول على التحديد سواء على شكل نصّ أو عقد 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 = ""; // من اﻷمداء (ندعم التحديد المتعدّد هنا) DOM استنسخ عقد for (let i = 0; i < selection.rangeCount; i++) { cloned.append(selection.getRangeAt(i).cloneContents()); } // على شكل نصّ astext.innerHTML += selection; }; </script>
توابع التحديد
توابع التحديد لإضافة/إزالة اﻷمداء:
-
getRangeAt(i)
-- يحصل على المدى رقم i، ابتداءًا من0
. في جميع المتصفّحات ما عدا فايرفوكس، لا يُستعمل سوى0
. -
addRange(range)
-- يضيف المدىrange
إلى التحديد. تتجاهل جميع المتصفّحات ما عدا فايرفوكس هذا الاستدعاء إذا كان للتحديد مدى مرفقًا به -
removeRange(range)
-- يزيل المدىrange
من التحديد. -
removeAllRanges()
-- يزيل جميع الأمداء. -
empty()
-- اسم بديل لـremoveAllRanges
.
زيادة على ذلك، هناك توابع ملائمة للتحكّم في المدى الخاصّ بالتحديد مباشرة، دون Range
:
-
collapse(node, offset)
- استبدل المدى المُحدَّد بواحد جديد يبدأ وينتهي عند العقدة المعطاة،node
، وفي الموضعoffset
. -
setPosition(node, offset)
-- اسم بديل لـcollapse
. -
collapseToStart()
-- يطوي (يستبدل بمدى فارغ) إلى بداية التحديد، -
collapseToEnd()
-- يطوي إلى نهاية التحديد، -
extend(node, offset)
-- ينقل تركيز التحديد إلى العقدة المعطاةnode
، والموضعoffset
، -
setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)
-- يستبدل مدى التحديد بالبداية المعطاةanchorNode/anchorOffset
والنهايةfocusNode/focusOffset
. يُحدّد جميع المحتوى الذي بينها. -
selectAllChildren(node)
-- يحدّد جميع أبناء العقدةnode
. -
deleteFromDocument()
-- يزيل المحتوى المُحدَّد من المستند. -
containsNode(node, allowPartialContainment = false)
-- يفحص ما إذا كان التحديد يحتوي على العقدةnode
(جزئيّا إذا كان الوسيط الثانيtrue
).
وبذلك، يمكننا في كثير من المهامّ استدعاء توابع Selection
، ولا حاجة للوصول إلى كائن Range
المنشأ.
على سبيل المثال، تحديد كامل محتويات الفقرة <p>
:
<p id="p">Select me: <i>italic</i> and <b>bold</b></p> <script> // إلى آخر ابن <p> يحدّد من الابن رقم 0 لـ document.getSelection().setBaseAndExtent(p, 0, p, p.childNodes.length); </script>
نفس الشيء باستخدام اﻷمداء:
<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>
ملاحظة: للقيام بالتحديد، أزل التحديد الموجود أوّلا
إذا كان التحديد موجودًا، أفرغه أوّلا باستخدام removeAllRanges()
. ثمّ أضف اﻷمداء. وإلّا، ستتجاهل جميع المتصفّحات ما عدا فايرفوكس الأمداء الجديدة.
يُستثنى من ذلك بعض توابع التحديد، التي تستبدل التحديد الموجود، مثل setBaseAndExtent
.
التحديد في عناصر التحكم بالاستمارات
توفّر عناصر الاستمارات، مثل input
وtextarea
واجهات برمجيّة خاصّة بالتحديد، دون الكائنات Selection
أو Range
. بما أنّ قيمة المُدخل هي نصّ صرف، وليس HTML، فلا حاجة هناك لهذه الكائنات، كلّ شيء أبسط بكثير.
الخاصّيّات:
-
input.selectionStart
-- موضع بداية التحديد (قابل للكتابة) -
input.selectionEnd
-- موضع نهاية التحديد (قابل للكتابة) -
input.selectionDirection
-- اتجاه التحديد، واحد من: "forward" (اﻷمام) أو "backward" (الخلف) أو "none" (إذا كان التحديد بواسطة النقر المزدوج بالفأرة مثلا)
اﻷحداث:
-
input.onselect
-- يقع عندما يُحدّد شيء ما
التوابع:
-
input.select()
-- يحدّد كلّ شيء في عنصر التحكّم بالنصّ (قد يكونtextarea
بدلinput
) -
input.setSelectionRange(start, end, [direction])
-- يغيّر التحديد ليمتدّ من الموضعstart
إلىend
، في الاتجاه المُعطى (اختياريّ) -
input.setRangeText(replacement, [start], [end], [selectionMode])
-- يستبدل المدى من النصّ بنصّ جديد
تضبط الوسائطُ الاختياريّة start
وend
، إذا أُعطيت، بداية المدى ونهايته، وإلّا فيُعتمد تحديد المستخدم.
يوضّح الوسيط اﻷخير، selectionMode
، كيفيّة ضبط التحديد بعد استبدال النصّ. القيم الممكنة هي:
-
"select"
-- يُحدَّد النصّ المُدرج الجديد. -
"start"
-- ينطوي مدى التحديد قبل النصّ المُدرج مباشرة (يكون المؤشّر قبله مباشرة). -
"end"
-- ينطوي مدى التحديد بعد النصّ المُدرج مباشرة (يكون المؤشّر بعده مباشرة). -
"preserve"
-- يحاول المحافظة على التحديد. هذه هي القيمة الافتراضيّة.
لنرى الآن هذه التوابع تعمل.
مثال: تتبع التحديد
على سبيل المثال، تستخدم هذه الشيفرة حدث onselect
لتتبّع التحديد:
<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>
يُرجى التنبّه:
-
يقع
onselect
عندما يُحدَّد شيء ما، لكن ليس عندما يُزال التحديد. -
يجب ألّا يقع
document.onselectionchange
من أجل التحديدات التي بداخل عناصر التحكّم في الاستمارة، وفقا للمواصفة، إذ ليست له علاقة بـتحديد وأمداء المستندdocument
. تقوم بعض المتصفّحات بتوليده، لكن ينبغي ألّا نعتمد عليه.
مثال: تحريك المؤشر
يمكننا تغيير selectionStart
وselectionEnd
، التي تضبط التحديد.
من الحالات الشاذّة المهمّة هي عندما تكون selectionStart
وselectionEnd
متساويتان، ويكون ذلك هو موضع المؤشّر بالضبط. أو، بعبارة أخرى، عندما لا يُحدَّد أيّ شيء، فإن التحديد ينطوي عند موضع المؤشّر. فبإعطاء selectionStart
وselectionEnd
نفس القيمة، فإنّنا نعمل على تحريك المؤشّر.
على سبيل المثال:
<textarea id="area" style="width:80%;height:60px"> Focus on me, the cursor will be at position 10. </textarea> <script> area.onfocus = () => { // بتأخير منعدم للتنفيذ بعد انتهاء فعل “التركيز” من المتصفّح setTimeout setTimeout(() => { // يمكننا ضبط أيّ تحديد // إذا كانت البداية=النهاية، يكون المؤشّر في ذلك الموضع بالذات area.selectionStart = area.selectionEnd = 10; }); }; </script>
مثال: تعديل التحديد
لتعديل محتوى التحديد، يمكننا استخدام التابع input.setRangeText()
. بالطبع، يمكننا قراءة selectionStart/End
و، مع معرفتنا بالتحديد، تغيير السلسلة النصّيّة الجزئيّة الموافقة لـ value
، لكنّ setRangeText
أقوى و أكثر ملائمة في الكثير من اﻷحيان.
هو تابع معقّد بعض الشيء. يقوم في صورته البسيطة ذات وسيط وحيد باستبدال المدى المُحدَّد من طرف المستخدم ويزيل التحديد.
على سبيل المثال، سُيحاط تحديد المستخدم هنا بـ *...*
:
<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>
بالمزيد من الوسائط، يمكننا ضبط بداية المدى start
ونهايته end
.
في هذا المثال نجد أنّ "THIS"
في نصّ المُدخل، نستبدله ونبقي البديل مُحدّدا:
<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>
مثال: الإدراج عند المؤشر
إذا لم يكن هناك شيء مُحدّد، أو استخدمنا start
وend
متساويتان في setRangeText
، فسيُدرج النصّ الجديد فقط، ولن يزال أيّ شيء، ويمكننا أيضا إدراج شيء ما "عند المؤشّر" باستخدام setRangeText
.
هذا زرّ يعمل على إدراج "HELLO"
عند موضع المؤشّر ويضع المؤشّر بعده مباشرة. إذا لم يكن التحديد فارغا، فإنّه يُستبدل (يمكننا اكتشاف ذلك بالمقارنة selectionStart!=selectionEnd
وعمل شيء آخر بدل ذلك):
<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>
جعل الشيء غير قابل للتحديد
لجعل شيء ما غير قابل للتحديد، هناك ثلاث طرق:
الطريقة الأولى
باستخدام الخاصّيّة user-select: none
في CSS
<style> #elem { user-select: none; } </style> <div>Selectable <div id="elem">Unselectable</div> Selectable</div>
لا يسمح هذا للتحديد بالبدء عند elem
لكن قد يبدأ المستخدم التحديد من مكان آخر ويدخل فيه elem
.
وبذلك يصير elem
جزءًا من document.getSelection()
، ويكون التحديد قد وقع فعلًا، إلّا أنّ مُحتواه يُتجاهل عادة عند النسخ-اللصق.
الطريقة الثانية
بمنع الفعل الافتراضيّ عند اﻷحداث onselectstart
أو mousedown
:
<div>Selectable <div id="elem">Unselectable</div> Selectable</div> <script> elem.onselectstart = () => false; </script>
يمنع هذا بدأ التحديد من elem
، لكن الزائر قد يبدأ من عنصر آخر، ثمّ يمدّه إلى elem
.
هذا ملائم عندما يكون هناك معالج حدث آخر لنفس الفعل الذي يُحدث التحديد (mousedown
مثلا). فيمكننا إذًا تعطيل التحديد لتجنّب التعارض، مع السماح لمحتويات elem
أن تُنسخ.
الطريقة الثالثة
يمكننا أيضا مسح التحديد بعد حصوله باستخدام document.getSelection().empty()
. يندر استخدام هذا، إذ يتسبّب في ومضات غير محبّذة عند ظهور-اختفاء التحديد.
الملخص
تناولنا واجهتين برمجيّتين للتحديدات:
-
للصفحة: الكائنان
Selection
وRange
. -
للمدخلات النصية
input
وtextarea
: توابع وخاصّيّات إضافيّة.
الواجهة البرمجيّة الثانية بسيطة للغاية، إذ تعمل مع النصّ.
قد تكون أكثر الوصفات استخداما هي:
- الحصول على التحديد:
let selection = document.getSelection(); let cloned = /* العنصر الذي تُستنسخ فيه العقد المُحدَّدة*/; // selection.getRangeAt(0) على Range ثمّ طبّق توابع // أو، كما هنا، على جميع اﻷمداء لدعم التحديد المتعدّد for (let i = 0; i < selection.rangeCount; i++) { cloned.append(selection.getRangeAt(i).cloneContents()); }
- ضبط التحديد
let selection = document.getSelection(); // :مباشرة selection.setBaseAndExtent(...from...to...); // أو يمكننا إنشاء مدى و: selection.removeAllRanges(); selection.addRange(range);
وفي النهاية، بالنسبة للمؤشّر. يكون موضع المؤشّر في العناصر القابلة للتحرير، مثل <textarea>
، دائما عند بداية التحديد أو نهايته. يمكننا استخدام ذلك للحصول على موضع المؤشّر أو لتحريك المؤشّر بضبط elem.selectionStart
وelem.selectionEnd
.
المراجع
- مواصفة DOM: المدى
- الواجهة البرمجيّة للتحديد
- مواصفة HTML: الواجهات البرمجيّة لتحديدات عناصر التحكّم في النصّ
ترجمة -وبتصرف- للمقال Selection and Range من سلسلة Browser: Document, Events, Interfaces لصاحبها Ilya Kantor
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.