У меня есть элемент div с несколькими промежутками, каждый из которых содержит одно слово. Пользователь может нажать/щелкнуть слово, чтобы выбрать его, но я также пытаюсь реализовать ввод с клавиатуры.
Я хотел бы выделить каждую букву в любых совпадающих словах по мере ввода пользователем.
Например, предположим, что у нас есть эти пять слов:
банан кот малыш мяч конфеты
Если пользователь вводит «ba», я бы хотел выделить первые две буквы в «banana», «baby» и «ball» (тогда, если он наберет «n», буквы в «baby» не будут выделены). " и "мяч" и выделял только первые три буквы слова "банан")
Все эти слова динамически генерируются из массива списка слов, поэтому я надеюсь, что найдется способ добиться такого выделения без необходимости заключать каждую букву в интервал.
Возможно ли это?
Этот вопрос похож на: JavaScript найти строку и выделить. Если вы считаете, что это другое, отредактируйте вопрос, поясните, чем он отличается и/или как ответы на этот вопрос не помогают решить вашу проблему.
Вам не обязательно обертывать каждую букву заранее, но да, вам придется обернуть каждую последовательность букв, которую вы хотите выделить.
Чтобы предотвратить добавление элемента span:
Вы можете использовать стиль CSS линейного градиента, чтобы выделить определенную последовательность букв. Измените приведенный ниже код в соответствии с вашими потребностями, поскольку вам может потребоваться выделить несколько вхождений букв в элементе span.
/* index.css */
/* this may be applied if you use <p> other than <span> to wrap your text
p {
display: inline-block;
}
/* update: to ensure each letter has same width */
span {
font-family: monospace;
}
<input type = "text" id = "searchInput" placeholder = "Type to highlight">
<div id = "wordContainer">
<span>banana</span>
<span>cat</span>
<span>baby</span>
<span>ball</span>
<span>candy</span>
</div>
<script>
function highlightWords() {
const searchInput = document.getElementById("searchInput").value.toLowerCase();
const spans = document.querySelectorAll("#word-container span");
spans.forEach((span) => {
const word = span.textContent.toLowerCase();
const wordLength = word.length;
const inputLength = searchInput.length;
// Find the start and end index of the search input within the word
const startIndex = word.indexOf(searchInput);
const endIndex = startIndex + inputLength;
if (startIndex !== -1 && searchInput.length > 0) {
const startPercentage = (startIndex / wordLength) * 100;
const endPercentage = (endIndex / wordLength) * 100;
// Apply the gradient background
span.style.background = `linear-gradient(
to right,
white 0%,
white ${startPercentage}%,
yellow ${startPercentage}%,
yellow ${endPercentage}%,
white ${endPercentage}%,
white 100%
)`;
} else {
span.style.background = "none"; // Reset background if no match
}
});
}
</script>
Это решение предполагает, что все буквы имеют одинаковую ширину.
О, @HeikoTheißen, спасибо, что указали на это. Возможно, мы можем добавить «font-family: monospace» к элементу span.
Вы можете получить текст и ввод, а затем разделить текст на слова, используя Split() , затем перебрать этот массив и проверить, имеет ли значение BeginsWith() любую строку, которую ввод передает во время входного eventListener. Поместив логику в метод карты, вы можете переформатировать искомую строку и объединить оставшуюся часть слова, используя substring() или splice() . Присоединяйтесь к сопоставленному массиву, а затем присвойте элементам innerHTML вновь отформатированную строку с помощью highlight
класса.
Дополнительные комментарии см. во фрагменте кода.
// define variables for the elements in HTML
const par = document.querySelector('#text');
const input = document.querySelector('#matchText');
// callback passing in the event
const searchtext = e => {
// define the input value toLowerCase
const searchText = e.target.value.toLowerCase();
// get the text content of the element you wish to search through
const textCont = par.textContent;
// split the text into words using a whitespace
const words = textCont.split(' ');
// iterate over the words with map and define word
const highlightedText = words.map(word => {
// check and see if the word startWith the searchText string
if (word.toLowerCase().startsWith(searchText) && searchText !== '') {
// return the formatted string of the word
// with the letters highlighted using substring
return `<span class = "highlight">${word.substring(0, searchText.length)}</span><span>${word.substring(searchText.length, word.length)}</span>`;
}
// return the non-matching words in teh original string
return word;
// create a string with the array values using .join()
}).join(' ');
// set the innerHTML to the new string containing the highlighted string
par.innerHTML = highlightedText;
}
// eventListener using an input event, pass the callback
input.addEventListener('input', searchtext);
.highlight {
background-color: yellow;
}
<p id = "text">
<span>Banana</span>
<span>ball</span>
<span>ballistic</span>
<span>cat</span>
<span>chat</span>
<span>change</span>
<span>chap</span>
<span>character</span>
<span>color</span>
<span>people</span>
<span>peel</span>
<span>peon</span>
</p>
<input type = "text" id = "matchText" name = "matchingText">
Может быть, вам нужно следующее? Он также будет выделять буквы или группы букв в середине слов:
// define variables for the elements in HTML
const words = document.querySelectorAll('#text>span');
const input = document.getElementById('matchText');
input.addEventListener("input",()=>{
const rx=new RegExp(input.value,"ig");
words.forEach(w=>w.innerHTML=w.textContent.replace(rx,`<span class = "highlight">$&</span>`));
});
.highlight {
background-color: yellow;
}
<p id = "text">
<span>Banana</span>
<span>ball</span>
<span>ballistic</span>
<span>cat</span>
<span>chat</span>
<span>change</span>
<span>chap</span>
<span>character</span>
<span>color</span>
<span>people</span>
<span>peel</span>
<span>peon</span>
</p>
<input type = "text" id = "matchText" name = "matchingText">
Обновлять
Внимательно прочитав вопрос, я подготовил приведенный выше фрагмент так, чтобы он находил введенные пользователем фрагменты в любом месте каждого слова. Комментарий ОП теперь ясно дал понять, что это было недоразумение, поэтому вот модифицированное решение, которое будет выделять буквы только в начале каждого слова:
// define variables for the elements in HTML
const words = document.querySelectorAll('#text>span');
const input = document.getElementById('matchText');
input.addEventListener("input",()=>{
const rx=new RegExp("^"+input.value,"ig");
words.forEach(w=>w.innerHTML=w.textContent.replace(rx,`<span class = "highlight">$&</span>`));
});
.highlight {
background-color: yellow;
}
<p id = "text">
<span>Banana</span>
<span>ball</span>
<span>ballistic</span>
<span>cat</span>
<span>chat</span>
<span>change</span>
<span>chap</span>
<span>character</span>
<span>color</span>
<span>people</span>
<span>peel</span>
<span>peon</span>
</p>
<input type = "text" id = "matchText" name = "matchingText">
Изменение касается определения регулярного выражения rx
:
const rx=new RegExp("^"+input.value,"ig");
Теперь он всегда начинается с ^
, поэтому шаблон должен находиться в начале каждого <span>
.
Если вы решите использовать такое решение, было бы целесообразно включить aria-метку в элементы диапазона, например «<span aria-label='Banana'>Banana</span>». Таким образом, люди с нарушениями зрения по-прежнему смогут понимать контент, и это никак не повлияет на людей без инвалидности.
Ах, сказал слишком рано. Я тоже пропустил немного выделения в середине слов. Есть ли способ изменить вызов RegExp(), чтобы он соответствовал только словам, начинающимся со строки?
Я изменил свой ответ соответственно. Пожалуйста, проверьте.
Ага! Спасибо, я знал, что это такая маленькая обработка! Я даже попробовал изменить «ig» на «^ig» после поиска кое-что о регулярных выражениях, но я не до конца понял это, и, очевидно, это не сработало. Мне в голову не пришло, что я ввел неправильный параметр, ха-ха... Еще раз спасибо!
выделенный
ban
поверх обычногоbanana