kensaku-7
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>【検索履歴ドロップダウン】contenteditable</title>
<style>
body { font-family: sans-serif; padding: 1em; }
.container { display: flex; flex-direction: column; gap: 10px; }
.controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.editable-area {
width: 500px;
height: 200px;
border: 1px solid #767676;
padding: 8px;
overflow: auto;
font-size: 16px;
line-height: 1.5;
background-color: white;
white-space: pre-wrap;
}
.highlight {
background-color: #ff0;
border-radius: 3px;
}
#searchCounter {
font-size: 0.9em;
color: #333;
min-width: 50px;
}
input[type="text"], select, input[type="button"] {
padding: 5px;
font-size: 1em;
}
</style>
</head>
<body>
<h1>【検索履歴ドロップダウン】検索 & ジャンプ機能</h1>
<div class="container">
<div class="controls">
<input type="text" id="searchInput" placeholder="検索キーワードを入力">
<input type="button" value="検索 / 次へ" onclick="handleSearch()">
<select id="searchHistorySelect" onchange="selectFromHistory()">
<option value="" disabled selected>検索履歴...</option>
</select>
<span id="searchCounter"></span>
</div>
<div id="editor" class="editable-area" contenteditable="true"></div>
</div>
<script>
const editor = document.getElementById('editor');
const searchInput = document.getElementById('searchInput');
const historySelect = document.getElementById('searchHistorySelect');
const counterElement = document.getElementById('searchCounter');
const sampleText = "このテキストエリアは、長文の入力が可能です。\nJavaScriptを使うことで、様々な便利機能を追加できます。例えば、このように検索{タンを押すと、指定したキーワードの場所までカーソルがジャンプします。\nもう一度「次を検索」{タンを押すと、次の「キーワード」に移動します。JavaScriptの可能性は無限大です。\nぜひ、このサンプルを改造して、あなただけの機能を作ってみてください。\n最後のキーワードはこちらです。\n\n";
editor.innerText = sampleText.repeat(5);
let currentMatches = [];
let currentIndex = -1;
const MAX_HISTORY = 10; // 履歴の最大件数を設定
// Enterキーでも検索できるように設定
searchInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
handleSearch();
}
});
/**
* 新しい統合された検索{タンの処理
*/
function handleSearch() {
const keyword = searchInput.value;
if (!keyword.trim()) return; // 空白は無視
// 前回の検索キーワードと違う場合は新しい検索、同じ場合は次を検索
if (editor.dataset.lastKeyword !== keyword) {
addToHistory(keyword);
findNext(true); // isNewSearch = true
} else {
findNext(false); // isNewSearch = false
}
}
/**
* 履歴ドロップダウンにキーワードを追加する関数
* @param {string} keyword 追加するキーワード
*/
function addToHistory(keyword) {
// 既に履歴に存在するかチェック
for (let i = 0; i < historySelect.options.length; i++) {
if (historySelect.options[i].value === keyword) {
historySelect.value = keyword; // 存在すればそれを選択状態にするだけ
return;
}
}
// 新しいoptionを作成して追加
const newOption = document.createElement('option');
newOption.value = keyword;
newOption.text = keyword;
historySelect.appendChild(newOption);
// 履歴が最大件数を超えたら、一番古いもの(インデックス1)を削除
// インデックス0は "検索履歴..." のプレースホルダーのため
if (historySelect.options.length > MAX_HISTORY + 1) {
historySelect.remove(1);
}
// 追加した項目を選択状態にする
historySelect.value = keyword;
}
// 履歴ドロップダウンが変更されたときの機能
function selectFromHistory() {
// ドロップダウンで選んだ値をテキスト{ックスに反映
searchInput.value = historySelect.value;
// 新しい検索を開始
findNext(true);
}
// 検索のコア機能
function findNext(isNewSearch = false) {
const keyword = searchInput.value;
if (!keyword) return;
if (isNewSearch || editor.dataset.lastKeyword !== keyword) {
removeHighlights();
editor.dataset.lastKeyword = keyword;
// 正規表現を使ってキーワードを<span class="highlight">で囲む
// 特殊文字をエスケープしてエラーを防ぐ
const regex = new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
editor.innerHTML = editor.innerText.replace(regex, (match) => `<span class="highlight">${match}</span>`);
currentMatches = Array.from(editor.querySelectorAll('.highlight'));
currentIndex = -1;
}
if (currentMatches.length === 0) {
if (isNewSearch) {
alert('キーワードが見つかりませんでした。');
}
counterElement.textContent = '(0 / 0)';
return;
}
currentIndex = (currentIndex + 1) % currentMatches.length;
counterElement.textContent = `(${currentIndex + 1} / ${currentMatches.length})`;
const currentElement = currentMatches[currentIndex];
// スクロールして中央に表示
currentElement.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
// 現在地だけ色を変える
currentMatches.forEach(el => el.style.backgroundColor = '#ff0'); // 黄色
currentElement.style.backgroundColor = '#ffa500'; // オレンジ
}
// ハイライトをすべて解除する関数
function removeHighlights() {
// innerTextを使うことで、spanタグなどのHTML要素をすべて取り除く
editor.innerHTML = editor.innerText;
counterElement.textContent = '';
}
</script>
</body>
</html>
使用変数
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>【検索履歴ドロップダウン】contenteditable</title>
<style>
body { font-family: sans-serif; padding: 1em; }
.container { display: flex; flex-direction: column; gap: 10px; }
.controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.editable-area {
width: 500px;
height: 200px;
border: 1px solid #767676;
padding: 8px;
overflow: auto;
font-size: 16px;
line-height: 1.5;
background-color: white;
white-space: pre-wrap;
}
.highlight {
background-color: #ff0;
border-radius: 3px;
}
#searchCounter {
font-size: 0.9em;
color: #333;
min-width: 50px;
}
input[type="text"], select, input[type="button"] {
padding: 5px;
font-size: 1em;
}
</style>
</head>
<body>
<h1>【検索履歴ドロップダウン】検索 & ジャンプ機能</h1>
<div class="container">
<div class="controls">
<input type="text" id="searchInput" placeholder="検索キーワードを入力">
<input type="button" value="検索 / 次へ" onclick="handleSearch()">
<select id="searchHistorySelect" onchange="selectFromHistory()">
<option value="" disabled selected>検索履歴...</option>
</select>
<span id="searchCounter"></span>
</div>
<div id="editor" class="editable-area" contenteditable="true"></div>
</div>
<script>
const editor = document.getElementById('editor');
const searchInput = document.getElementById('searchInput');
const historySelect = document.getElementById('searchHistorySelect');
const counterElement = document.getElementById('searchCounter');
const sampleText = "このテキストエリアは、長文の入力が可能です。\nJavaScriptを使うことで、様々な便利機能を追加できます。例えば、このように検索{タンを押すと、指定したキーワードの場所までカーソルがジャンプします。\nもう一度「次を検索」{タンを押すと、次の「キーワード」に移動します。JavaScriptの可能性は無限大です。\nぜひ、このサンプルを改造して、あなただけの機能を作ってみてください。\n最後のキーワードはこちらです。\n\n";
editor.innerText = sampleText.repeat(5);
let currentMatches = [];
let currentIndex = -1;
const MAX_HISTORY = 10; // 履歴の最大件数を設定
// Enterキーでも検索できるように設定
searchInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
handleSearch();
}
});
/**
* 新しい統合された検索{タンの処理
*/
function handleSearch() {
const keyword = searchInput.value;
if (!keyword.trim()) return; // 空白は無視
// 前回の検索キーワードと違う場合は新しい検索、同じ場合は次を検索
if (editor.dataset.lastKeyword !== keyword) {
addToHistory(keyword);
findNext(true); // isNewSearch = true
} else {
findNext(false); // isNewSearch = false
}
}
/**
* 履歴ドロップダウンにキーワードを追加する関数
* @param {string} keyword 追加するキーワード
*/
function addToHistory(keyword) {
// 既に履歴に存在するかチェック
for (let i = 0; i < historySelect.options.length; i++) {
if (historySelect.options[i].value === keyword) {
historySelect.value = keyword; // 存在すればそれを選択状態にするだけ
return;
}
}
// 新しいoptionを作成して追加
const newOption = document.createElement('option');
newOption.value = keyword;
newOption.text = keyword;
historySelect.appendChild(newOption);
// 履歴が最大件数を超えたら、一番古いもの(インデックス1)を削除
// インデックス0は "検索履歴..." のプレースホルダーのため
if (historySelect.options.length > MAX_HISTORY + 1) {
historySelect.remove(1);
}
// 追加した項目を選択状態にする
historySelect.value = keyword;
}
// 履歴ドロップダウンが変更されたときの機能
function selectFromHistory() {
// ドロップダウンで選んだ値をテキスト{ックスに反映
searchInput.value = historySelect.value;
// 新しい検索を開始
findNext(true);
}
// 検索のコア機能
function findNext(isNewSearch = false) {
const keyword = searchInput.value;
if (!keyword) return;
if (isNewSearch || editor.dataset.lastKeyword !== keyword) {
removeHighlights();
editor.dataset.lastKeyword = keyword;
// 正規表現を使ってキーワードを<span class="highlight">で囲む
// 特殊文字をエスケープしてエラーを防ぐ
const regex = new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
editor.innerHTML = editor.innerText.replace(regex, (match) => `<span class="highlight">${match}</span>`);
currentMatches = Array.from(editor.querySelectorAll('.highlight'));
currentIndex = -1;
}
if (currentMatches.length === 0) {
if (isNewSearch) {
alert('キーワードが見つかりませんでした。');
}
counterElement.textContent = '(0 / 0)';
return;
}
currentIndex = (currentIndex + 1) % currentMatches.length;
counterElement.textContent = `(${currentIndex + 1} / ${currentMatches.length})`;
const currentElement = currentMatches[currentIndex];
// スクロールして中央に表示
currentElement.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
// 現在地だけ色を変える
currentMatches.forEach(el => el.style.backgroundColor = '#ff0'); // 黄色
currentElement.style.backgroundColor = '#ffa500'; // オレンジ
}
// ハイライトをすべて解除する関数
function removeHighlights() {
// innerTextを使うことで、spanタグなどのHTML要素をすべて取り除く
editor.innerHTML = editor.innerText;
counterElement.textContent = '';
}
</script>
</body>
</html>
使用変数
addToHistory -------( Function ) | |
backgroundColor | |
charset | |
class | |
contenteditable | |
counterElement | |
currentElement | |
currentIndex | |
currentMatches | |
editor | |
el | |
event) { if -------( Function ) | |
findNext -------( Function ) | |
handleSearch -------( Function ) | |
historySelect | |
i | |
id | |
innerHTML | |
innerText | |
isNewSearch | |
key | |
keyword | |
lang | |
lastKeyword | |
length | |
MAX_HISTORY | |
newOption | |
onchange | |
onclick | |
placeholder | |
regex | |
removeHighlights -------( Function ) | |
sampleText | |
searchInput | |
selectFromHistory -------( Function ) | |
text | |
textContent | |
type | |
value |