知恵袋 (前に戻る)
101

 ホームページ
サイト内(ページ内)検索HTML作成手順

①search-demo.htmlをルートに設置(テスト用なので不要・削除してもOK)
内容は yoko8zに有る。
このページの下段にも記述あり


②目的ページの</HEAD>の直前に

<style>
/* 固定表示検索ボックス */
#searchBox {
position: fixed; /* 画面上段に固定 */
top: 0;
left: 0;
width: 100%;
background-color: #fff;
border-bottom: 1px solid #ccc;
padding: 8px;
box-sizing: border-box;
z-index: 9999;
}

/* 入力欄 */
#searchInput {
padding: 8px 12px;
font-size: 16px;
width: 65%; /* 入力欄の幅調整 */
border: 1px solid #666;
border-radius: 6px;
}

/* ボタン */
#searchBox button {
padding: 8px 12px;
font-size: 14px;
margin-left: 4px;
cursor: pointer;
}

/* 本文との重なり防止 */
#content {
margin-top: 70px; /* 検索ボックスの高さに合わせる */
}

/* スマホ向けレスポンシブ調整 */
@media (max-width: 480px) {
#searchInput {
width: 55%;
font-size: 14px;
}
#searchBox button {
font-size: 12px;
padding: 6px 10px;
}
#content {
margin-top: 90px;
}
}
</style>

<style>
/* 🔍 検索ボックス用スタイル */
#searchBox {
display: flex; /* 横並びにする */
flex-wrap: nowrap; /* 折り返さない */
align-items: center; /* ボタンを縦位置中央に */
gap: 5px; /* ボタンとの隙間 */
justify-content: center; /* 中央寄せ */
margin: 10px 0;
}

#searchBox input[type="text"] {
width: 50%; /* 入力欄を半分くらいに */
min-width: 120px; /* スマホでも潰れすぎないように下限幅 */
padding: 5px;
font-size: 14px;
}

#searchBox button {
padding: 5px 10px;
font-size: 14px;
white-space: nowrap; /* ボタン内の文字を折り返さない */
}
</style>

を記述
内容はhttp://hamada777.g1.xrea.com/index2.htmlに有る
以下すべて。



③<body>の下(<table>の上)に下記を記述

<!-- 🔍 固定表示検索ボックス -->
<div id="searchBox">
<input type="text" id="searchInput" placeholder="検索ワードを入力">
<button onclick="searchText()">検索</button>
<button onclick="nextResult()">次へ</button>
<button onclick="prevResult()">前へ</button>
</div>

<!-- ★検索対象を div#content で囲む -->
<div id="content">

このあと検索対象の最後を

</div>
<!-- ★ここまで -->

で閉じる



④</body>の直前に下記scriptを2つ記述
(ホームページビルダーのプレビューでスクリプトエラーになっても問題ない)

<script>
let results = []; // 検索ヒットした要素を保存
let currentIndex = 0; // 現在の位置

function searchText() {
const input = document.getElementById("searchInput").value.trim();
const content = document.getElementById("content");

// 前回のハイライトを解除
content.querySelectorAll("mark").forEach(mark => {
const parent = mark.parentNode;
parent.replaceChild(document.createTextNode(mark.textContent), mark);
parent.normalize();
});

results = [];
currentIndex = 0;

if (input === "") return;

// 正規表現で検索語をハイライト
const regex = new RegExp(input, "gi");
content.innerHTML = content.innerHTML.replace(regex, match => `<mark>${match}</mark>`);

results = Array.from(content.querySelectorAll("mark"));

if (results.length > 0) {
results[0].scrollIntoView({ behavior: "smooth", block: "center" });
results[0].style.backgroundColor = "yellow";
}
}

function nextResult() {
if (results.length === 0) return;
results[currentIndex].style.backgroundColor = "orange";
currentIndex = (currentIndex + 1) % results.length;
results[currentIndex].scrollIntoView({ behavior: "smooth", block: "center" });
results[currentIndex].style.backgroundColor = "yellow";
}

function prevResult() {
if (results.length === 0) return;
results[currentIndex].style.backgroundColor = "orange";
currentIndex = (currentIndex - 1 + results.length) % results.length;
results[currentIndex].scrollIntoView({ behavior: "smooth", block: "center" });
results[currentIndex].style.backgroundColor = "yellow";
}
</script>

<script>
// 検索ボックスの高さを自動計算させる
window.addEventListener("load", function() {
var searchBox = document.getElementById("searchBox");
var content = document.getElementById("content");
if (searchBox && content) {
var h = searchBox.offsetHeight;
content.style.marginTop = h + "px";
}
});
</script>



①search-demo.html(参考保存のみ)

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>ページ内テキスト検索デモ(HTML+JSのみ)</title>
<style>
:root { --accent: #0d6efd; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif; line-height: 1.7; margin: 0; }
header { position: sticky; top: 0; background: #fff; border-bottom: 1px solid #e5e7eb; z-index: 10; }
.container { max-width: 960px; margin: 0 auto; padding: 16px; }
.toolbar { display: grid; grid-template-columns: 1fr auto auto auto auto; gap: 8px; align-items: center; }
.toolbar input[type="text"] { padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 10px; }
.toolbar button, .toolbar label { padding: 10px 12px; border-radius: 10px; border: 1px solid #cbd5e1; background: #f8fafc; cursor: pointer; }
.toolbar button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.toolbar .count { font-variant-numeric: tabular-nums; color: #334155; padding: 0 8px; }
mark.find-hit { background: #fff59d; padding: 0 2px; border-radius: 3px; }
mark.find-current { background: #ffd54f; outline: 2px solid #ffb300; }
.content { padding: 24px 16px 64px; }
.hint { color: #64748b; font-size: 14px; }
.kbd { padding: 1px 6px; border: 1px solid #cbd5e1; border-radius: 6px; background: #fff; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
</style>
</head>
<body>
<header>
<div class="container">
<div class="toolbar" role="search">
<input id="q" type="text" placeholder="このページ内を検索… (例: フェーズドアレイ)" autocomplete="off" />
<button id="btnFind" class="primary" title="検索 (Enter)">検索</button>
<button id="btnPrev" title="前の一致 (Shift+Enter)">前へ</button>
<button id="btnNext" title="次の一致 (Enter)">次へ</button>
<button id="btnClear" title="結果を解除 (Esc)">解除</button>
<div class="count" id="counter" aria-live="polite"></div>
</div>
<div class="hint">Enter=検索/次へ、Shift+Enter=前へ、Esc=解除。 大文字小文字 <label><input type="checkbox" id="optCase" /> 区別する</label>・<label><input type="checkbox" id="optWord" /> 単語全体一致</label></div>
</div>
</header>

<main class="container content">
<!-- ★★ 検索対象はこの#content内だけです。自分のページでは本文をこのdivに入れてください。 -->
<article id="content">
<h1>ページ内テキスト検索デモ</h1>
<p>このサンプルは、サーバー側のプログラムなし(FTPアップロードのみ)で<strong>ページ内のテキストを検索</strong>し、
一致箇所へ自動スクロール&ハイライトします。日本語もOKです。</p>
<p>主なポイント:
<ul>
<li>検索語の全一致を順送り/逆送り(次へ/前へ)</li>
<li>大文字小文字の区別・単語全体一致の切替</li>
<li>Escで解除して元の本文に戻す(元HTMLを一時保存)</li>
<li>検索バーはページ上部に固定(<code>position: sticky</code>)</li>
</ul>
</p>
<h2>ダミー本文</h2>
<p>たとえば「アンテナ」「フェーズドアレイ」「Excel」などで試してみてください。検索結果は
<mark>強調表示</mark>され、現在位置はさらに濃い色で表示されます。</p>
<p>段落が長くてもOKです。複数の一致がある場合、Enterで次の一致へ自動スクロールします。</p>
<p>英語: phased array antenna / array factor / beam steering / FFT.
日本語: 位相制御 / 素子間隔 / 指向性。
</p>
</article>
</main>

<script>
(function(){
const $ = (sel, ctx=document) => ctx.querySelector(sel);
const $$ = (sel, ctx=document) => Array.from(ctx.querySelectorAll(sel));

const box = $('#q');
const btnFind = $('#btnFind');
const btnNext = $('#btnNext');
const btnPrev = $('#btnPrev');
const btnClear = $('#btnClear');
const counter = $('#counter');
const optCase = $('#optCase');
const optWord = $('#optWord');
const content = $('#content');

let hits = []; // Array<HTMLElement mark.find-hit>
let pos = -1; // 現在の一致位置インデックス
let originalHTML = null; // 元のHTML(解除用)

function escapeRegExp(s){ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }

function buildRegex(query){
if(!query) return null;
const flags = optCase.checked ? 'g' : 'gi';
const src = optWord.checked ? `\\b${escapeRegExp(query)}\\b` : escapeRegExp(query);
try { return new RegExp(src, flags); } catch(e){ return null; }
}

function clearHighlights(){
if(originalHTML != null){
content.innerHTML = originalHTML;
} else {
// 念のため:markを外す(初回前はmark存在しない)
$$('.find-hit', content).forEach(m => {
const t = document.createTextNode(m.textContent);
m.replaceWith(t);
});
}
hits = [];
pos = -1;
originalHTML = null;
updateCounter();
}

function updateCounter(){
if(hits.length === 0){ counter.textContent = ''; return; }
counter.textContent = `${pos+1} / ${hits.length}`;
}

function scrollToCurrent(){
if(pos < 0 || pos >= hits.length) return;
hits.forEach(h => h.classList.remove('find-current'));
const el = hits[pos];
el.classList.add('find-current');
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
}

function doFind(direction = 0){
const q = box.value.trim();
if(!q){ clearHighlights(); return; }

// 既存の結果が検索語・オプションと一致しているか簡易チェック
const sameQuery = content.dataset._lastQuery === q &&
content.dataset._lastCase === String(optCase.checked) &&
content.dataset._lastWord === String(optWord.checked);

if(!sameQuery){
// 初回または条件変更:ハイライトを作り直す
clearHighlights();
originalHTML = content.innerHTML; // 元を保存(解除で復元)

const regex = buildRegex(q);
if(!regex){ return; }

// テキストノードを走査してmark化
const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, {
acceptNode(node){
// 目に見えるテキストのみ(空白や改行のみは除外)
return /\S/.test(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
});

const textNodes = [];
while(walker.nextNode()) textNodes.push(walker.currentNode);

textNodes.forEach(node => {
const parent = node.parentNode;
let html = node.nodeValue;
if(!regex.test(html)) return; // 該当なし
// global RegExp は状態を持つので都度リセット
regex.lastIndex = 0;
const parts = [];
let lastIdx = 0;
let m;
while((m = regex.exec(html))){
const before = html.slice(lastIdx, m.index);
const hit = m[0];
parts.push(document.createTextNode(before));
const mark = document.createElement('mark');
mark.className = 'find-hit';
mark.textContent = hit;
parts.push(mark);
hits.push(mark);
lastIdx = m.index + hit.length;
}
parts.push(document.createTextNode(html.slice(lastIdx)));
const frag = document.createDocumentFragment();
parts.forEach(p => frag.appendChild(p));
parent.replaceChild(frag, node);
});

content.dataset._lastQuery = q;
content.dataset._lastCase = String(optCase.checked);
content.dataset._lastWord = String(optWord.checked);

if(hits.length === 0){
updateCounter();
return;
}
pos = 0; // 最初の一致へ
updateCounter();
scrollToCurrent();
return;
}

// 既存の結果で移動
if(hits.length === 0){ updateCounter(); return; }
if(direction === 0){ // Enter=次へ
pos = (pos + 1) % hits.length;
} else if(direction < 0){ // 前へ
pos = (pos - 1 + hits.length) % hits.length;
} else { // 明示的に次へ
pos = (pos + 1) % hits.length;
}
updateCounter();
scrollToCurrent();
}

// ---- イベント
btnFind.addEventListener('click', () => doFind(+1));
btnNext.addEventListener('click', () => doFind(+1));
btnPrev.addEventListener('click', () => doFind(-1));
btnClear.addEventListener('click', clearHighlights);

box.addEventListener('keydown', (e) => {
if(e.key === 'Enter'){
e.preventDefault();
doFind(e.shiftKey ? -1 : +1);
} else if(e.key === 'Escape'){
clearHighlights();
}
});

// ページ全体のショートカット(/ でフォーカス)
document.addEventListener('keydown', (e) => {
if(e.key === '/' && !e.ctrlKey && !e.metaKey && !e.altKey){
e.preventDefault();
box.focus();
box.select();
}
});
})();
</script>
</body>
</html>

(前に戻る)

chie2.html(tvm)
<EOF>