JavaScript实现光标双向文本高亮:解决方向切换高亮失效问题
解决方案:实现鼠标双向移动时的文本高亮跟随
问题根源
当前代码的handleMouseMove函数始终以初始点击位置和当前鼠标位置的最小/最大索引作为选区范围,这导致只能单向扩展高亮,无法在鼠标反向移动时收缩选区。比如从索引A往右选到B后,再往左移到C(C<A),选区依然会保持A到B的范围,而不是C到A。
修改步骤
我们需要调整鼠标移动的处理逻辑,跟踪鼠标移动的基准锚点,动态更新选区范围,反向移动时自动收缩高亮:
- 新增全局变量存储初始点击的锚点索引,作为选区的固定基准
- 修改鼠标按下事件,记录初始锚点
- 重写鼠标移动事件,以锚点和当前位置动态计算选区范围
修改后的完整代码
HTML
<div id="trial_display"> <div id="content"> <p id="textParagraph" style="display: none"></p> </div> <div id="buttons"> <button id="removeHighlight">Clear</button> <button id="undoHighlight">Undo</button> </div> </div>
CSS
#buttons { margin-top: 30px; } .line-break { display: block; margin: 15px; } #trial_display { display: block; padding: 50px; } #title{ text-align: center; font-size: 20px; } #content { display: block; border: 2px solid gray; padding: 50px; }
JavaScript
// Initialize data structures and variables var wordDictionary = {}; // Dictionary to store words var selectedWords = {}; // Currently selected words var isMouseDown = false; // Flag for mouse state var currentColor = ''; // Current highlight color var usedColors = new Set(); // Set of used colors // Available highlight colors var availableColors = ["yellow", "red", "blue", "green", "orange"]; var highlights = {}; // Store highlighted words for each event var eventCounter = 0; // Counter for events var anchorIndex = null; // 记录鼠标按下的初始锚点索引 var text = "Pangolins, sometimes known as scaly anteaters, are mammals of the order Pholidota. \n The one extant family, the Manidae, has three genera: Manis, Phataginus, and Smutsia. \n Manis comprises four species found in Asia, while Phataginus and Smutsia include two species each, all found in sub-Saharan Africa. \n These species range in size from 30 to 100 cm (12 to 39 in). \n A number of extinct pangolin species are also known. \n In September 2023, nine species were reported.<br><br> \n Pangolins have large, protective keratin scales, similar in material to fingernails and toenails, covering their skin; \n they are the only known mammals with this feature. \n They live in hollow trees or burrows, depending on the species. \n Pangolins are nocturnal, and their diet consists of mainly ants and termites, which they capture using their long tongues. \n They tend to be solitary animals, meeting only to mate and produce a litter of one to three offspring, which they raise for about two years."; var textParagraph = document.getElementById("textParagraph"); textParagraph.innerHTML = text; // Execute when the window is loaded window.onload = function () { var contentDiv = document.getElementById('content'); let ptag = contentDiv.querySelector('p'); var text = ptag.innerHTML.trim(); var words = text.split(/\s+|(?=<br><br>)/); // Populate wordDictionary with words and create span elements for each word for (var i = 0; i < words.length; i++) { wordDictionary[i] = words[i]; } for (var i = 0; i < words.length; i++) { var wordElement = document.createElement('span'); wordElement.textContent = words[i] + ' '; wordElement.dataset.index = i; wordElement.addEventListener('mousedown', handleMouseDown); if (words[i] == '<br><br>') { wordElement.textContent = ' '; wordElement.classList.add('line-break') } contentDiv.appendChild(wordElement); } // Add mouseup event listener for handling mouse up events document.addEventListener('mouseup', handleMouseUp); }; // Function to get a random highlight color function getRandomColor() { if (availableColors.length === 0) { availableColors = [...usedColors]; usedColors.clear(); } var randomIndex = Math.floor(Math.random() * availableColors.length); var color = availableColors.splice(randomIndex, 1)[0]; usedColors.add(color); return color; } // Function to handle mouse down event on words function handleMouseDown(event) { isMouseDown = true; var index = parseInt(event.target.dataset.index); var word = event.target.textContent.trim(); if (!isHighlighted(index)) { selectedWords = {}; anchorIndex = index; // 存储初始锚点 selectedWords.startIndex = index; selectedWords.endIndex = index; selectedWords[index] = word; currentColor = getRandomColor(); event.target.style.backgroundColor = currentColor; event.preventDefault(); document.addEventListener('mousemove', handleMouseMove); } } // Function to handle mouse up event function handleMouseUp(event) { if (isMouseDown) { document.removeEventListener('mousemove', handleMouseMove); var highlightedWords = {}; for (var index in selectedWords) { if (index !== 'startIndex' && index !== 'endIndex') { highlightedWords[index] = selectedWords[index]; } } eventCounter++; highlights[eventCounter] = highlightedWords; } isMouseDown = false; anchorIndex = null; // 重置锚点 } // Function to handle mouse move event (word selection) function handleMouseMove(event) { if (isMouseDown && anchorIndex !== null) { var currentIndex = parseInt(event.target.dataset.index); if (isNaN(currentIndex)) return; // 忽略非单词元素 var contentDiv = document.getElementById('content'); clearPreviousSelection(); // 根据锚点和当前位置确定选区范围 var newStartIndex = Math.min(anchorIndex, currentIndex); var newEndIndex = Math.max(anchorIndex, currentIndex); // 更新选中的单词并高亮 for (var i = newStartIndex; i <= newEndIndex; i++) { selectedWords[i] = wordDictionary[i]; contentDiv.children[i].style.backgroundColor = currentColor; } selectedWords.startIndex = newStartIndex; selectedWords.endIndex = newEndIndex; } } // Function to clear previously selected words function clearPreviousSelection() { var contentDiv = document.getElementById('content'); for (var i in selectedWords) { if (i !== 'startIndex' && i !== 'endIndex') { contentDiv.children[i].style.backgroundColor = ''; delete selectedWords[i]; } } } // Function to check if a word is already highlighted function isHighlighted(index) { for (var eventKey in highlights) { var highlightedWords = highlights[eventKey]; for (var wordIndex in highlightedWords) { if (parseInt(wordIndex) === parseInt(index)) { return true; } } } return false; } // Function to clear all selections and reset function clearSelections() { var contentDiv = document.getElementById('content'); var wordElements = contentDiv.getElementsByTagName('span'); for (var i = 0; i < wordElements.length; i++) { wordElements[i].style.backgroundColor = ''; } highlights = {}; eventCounter = 0; } // Function to undo the last selection function undoSelection() { if (eventCounter > 0) { var lastHighlight = highlights[eventCounter]; for (var index in lastHighlight) { var wordIndex = parseInt(index); var contentDiv = document.getElementById('content'); if (!isNaN(wordIndex)) { contentDiv.children[wordIndex].style.backgroundColor = ''; } } delete highlights[eventCounter]; eventCounter--; } } // Add event listeners to the clear and undo buttons document.getElementById("removeHighlight").addEventListener("click", clearSelections); document.getElementById("undoHighlight").addEventListener("click", undoSelection);
关键修改说明
- 锚点索引
anchorIndex:固定初始点击位置作为选区基准,确保鼠标移动时始终以此为参照计算范围 - 鼠标移动逻辑优化:每次移动时清空旧选区,以锚点和当前位置重新计算高亮范围,反向移动时自动收缩选区
- 类型匹配修复:将字符串索引转为数字后再比较,避免索引匹配错误
修改后,鼠标按下后无论向左还是向右移动,高亮选区都会准确跟随光标方向变化,反向移动时自动移除超出当前选区的高亮。
内容的提问来源于stack exchange,提问作者psychcoder
相关产品推荐
相关产品推荐

