CSSの::highlight()で文字列をタグで囲まずにハイライトする方法

CSSの::highlight()でハイライト

CSSを使用すれば特定の文字をハイライトすることができますが、一般的な方法では特定の文字をspanなどのタグで囲む必要があります。

::highlight()疑似要素とCSS Custom Highlight APIを組み合わせれば、特定の文字列(キーワード)だけを動的にハイライトすることができます。

ハイライトと言っても背景色を変えるだけでなく、以下のプロパティの適用できます。

  • color
  • background-color
  • text-decoration および関連するプロパティ
  • text-shadow
  • -webkit-text-stroke-color
  • -webkit-text-fill-color
  • -webkit-text-stroke-width

逆に言うと、これらのプロパティ以外は適用できないので、フォントサイズを変えたり、太字にはできません。

::highlight()とHighlight APIの実装方法

以下のような文章があって、「CSS」の文字列をハイライトする例で説明します。

HTML
<p id="target">
  CSSの::highlight()を使えば、JavaScriptで範囲を指定するだけで<br />
  HTMLを書き換えずにCSSで装飾できます。CSSは非常に強力です。
</p>

黄色のハイライト用CSSを::highlight()疑似要素を使って記述します。

名前は任意なので、css-highlightにします。

CSS
::highlight(css-highlight) {
  background-color: yellow;
  text-decoration: underline dotted;
}

次にJavaScriptでハイライトさせる要素、文字列、名前などを以下のように指定します。

JavaScript
const targetNode = document.getElementById('target')
const searchWord = 'CSS'
const textNodes = []
const walker = document.createTreeWalker(targetNode, NodeFilter.SHOW_TEXT)
let node
let fullText = ''

while ((node = walker.nextNode())) {
  textNodes.push({
    node,
    start: fullText.length,
    end: fullText.length + node.textContent.length,
  })
  fullText += node.textContent
}

const ranges = []
let startPos = 0

while ((startPos = fullText.indexOf(searchWord, startPos)) !== -1) {
  const endPos = startPos + searchWord.length
  let startInfo = null
  let endInfo = null

  for (const item of textNodes) {
    if (!startInfo && startPos >= item.start && startPos < item.end) {
      startInfo = {
        node: item.node,
        offset: startPos - item.start,
      }
    }

    if (!endInfo && endPos > item.start && endPos <= item.end) {
      endInfo = {
        node: item.node,
        offset: endPos - item.start,
      }
    }
  }

  if (startInfo && endInfo) {
    const range = new Range()
    range.setStart(startInfo.node, startInfo.offset)
    range.setEnd(endInfo.node, endInfo.offset)
    ranges.push(range)
  }

  startPos += searchWord.length
}

const highlight = new Highlight(...ranges)
CSS.highlights.set('css-highlight', highlight)

ターゲットの要素内に<br />が入ると、targetNode.firstChildだけでは全文を扱えないため、複数のテキストノードをまたいでRangeを作る必要があります。

これだけで、指定した要素に「CSS」の文字列がある場合は、該当箇所をハイライトさせることができます。

旧来のタグで囲んでハイライトさせる方法よりもタグで囲む手間がなく、保守性にも優れているので、ハイライトさせたいときは::highlight()とHighlight APIを使用することを推奨します。

::highlight()とHighlight APIで特定の文字をハイライトさせたサンプル