Поиск по содержимому страницы без CSS?

Есть два поиска, аналоги Ctrl+F в браузере.

Первый

var lastResFind=""; // последний удачный результат
function TrimStr(s) {
     s = s.replace( /^\s+/g, '');
  return s.replace( /\s+$/g, '');
}
function FindOnPage(inputId) {//ищет текст на странице, в параметр передается ID поля для ввода
  var obj = window.document.getElementById(inputId);
  var textToFind;
  
  if (obj) {
    textToFind = TrimStr(obj.value);//обрезаем пробелы
  } else {
    alert("Введенная фраза не найдена");
    return;
  }
  if (textToFind == "") {
    alert("Вы ничего не ввели");
    return;
  }
   
  if(document.body.innerHTML.indexOf(textToFind)=="-1")
  alert("Ничего не найдено, проверьте правильность ввода!");
 
   
  document.body.innerHTML = document.body.innerHTML.replace(eval("/name="+lastResFind+"/gi")," ");//стираем предыдущие якори для скрола
  document.body.innerHTML = document.body.innerHTML.replace(eval("/"+textToFind+"/gi"),"<a name="+textToFind+" style='background:red'>"+textToFind+"</a>"); //Заменяем найденный текст ссылками с якорем;
  lastResFind=textToFind; // сохраняем фразу для поиска, чтобы в дальнейшем по ней стереть все ссылки
  window.location = '#'+textToFind;//перемещаем скрол к последнему найденному совпадению
 } 
<input type="text" id="text-to-find" value=""> 
<input type="button" onclick="javascript: FindOnPage('text-to-find'); return false;" value="Искать"/>
<br/><i>Слова для теста Слова для теста Слова для теста Слова для теста</i>
<hr/>

Второй (Живой)

const minCharsToSearch = 3;
        
        const $ = (...q) => q.reduce((r,s)=>!r?null:'object'==typeof s?s:r.querySelector(s),document),
              $$ = (...q) => q.reduce((r,s,i,q)=>!r?null:'object'==typeof s?s:r["querySelector"+(i==q.length-1?'All':'')](s),document),
              on = (o,e,c) => o.addEventListener(e,c,false),
              stop = e => ['preventDefault','stopPropagation'].map(f=>e[f]()),
              addCls = (o,c) => (o.classList.add(c),o),
              remCls = (o,c) => (o.classList.remove(c),o),
              newEl = t => document.createElement(t),
              append = (p,o) => (p.appendChild(o),o),
              pageTop = () => document.documentElement.scrollTop + document.body.scrollTop,
              // @todo document.documentElement.clientHeight или document.body.clientHeight
              pageBottom = () => pageTop() + document.documentElement.clientHeight;

        const form = $('form'),
              input = $(form,'input'),
              search = $(form,'.search'),
              clear = $(form,'.clear'),
              output = $(form,'.output'),
              next = $(form,'.next'),
              prev = $(form,'.prev'),
              content = $('.content');

        on(form, 'submit', onSubmit);
        on(form, 'input', onInput);
        on(window, 'resize', onResize);

        on(clear,'click', doClear);
        on(output,'click', curPosLast);
        on(next, 'click', goNext);
        on(prev, 'click', goPrev);

        function onSubmit(e){
          stop(e);
        }

        let timeout, searchResult = [], searchResultActive = null;
      
        function onInput(e){
          stop(e);
          if(input.value){
            remCls(search,'visible');
            addCls(clear,'visible');
          } else {
            output.innerHTML = ``;
            addCls(search,'visible');
            remCls(clear,'visible');
          }
          remCls(next,'enabled');
          remCls(prev,'enabled');
          if(timeout) {
            clearTimeout(timeout);
          }
          timeout = setTimeout(()=>{
            timeout = null;
            const searchValue = input.value;
            if(searchValue.length>=minCharsToSearch){
              doSearch(searchValue);
            } else {
              output.innerHTML = `от 3х букв`;
            }
          },200);
        }

        function doSearch(searchValue){
          searchResultActive = null;
          searchResult = findAll(searchValue);
          if(searchResult.length){
            let top=pageTop(),found = 0;
            for(const [idx,res] of searchResult.entries()){
              if(res.rects.every(rect => rect.top > top)){
                found = idx;
                break;
              }
            }
            setActive(found);
          } else {
            output.innerHTML = `совпадений нет`;
          }
        }

        function doClear(){
          searchResultActive = null;
          searchResult = [];
          input.value = '';
          addCls(search,'visible');
          remCls(clear ,'visible');
          remCls(next  ,'enabled');
          remCls(prev  ,'enabled');
          $$('.highlight').forEach(hl=>hl.remove());
          output.innerHTML = ``;
        }

        function goNext(){
          setActive(searchResultActive+1);
        }

        function goPrev(){
          setActive(searchResultActive-1);
        }

        function onResize(){
          setTimeout(()=>
            searchResult.map(res=>(
              res.rects = getRects(res.range)
                .map((rect,i)=>(setRectSize(res.hls[i],rect),rect))
            ))
          ,1);
        }

        function findAll(text){
          $$('.highlight').forEach(hl=>hl.remove());
          const { textContent } = content;
          const rx = new RegExp(text.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1")
                                .replace(/\s+/g, '\\s+'),'ig');
          const res=[];
          let match;
          while(match = rx.exec(textContent)){
            res.push({ startAbs : match.index,
                       startNode: null,
                       startOfs : null,
                       endAbs   : match.index + match[0].length,
                       endNode  : null,
                       endOfs   : null,
                       range    : null,
                       rects    : null,
                       hls      : null  });
          }
          const walk = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, null, false); 
          let idx = 0, len = res.length, curStart = 0, node;
          while(idx < len && (node = walk.nextNode())){
            const curEnd = curStart + node.textContent.length;
            let found = false;
            do{
              found = false;
              const curRes = res[idx], { startAbs, endAbs } = curRes;
              if(!curRes.startNode && curStart <= startAbs && startAbs < curEnd ){
                curRes.startNode = node; 
                curRes.startOfs = startAbs - curStart;
              }
              if(curRes.startNode && curStart < endAbs && endAbs < curEnd ){
                const range = document.createRange();
                curRes.range = range;
                curRes.endNode = node; 
                curRes.endOfs = endAbs - curStart;
                range.setStart(curRes.startNode,curRes.startOfs);
                range.setEnd(curRes.endNode,curRes.endOfs);
                curRes.hls = (curRes.rects = getRects(range))
                  .map(rect=>append(document.body,
                    setRectSize(addCls(newEl('div'),'highlight'),rect)));
                found = true;
                idx++;
              }
            } while(found && idx < len);
            curStart = curEnd;
          }      
          return res;
        }

        function setActive(resNum){
          $$('.highlight.active').forEach(hl=>remCls(hl,'active'));
          const res = searchResult[resNum], top = pageTop(), bottom = pageBottom();
          if(!res.rects.every(rect=>rect.top>top && rect.top+rect.height<bottom)){
            const minRectTop = Math.min(...res.rects.map(rect=>rect.top));
            const maxRectBottom = Math.max(...res.rects.map(rect=>rect.top+rect.height));
            const centerRect = (maxRectBottom + minRectTop) / 2;
            const centerScr = (bottom - top) / 2;
            // @todo document.documentElement или document.body
            document.documentElement.scrollTop = centerRect - centerScr;
          }
          searchResultActive = resNum;
          res.hls.forEach(hl=>addCls(hl,'active'));
          const len = searchResult.length;
          output.innerHTML = `${resNum+1} из ${len}`;
          if(len > 1){
            (resNum < len - 1 ? addCls : remCls)(next,'enabled');
            (resNum > 0 ? addCls : remCls)(prev, 'enabled');
          }
        }

        function curPosLast(){
          const len = input.value.length;
          input.focus();
          input.setSelectionRange(len,len);
        }

        function getRects(range){
          return [...range.getClientRects()]
            .map((rect)=>['left','width','top','height']
              .reduce((r,k)=>(r[k]=rect[k],r),{}))
            .map(rect=>(rect.top += pageTop(),rect));
        }

        function setRectSize(el, rect){
          Object.assign(el.style,
            Object.fromEntries(
              Object.entries(rect).map(([key,val])=>([key,val+'px']))));
          return el;
        }
* { box-sizing: border-box;}
html,body{ margin: 0; scroll-behavior: smooth; }

form {
  position: fixed;
  top: 0;
  width: 100vw;
  height: 3rem;
  padding: 5px;
  z-index: 5;
  background: #ccc;
}

.input {
  border: 1px solid #888;
  width: calc( 100vw - 10px - 6rem);
  height: calc( 3rem - 10px);
  border-radius: 3px;
  background: #fff;
  overflow: hidden;
}

.input:hover {
  border: 1px solid #ad1d1d;
}

.input:focus,.input:focus-within,.input:active {
  border: 1px solid #c58e37;
  box-shadow: 0 0 2px #c58e37;
}

.input * {
  margin: 0; 
  padding:0;
  height: calc( 3rem - 12px);
  display: inline-block;
  white-space: nowrap;
  vertical-align: middle;
}

.input input {
  border: none;
  width: calc( 100vw - 16rem - 12px - 6rem);
  height: calc( 3rem - 12px);
  outline: none;
}
.input .search,
.input .clear,
.input .next,
.input .prev,
.input .output {
  user-select: none;
}
.search {
  display:none;
  width: 3rem;
  height: calc(3rem - 12px);
  text-align: center;
  padding: .2rem 0 0 0;
}

.clear {
  width: calc(3rem - 20px);
  height: calc(3rem - 24px);
  text-align: center;
  display: none;
  line-height: 1.4rem;
  margin: 6px 10px 8px 10px;
  cursor: default;
  border: 0px solid transparent;
  background: #8888;
  color: #fff;
  border-radius: 3px;
}
.clear:hover {
  color:#fff;
  background:#f008;
}

.visible {
  display: inline-block;
}

.output {
  width: 7rem;
  text-align: right;
  height: 2rem;
  display: inline-block;
  height: calc(3rem - 12px);
  padding: .5rem 1rem 0 0;
  cursor: text;
}

.input button{
  width: 3rem;
  height: calc(3rem - 11px);
  pointer-events: none;
  background: #aaa;
  color: #888;
  border-radius: 0;
  border: 1px solid transparent;
  border-left: 1px solid #888;
  outline: none;
  margin: -1px 0 1px 0;
}

.input button.enabled {
  pointer-events: all;
  background: #eee;
  color: #000;
}

.input button.enabled:focus{
  box-shadow: inset 0 0 2px 1px #f84;
}

.input button.enabled:active{
  border-top: 1px solid #888;
  border-left: 2px solid #888;
  padding-top: 2px;
  padding-left: 1px;
}


.content{
  clear:both;
  margin-top: 3.5rem;
  z-index: 3;
  position: relative;
  padding: 1rem;
}

.highlight{
  background: #ff04;
  box-shadow: 0 0 2px #ff0;
  position:absolute;
  z-index:1;
}

.highlight.active {
  background: #f844;
  box-shadow: 0 0 2px #f00;
}
 <form><div class="input"><label for="search" class="search visible">?</label><span class="clear">✖</span><input id="search" type="text"/><span class="output"></span><button class="prev">⫷</button><button class="next">⫸</button></div></form>
    
    <div class="content">
      <p>Слова для теста Слова для теста Слова для теста Слова для теста</div>

Как видно, у первого нет css, и он отлично работает.

Но вот если убрать из второго css, то результат уже не выделяется.

const minCharsToSearch = 3;
        
        const $ = (...q) => q.reduce((r,s)=>!r?null:'object'==typeof s?s:r.querySelector(s),document),
              $$ = (...q) => q.reduce((r,s,i,q)=>!r?null:'object'==typeof s?s:r["querySelector"+(i==q.length-1?'All':'')](s),document),
              on = (o,e,c) => o.addEventListener(e,c,false),
              stop = e => ['preventDefault','stopPropagation'].map(f=>e[f]()),
              addCls = (o,c) => (o.classList.add(c),o),
              remCls = (o,c) => (o.classList.remove(c),o),
              newEl = t => document.createElement(t),
              append = (p,o) => (p.appendChild(o),o),
              pageTop = () => document.documentElement.scrollTop + document.body.scrollTop,
              // @todo document.documentElement.clientHeight или document.body.clientHeight
              pageBottom = () => pageTop() + document.documentElement.clientHeight;

        const form = $('form'),
              input = $(form,'input'),
              search = $(form,'.search'),
              clear = $(form,'.clear'),
              output = $(form,'.output'),
              next = $(form,'.next'),
              prev = $(form,'.prev'),
              content = $('.content');

        on(form, 'submit', onSubmit);
        on(form, 'input', onInput);
        on(window, 'resize', onResize);

        on(clear,'click', doClear);
        on(output,'click', curPosLast);
        on(next, 'click', goNext);
        on(prev, 'click', goPrev);

        function onSubmit(e){
          stop(e);
        }

        let timeout, searchResult = [], searchResultActive = null;
      
        function onInput(e){
          stop(e);
          if(input.value){
            remCls(search,'visible');
            addCls(clear,'visible');
          } else {
            output.innerHTML = ``;
            addCls(search,'visible');
            remCls(clear,'visible');
          }
          remCls(next,'enabled');
          remCls(prev,'enabled');
          if(timeout) {
            clearTimeout(timeout);
          }
          timeout = setTimeout(()=>{
            timeout = null;
            const searchValue = input.value;
            if(searchValue.length>=minCharsToSearch){
              doSearch(searchValue);
            } else {
              output.innerHTML = `от 3х букв`;
            }
          },200);
        }

        function doSearch(searchValue){
          searchResultActive = null;
          searchResult = findAll(searchValue);
          if(searchResult.length){
            let top=pageTop(),found = 0;
            for(const [idx,res] of searchResult.entries()){
              if(res.rects.every(rect => rect.top > top)){
                found = idx;
                break;
              }
            }
            setActive(found);
          } else {
            output.innerHTML = `совпадений нет`;
          }
        }

        function doClear(){
          searchResultActive = null;
          searchResult = [];
          input.value = '';
          addCls(search,'visible');
          remCls(clear ,'visible');
          remCls(next  ,'enabled');
          remCls(prev  ,'enabled');
          $$('.highlight').forEach(hl=>hl.remove());
          output.innerHTML = ``;
        }

        function goNext(){
          setActive(searchResultActive+1);
        }

        function goPrev(){
          setActive(searchResultActive-1);
        }

        function onResize(){
          setTimeout(()=>
            searchResult.map(res=>(
              res.rects = getRects(res.range)
                .map((rect,i)=>(setRectSize(res.hls[i],rect),rect))
            ))
          ,1);
        }

        function findAll(text){
          $$('.highlight').forEach(hl=>hl.remove());
          const { textContent } = content;
          const rx = new RegExp(text.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1")
                                .replace(/\s+/g, '\\s+'),'ig');
          const res=[];
          let match;
          while(match = rx.exec(textContent)){
            res.push({ startAbs : match.index,
                       startNode: null,
                       startOfs : null,
                       endAbs   : match.index + match[0].length,
                       endNode  : null,
                       endOfs   : null,
                       range    : null,
                       rects    : null,
                       hls      : null  });
          }
          const walk = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, null, false); 
          let idx = 0, len = res.length, curStart = 0, node;
          while(idx < len && (node = walk.nextNode())){
            const curEnd = curStart + node.textContent.length;
            let found = false;
            do{
              found = false;
              const curRes = res[idx], { startAbs, endAbs } = curRes;
              if(!curRes.startNode && curStart <= startAbs && startAbs < curEnd ){
                curRes.startNode = node; 
                curRes.startOfs = startAbs - curStart;
              }
              if(curRes.startNode && curStart < endAbs && endAbs < curEnd ){
                const range = document.createRange();
                curRes.range = range;
                curRes.endNode = node; 
                curRes.endOfs = endAbs - curStart;
                range.setStart(curRes.startNode,curRes.startOfs);
                range.setEnd(curRes.endNode,curRes.endOfs);
                curRes.hls = (curRes.rects = getRects(range))
                  .map(rect=>append(document.body,
                    setRectSize(addCls(newEl('div'),'highlight'),rect)));
                found = true;
                idx++;
              }
            } while(found && idx < len);
            curStart = curEnd;
          }      
          return res;
        }

        function setActive(resNum){
          $$('.highlight.active').forEach(hl=>remCls(hl,'active'));
          const res = searchResult[resNum], top = pageTop(), bottom = pageBottom();
          if(!res.rects.every(rect=>rect.top>top && rect.top+rect.height<bottom)){
            const minRectTop = Math.min(...res.rects.map(rect=>rect.top));
            const maxRectBottom = Math.max(...res.rects.map(rect=>rect.top+rect.height));
            const centerRect = (maxRectBottom + minRectTop) / 2;
            const centerScr = (bottom - top) / 2;
            // @todo document.documentElement или document.body
            document.documentElement.scrollTop = centerRect - centerScr;
          }
          searchResultActive = resNum;
          res.hls.forEach(hl=>addCls(hl,'active'));
          const len = searchResult.length;
          output.innerHTML = `${resNum+1} из ${len}`;
          if(len > 1){
            (resNum < len - 1 ? addCls : remCls)(next,'enabled');
            (resNum > 0 ? addCls : remCls)(prev, 'enabled');
          }
        }

        function curPosLast(){
          const len = input.value.length;
          input.focus();
          input.setSelectionRange(len,len);
        }

        function getRects(range){
          return [...range.getClientRects()]
            .map((rect)=>['left','width','top','height']
              .reduce((r,k)=>(r[k]=rect[k],r),{}))
            .map(rect=>(rect.top += pageTop(),rect));
        }

        function setRectSize(el, rect){
          Object.assign(el.style,
            Object.fromEntries(
              Object.entries(rect).map(([key,val])=>([key,val+'px']))));
          return el;
        }
<form><div class="input"><label for="search" class="search visible">?</label><span class="clear">✖</span><input id="search" type="text"/><span class="output"></span><button class="prev">⫷</button><button class="next">⫸</button></div></form>
    
    <div class="content">
      <p>Слова для теста Слова для теста Слова для теста Слова для теста</div>

Можно ли как нибудь сделать так, что бы второй тоже выделял результаты без css?


Ответы (0 шт):