行番号枠線用の span 要素を作成しない(使わない)バージョン

以下の HTML をコピーして style タグと script タグにサンプルの CSS と JavaScript をコピペして試せます。

行番号枠線用の span 要素を作成するのと異なり、例えば、以下の場合、ツールバーの wrap をクリックして、自動折り返しを有効にすると行番号横の枠線(8行目と20行目)が途切れます。

また、コメント行の枠線も途切れてしまっています。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hilight.js Sample</title>
  <!-- Hilight.js テーマ CSS の読み込み -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css" integrity="sha512-Jk4AqjWsdSzSWCSuQTfYRIF84Rq/eV0G2+tu07byYwHcbTGfdmLrHjUSwvzp5HvbiqK4ibmNwdcG49Y5RGYPTg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  <style>
    /* 以下のサンプルの CSS(custom.css)をコピペ */
  </style>
</head>
<body>

  <div class="hljs-wrap">
    <pre><code>ハイライト表示するコードを記述</code></pre>
  </div>

  <!-- Hilight.js JavaScript の読み込み -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <script>
    // 以下のサンプルの JavaScript(custom.js)をコピペ
  </script>
</body>
</html>
/* updated (changed) on: 2024/03/29  */
/* pre 要素のラッパー要素(div)に指定するクラス( CSS ネスティングを使用)*/
.hljs-wrap {
  position: relative;

  /* pre 要素 */
  pre {
    overflow-wrap: break-word;
    overflow-x: hidden;
    padding: 0;
    margin: 0;
    /* 必要に応じてフォントサイズなどを設定 */
  }

  /* pre-wrap クラスを指定すると行を自動で折り返す */
  pre.pre-wrap {
    white-space: pre-wrap;
  }

  /* pre クラスを指定すると行を自動で折返さない */
  pre.pre {
    white-space: pre;
  }

  /* 以下の padding-left を変更したら custom.js の codePadding も変更 */
  pre code {
    padding-left: 3rem;
    position: relative;
    /* 自動折り返しの設定は親要素(pre)の値を継承 */
    white-space: inherit;
    /* コード内に垂直方向のスクロールバーが表示されるのを防止(必要に応じて) */
    overflow-y: hidden;
    /* border-left: 3px solid red; */
  }

  /* ツールバーを使わない場合の code 要素 */
  &.no-toolbar pre code,
  body.no-toolbar & pre code {
    padding-bottom: 1.5rem;
    padding-top: 2.5rem;
  }

  &.no-toolbar pre code.show-no-lang,
  body.no-toolbar & pre code.show-no-lang {
    padding-top: 1rem;
  }

  &.no-toolbar.has-copy-btn pre code.show-no-lang,
  body.no-toolbar.has-copy-btn & pre code.show-no-lang {
    padding-top: 2rem;
  }

  &.no-toolbar pre.no-copy-btn code,
  body.no-toolbar & pre.no-copy-btn code {
    padding-bottom: 1rem;
  }

  /* ツールバー */
  .highlight-toolbar {
    height: 2rem;
    background-color: #3a3e4a;
    padding-right: 5px;
    color: #999;
    font-size: 12px;
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
  }

  &.no-lang .highlight-toolbar {
    justify-content: flex-end;
  }

  .highlight-toolbar + pre {
    margin-top: 0;
  }

  .highlight-toolbar label {
    color: #888;
    cursor: pointer;
    margin: 0 10px 0 0;
    transition: color 0.3s;
  }

  .highlight-toolbar input[type="checkbox"] {
    background-color: #262b37;
    transition: background-color 0.3s;
    display: none;
  }

  @media screen and (min-width: 640px) {
    .highlight-toolbar label {
      margin: 0 10px 0 3px;
    }

    .highlight-toolbar input[type="checkbox"] {
      display: block;
    }
  }

  .highlight-toolbar input[type="checkbox"]:hover {
    background-color: #0d37a9;
  }

  /* チェックボックスのスタイルのリセット */
  .highlight-toolbar input[type="checkbox"] {
    border-radius: 0;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
  }

  /* チェックボックスのスタイル */
  .highlight-toolbar input[type="checkbox"] {
    position: relative;
    width: 16px;
    height: 16px;
    border: 1px solid #333;
    cursor: pointer;
  }

  /* チェックマークのスタイル */
  .highlight-toolbar input[type="checkbox"]:checked:before {
    content: "";
    position: absolute;
    top: 0px;
    left: 4px;
    transform: rotate(45deg);
    width: 4px;
    height: 8px;
    border-right: 2px solid #bbb;
    border-bottom: 3px solid #bbb;
  }

  .highlight-toolbar input[type="checkbox"]:checked + label {
    color: #b9bfd0;
  }

  /* 言語名を表示 */
  code[data-language]::before {
    content: attr(data-language);
    position: absolute;
    top: 0;
    left: 0;
    color: #ccc;
    display: inline-block;
    padding: 0.5rem 1rem;
    /* コードの背景色と同じにする場合は background-color: #282c34; */
    background-color: #40547d;
    z-index: 5;
  }

  code[data-language].hide-line-num::before {
    left: 2.5rem;
  }

  /* ツールバーの中の言語名の表示 */
  .highlight-toolbar .lng-span {
    margin-right: auto;
    margin-left: 10px;
    font-size: 13px;
    color: #ccc;
  }

  /* コピーボタン(ツールバーを使用しない場合) */
  .hljs-copy-btn {
    position: absolute;
    top: 0;
    right: 0;
    background-color: #262b37;
    border: none;
    padding: 8px;
    color: #999;
    cursor: pointer;
    transition: color 0.3s, background-color 0.3s;
  }

  .hljs-copy-btn:hover {
    color: #eee;
    background-color: #162858;
  }

  /* ツールバーの中のコピーボタン */
  .highlight-toolbar .hljs-copy-btn {
    position: relative;
    margin: 0 10px;
    padding: 2px 4px;
  }

  /* ラベルとリンク(data-label 属性と data-label-url 属性で指定した文字列) */
  .hljs-label,
  .hljs-label-url {
    position: absolute;
    top: -2rem;
    right: 10px;
    color: #999;
    display: inline-block;
    padding: 0.5rem 0;
  }

  .hljs-label-url {
    color: #3987c7;
    text-decoration: none;
  }

  .hljs-label-url:hover {
    color: #55924f;
  }

  &.has-label {
    margin-top: 4rem;
  }

  /* 行番号(CSS カウンター)*/
  pre {
    counter-reset: lineNumber;
  }

  pre span.line-num::before {
    counter-increment: lineNumber;
    content: counter(lineNumber);
    min-width: 2.5rem;
    display: inline-block;
    color: #777;
    text-align: center;
    position: absolute;
    left: 0;
    background: #282c34;
    /* 行番号横の枠線が不要な場合は以下を削除 */
    border-right: 1px solid #595a60;
  }

  /* 行番号を非表示にする場合 */
  pre code.hide-line-num span.line-num::before {
    left: -2.5rem;
  }

  /* 行番号を非表示にする場合 */
  pre code.hide-line-num {
    margin-left: -2.5rem;
  }

  /* 行のハイライト時の行番号部分 */
  pre span.line-num.line-num-highlight::before {
    color: #c2c21a;
    /* background: #424638; */
  }

  /* 行のハイライト時のコード部分 */
  .line-highlight {
    position: absolute;
    /* 行番号の幅と合わせる */
    left: 2.5rem;
    width: calc(100% - 2.5rem);
    margin-left: -2.5rem;
    width: 100%;
    background: linear-gradient(
      to right,
      hsla(254, 15%, 51%, 0.2) 50%,
      hsla(254, 15%, 51%, 0.01)
    );
    pointer-events: none;
  }

  /* 行数を指定して表示する場合にコード下に表示する領域 */
  .scroll-footer {
    margin: 0;
    font-size: 0.75rem;
    color: #999;
    /* コードの背景色と同じ色 */
    background-color: #282c34;
    padding: 8px;
    display: flex;
    justify-content: flex-end;
  }

  /* .scroll-footer の右端に表示するテキスト*/
  .scroll-footer-text {
    margin-left: auto;
  }

  /* .scroll-footer-text の左側に表示するアイコン(色は fill='%23aaaaaa' の aaaaaa 部分) */
  .scroll-footer-text::before {
    content: "";
    display: inline-block;
    height: 0.875rem;
    width: 0.875rem;
    vertical-align: -2px;
    margin-right: 8px;
    background-repeat: no-repeat;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23aaaaaa' viewBox='0 0 16 16'%3E  %3Cpath fill-rule='evenodd' d='M11.5 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L11 2.707V14.5a.5.5 0 0 0 .5.5m-7-14a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V1.5a.5.5 0 0 1 .5-.5'/%3E%3C/svg%3E");
  }

  /* テーマ(atom-one-dark)のコメントの色を上書き */
  .hljs-comment {
    color: #6b788f;
  }
}

/* コードの表示・非表示( details 要素と summary 要素によるアコーディオン)*/
details.toggle-code-animation {

  border: none;
  margin: 2rem 0;

  .details-content-wrapper {
    padding: 1rem 0;
  }

  .details-content {
    overflow: hidden;
  }

  summary {
    display: inline-block;
    cursor: pointer;
    position: relative;
    padding: 0.5rem 0.5rem 0.5rem 36px;
    border: 1px solid #aaa;
    font-size: 13px;
  }

  summary::-webkit-details-marker {
    display: none;
  }

  summary::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 10px;
    margin: auto 0;
    width: 8px;
    height: 8px;
    border-top: 3px solid #097b27;
    border-right: 3px solid #097b27;
    transform: rotate(45deg);
  }
}
/* 行番号枠線用の span 要素を作成しない(使わない)バージョン updated on: 2024/03/31  */
document.addEventListener('DOMContentLoaded', () => {
  // Highlight.js のプラグインをセットアップ
  mySetUpHljsPlugins(myCustomHighlightJsSettings);
  // Highlight.js の初期化とカスタマイズの実行
  mySetupHighlightJs(myCustomHighlightJsSettings);
  // アコーディオンアニメーションの開閉パネルの追加(オプショナル)
  myAddAccordionPanel(myCustomHighlightJsSettings.accordionClassName);
  // アコーディオンアニメーションの呼び出し(オプショナル)
  mySetupToggleDetailsAnimation();
});

const myCustomHighlightJsSettings = {
  // pre code のラッパーのクラス名
  wrapperClassName: 'hljs-wrap',
  // pre 要素なし(インライン)の code 要素でもハイライトするかどうか
  useInlineHighlight: true,
  // インラインの code 要素でハイライトする場合に code 要素に指定するクラス(空の場合、全ての code 要素)
  inlineHighlightClassName: 'highlight',
  // アコーディオンパネルで表示場合にラッパー要素に指定するクラス名
  accordionClassName: 'toggle-accordion'
}

// Hightlight.js のプラグインを定義
function mySetUpHljsPlugins(settings) {
  const { wrapperClassName, useInlineHighlight } = settings;
  // デフォルトで行の自動折り返しを有効にするかどうか
  const preWrapOnInit = false;
  // 初期状態でコピーボタンを非表示
  const noCopyBtnOnInit = false;
  // 初期状態で行番号を非表示(ツールバー使用時のみ有効)
  const noLineNumOnInit = document.body.classList.contains('no-line-num') ? true  : false;
  // ツールバーを表示(使用)するかどうか(全てのページで使用しない場合は false を指定)
  const useToolbar = document.body.classList.contains('no-toolbar') ? false  : true;
  // 行数を指定して表示する場合の表示領域の高さの調整値
  const visibleHeightAdjustAmount = 0;
  // 行数を指定して表示する際に data-scroll-to を指定する場合の高さの調整値
  const scrollToHeightAdjustAmount = 0;
  // 行数を指定して表示する場合にスクロール量の調整値
  const scrollAdjustAmount = 0;
  // 行数を指定して表示する場合に折り返しや行番号表示の切り替えやウィンドウサイズの変更が発生した際にスクロール位置を元に戻すかどうか
  const resetScrollPosition = true;
  // 行数を指定して表示する場合にコードの下に情報領域(div.scroll-footer)を表示するかどうか
  const showScrollFooter = true;
  // 行数を指定して表示する場合にコードの下の情報領域(div.scroll-footer)に行数を表示するかどうか
  const showLineNumInfo = true;
  // ツールバーやボタンのテキスト(ラベル)
  const lineAutoWrapLabel = 'wrap';
  const lineNumLabel = 'number';
  const copyBtnLabel = 'Copy';
  const copyBtnCompleteLabel = 'Copied';
  const copyBtnFailedLabel = 'Failed';
  const copyFailedMessage = 'Sorry, can not copy with this browser.';
  const scrollableText = 'scrollable';

  // 最後の行の offsetTop と高さを取得する関数(lineNumSpans は行番号の全ての span 要素、el は code 要素)
  function getLastNumLineInfo(lineNumSpans, el) {
    // offsetTop と高さを格納するオブジェクト
    const info = {};
    const lastLineNumTop = lineNumSpans.item(lineNumSpans.length-1).offsetTop;
    // dummy 要素を追加して offsetTop の差分から現在の高さを取得
    const dummy = document.createElement('span');
    dummy.innerHTML = "<br>";
    el.appendChild(dummy);
    const dummy2 = document.createElement('span');
    el.appendChild(dummy2);
    const dummy2OffsetTop = dummy2.offsetTop;
    // offsetTop プロパティを設定
    info.offsetTop = dummy2OffsetTop;
    const lastLineNumHeight = dummy2OffsetTop - lastLineNumTop;
    // height プロパティを設定
    info.height = lastLineNumHeight
    dummy.remove();
    dummy2.remove();
    return info;
  }

  // data-max-lines 属性が指定された場合の表示領域の高さを設定及び更新する関数
  function updateVisibleHeight(pre, el, isSetMaxHeight = false) {
    let maxLineValue = null;
    const lineNumSpans = el.getElementsByClassName('line-num');
    if(pre.hasAttribute('data-max-lines') && lineNumSpans.length > 0) {
      const dataMaxLines = parseInt(pre.getAttribute('data-max-lines'));
      if(dataMaxLines && dataMaxLines >0 && dataMaxLines < lineNumSpans.length){
        maxLineValue = dataMaxLines;
      }
    }
    if(maxLineValue) {
      const elComputedStyle = window.getComputedStyle(el);
      const elPaddingTop = parseFloat(elComputedStyle.paddingTop);
      const elPaddingBottom = parseFloat(elComputedStyle.paddingBottom);
      const elPaddingY = elPaddingTop + elPaddingBottom;
      if (lineNumSpans.item(maxLineValue)) {
        // data-max-lines-offset の値を取得
        const maxLineValueOffset = pre.getAttribute('data-max-lines-offset') ? parseInt(pre.getAttribute('data-max-lines-offset')): 0;
        // 表示する最後の行の次の行の span 要素の offsetTop
        const maxNextOffsetTop = lineNumSpans.item(maxLineValue).offsetTop;
        const visibleHeight = maxNextOffsetTop + maxLineValueOffset - elPaddingY + visibleHeightAdjustAmount;
        el.style.setProperty('height', visibleHeight + 'px');
        el.style.setProperty('overflow-y', 'scroll');
        if(pre.classList.contains('no-scroll')) {
          el.style.setProperty('overflow-y', 'hidden');
        }
      }
      // data-scroll-to 属性が指定されている場合
      if (pre.hasAttribute('data-scroll-to')) {
        let dataScrollTo = parseInt(pre.getAttribute('data-scroll-to'));
        if (pre.hasAttribute('data-line-num-start')) {
          const startNumber = parseInt(pre.getAttribute('data-line-num-start'));
          if(startNumber) {
            dataScrollTo -= startNumber -1;
          }
        }
        if(dataScrollTo + maxLineValue > lineNumSpans.length + 1) {
          console.log(`data-scroll-to: ${dataScrollTo} or data-max-line : ${maxLineValue} is not valid.`)
          return;
        }
        if (dataScrollTo && lineNumSpans.item(dataScrollTo - 1) ) {
          const scrollToOffsetTop = lineNumSpans.item(dataScrollTo - 1).offsetTop;
          const maxLineValueOffset = pre.getAttribute('data-max-lines-offset') ? parseInt(pre.getAttribute('data-max-lines-offset')): 0;
          let visibleHeight;
          if(lineNumSpans.item(dataScrollTo + maxLineValue - 1)) {
            const lastRowOffsetTop = lineNumSpans.item(dataScrollTo + maxLineValue - 1).offsetTop;
            visibleHeight = lastRowOffsetTop - scrollToOffsetTop + maxLineValueOffset + scrollToHeightAdjustAmount - elPaddingY;
          }else if(dataScrollTo + maxLineValue === lineNumSpans.length + 1 && lineNumSpans.item(dataScrollTo) ) {
            // スクロールにより最終行を表示する場合
            const lastLineOffsetTop = getLastNumLineInfo(lineNumSpans, el).offsetTop;
            visibleHeight = lastLineOffsetTop - scrollToOffsetTop + maxLineValueOffset + scrollToHeightAdjustAmount - elPaddingTop;
          }
          el.style.setProperty('height', visibleHeight + 'px');
          if(isSetMaxHeight) {
            // 行数を指定して高さを設定する関数 setMaxHeight() での呼び出しの場合(一度スクロールを実行して終了)
            const scrollAmount = scrollToOffsetTop - scrollAdjustAmount;
            el.scroll(0, scrollAmount);
            return;
          }
          if(resetScrollPosition) {
            const scrollAmount = scrollToOffsetTop - scrollAdjustAmount;
            el.scroll(0, scrollAmount);
          }
        }
      }
    }
  }

  // Hightlight.js の addPlugin() でプラグインを定義
  hljs.addPlugin({
    'after:highlightElement': ({ el, result, text }) => {
      // ラッパー要素
      const wrapper = el.closest('.' + wrapperClassName);
      // pre 要素(親要素)
      const pre = el.parentElement;
      if(wrapper && pre) {
        showLanguage(el, result, wrapper);
        copyCode(text, pre, wrapper);
        addLineNumbers(el, result, wrapper, pre);
        highlightNumbers(el, pre);
        setMaxHeight(el, wrapper, pre);
        setUpWrapper(el, wrapper, pre);
      }
    }
  });

  // 言語名を表示する関数
  function showLanguage(el, result, wrapper) {
    if (el.classList.contains('show-no-lang')) {
      if (wrapper) wrapper.classList.add('no-lang');
      return;
    }
    if (el.hasAttribute('data-set-lang')) {
      addLanguageSpan(el.getAttribute('data-set-lang'));
      return;
    }
    if (result.language) {
      if (useToolbar) {
        addLanguageSpan(result.language);
      } else {
        el.dataset.language = result.language;
      }
    }
    function addLanguageSpan(language) {
      const languageSpan = document.createElement('span');
      languageSpan.setAttribute('class', 'lng-span');
      languageSpan.textContent = language;
      const wrapper = el.closest('.' + wrapperClassName);
      if (wrapper && !wrapper.classList.contains('no-toolbar')) {
        wrapper.appendChild(languageSpan);
      } else if (wrapper && wrapper.classList.contains('no-toolbar')) {
        el.dataset.language = language;
      }
    }
  }

  // コードをコピーする関数 (addPlugin で呼び出す)
  function copyCode(text, pre, wrapper) {
    const preClass = pre.classList;
    if (preClass.contains('no-copy-btn')) return;
    if (noCopyBtnOnInit && !preClass.contains('show-copy-btn')) return;
    if (useInlineHighlight && pre.nodeName !== 'PRE') return;
    const copyButton = document.createElement('button');
    copyButton.setAttribute('class', 'hljs-copy-btn');
    copyButton.textContent = copyBtnLabel;
    pre.after(copyButton);
    wrapper.classList.add('has-copy-btn');
    copyButton.addEventListener('click', () => {
      copyToClipboard(copyButton, text)
    });
    function copyToClipboard(btn, text) {
      if (!navigator.clipboard) {
        alert(copyFailedMessage);
      }
      // data-max-lines 属性と no-scroll クラスが指定されている場合は、表示されている部分のみをコピー
      if(preClass.contains('no-scroll') && pre.hasAttribute('data-max-lines')) {
        let startLine =  1;
        let endLine = parseInt(pre.getAttribute('data-max-lines'));
        if(pre.hasAttribute('data-scroll-to')) {
          const scrollTo = parseInt(pre.getAttribute('data-scroll-to'));
          if(scrollTo) {
            startLine = scrollTo;
            endLine += scrollTo -1;
          }
        }
        if (pre.hasAttribute('data-line-num-start')) {
          const startNumber = parseInt(pre.getAttribute('data-line-num-start'));
          if(startNumber) {
            startLine -= startNumber -1;
            endLine -= startNumber -1;
          }
        }
        // text を改行で分割
        const textArray = text.split(/\r?\n/);
        let visibleText = '';
        if(startLine >=1 && endLine < textArray.length){
          for(let i=startLine-1; i<=endLine-1; i++) {
            if(i !== endLine-1) {
              visibleText += textArray[i] + "\n";
            }else{
              visibleText += textArray[i];
            }
          }
        }
        text = visibleText;
      }
      // プロンプト文字($ と %)を除外してコピー
      if (preClass.contains('copy-no-prompt')) {
        text = text.replace(/^\$\s|^%\s/gm, '');
      }
      // シングルラインコメントを除外してコピー
      if (preClass.contains('copy-no-sl-comments') || preClass.contains('copy-no-comments')) {
        // 行の途中の「半角スペース + //」も削除。 [^\S\r\n] は改行を除く空白にマッチ(コメント以外も削除する可能性あり)
        text = text.replace(/^([^\S\r\n]*\/\/).*$\r?\n?/gm, "").replace(/(.*)\s\/\/.*/g, "$1");
      }
      // マルチラインコメントを除外してコピー
      if (preClass.contains('copy-no-ml-comments') || preClass.contains('copy-no-comments')) {
        // replace() の第2引数に関数 replaceComments を指定(正しくマッチしない可能性あり)
        text = text.replace(/^(.*)\/\*[\s\S]*?\*\/($\r?\n?)?/gm, replaceComments)
      }
      // HTML コメントを除外してコピー
      if (preClass.contains('copy-no-html-comments')) {
        // replace() の第2引数に関数 replaceComments を指定
        text = text.replace(/^(.*)<!\-\-[\s\S]*?\-\->($\r?\n?)?/gm, replaceComments)
      }
      function replaceComments(match, p1, p2) {
        // コメントの後に改行がない場合(p2 は undefined)
        if (!p2) p2 = '';
        // コメントの前が空白文字の場合
        if (!p1.trim()) {
          if(p2) {
            return '';
          }
          return p1;
        } else {
          return p1 + p2;
        }
      }
      navigator.clipboard.writeText(text).then(
        () => {
          btn.textContent = copyBtnCompleteLabel;
          resetCopyBtnText(btn, 1500);
        },
        (error) => {
          btn.textContent = copyBtnFailedLabel;
          resetCopyBtnText(btn, 1500);
          console.log(error.message);
        }
      );
    };
    function resetCopyBtnText(btn, delay) {
      setTimeout(() => {
        btn.textContent = copyBtnLabel
      }, delay)
    }
  }

  // 行番号表示する関数
  function addLineNumbers(el, result, wrapper, pre) {
    el.innerHTML = result.value.replace( /^/gm, '<span class="line-num"></span>');
    let startNumOffset = 0;
    if (pre.hasAttribute('data-line-num-start')) {
      const startNumber = parseInt(pre.getAttribute('data-line-num-start'));
      if (startNumber || startNumber === 0) {
        pre.style.setProperty('counter-reset', 'lineNumber ' + (startNumber - 1));
        startNumOffset = startNumber - 1;
      }
    }
    if (wrapper) {
      // ResizeObserver で code 要素のサイズを監視
      const codeObserver = new ResizeObserver((entries) => {
        if (entries.length > 0 && entries[0]) {
          // 行番号用の span 要素を全て取得
          const lineNumSpans = el.getElementsByClassName("line-num");
          // ハイライト行の高さの更新
          const lineNumSpansArray =  Array.prototype.slice.call( lineNumSpans );
          // HTMLCollection を配列に変換して処理
          lineNumSpansArray.forEach( (elem, index) => {
            // line-num-highlight クラスが指定されていれば高さを更新
            if(elem.classList.contains('line-num-highlight')) {
              // ハイライト用の span 要素(span.line-highlight)
              const highlightSpan = lineNumSpans[index].nextElementSibling;
              if(highlightSpan) {
                if(lineNumSpans.item(index) && lineNumSpans.item(index +1)) {
                  const spanOffsetTop =  lineNumSpans.item(index).offsetTop;
                  const nextSpanOffsetTop =  lineNumSpans.item(index +1).offsetTop;
                  const height = nextSpanOffsetTop - spanOffsetTop;
                  if(height !== 0) {
                    highlightSpan.style.setProperty('height', height + 'px');
                  }
                }else if(lineNumSpans.item(index) && index === lineNumSpans.length - 1) {
                  // 最後の行の場合(次の行がないのでダミーを挿入して取得)
                  const lastLineNumHeight = getLastNumLineInfo(lineNumSpans, el).height;
                  if(lastLineNumHeight !== 0) {
                    highlightSpan.style.setProperty('height', lastLineNumHeight + 'px');
                  }
                }
              }
            }
          });
          // 表示領域の高さを更新
          updateVisibleHeight(pre, el)
        }
      });
      codeObserver.observe(el);
    }
  }

  //指定された行をハイライト表示する関数
  function highlightNumbers(el, pre) {
     // pre 要素に data-line-highlight 属性が指定されていれば
    if (pre.hasAttribute('data-line-highlight')) {
      const targetLines = pre.getAttribute('data-line-highlight');
      const highlightCode = pre.classList.contains('no-highlight-code') ? false : true;
      const highlightNumber = pre.classList.contains('no-highlight-number') ? false : true;
      const targets = targetLines.split(',').map((val) => val.trim());
      if (targets.length > 0) {
        const lineNumSpans = el.getElementsByClassName('line-num');
        const lineLength = lineNumSpans.length;
        targets.forEach((target) => {
          let startNumOffset = 0;
          if (pre.hasAttribute('data-line-num-start')) {
            const startNumber = parseInt(pre.getAttribute('data-line-num-start'));
            if (startNumber || startNumber === 0) {
              startNumOffset = startNumber - 1;
            }
          }
          const range = target.split('-');
          if (range.length === 2) {
            if (range[0] !== '') {
              const start = startNumOffset === 0 ? parseInt(range[0]) : parseInt(range[0]) - startNumOffset;
              const end = startNumOffset === 0 ? parseInt(range[1]) : parseInt(range[1]) - startNumOffset;
              if (start && end) {
                if (end >= start) {
                  for (let i = start; i <= end; i++) {
                    addClassToSpan(i);
                  }
                } else {
                  for (let i = end; i <= start; i++) {
                    addClassToSpan(i);
                  }
                }
              }
            } else {
              const negativeNum = (startNumOffset === 0 ? parseInt(range[1]) : parseInt(range[1])) * -1;
              addClassToSpan(negativeNum - startNumOffset);
            }
          } else if (range.length === 1) {
            addClassToSpan(startNumOffset === 0 ? parseInt(target) : parseInt(target) - startNumOffset);
          }
          function addClassToSpan(number) {
            if (number > 0 && number <= lineLength) {
              if (highlightCode) {
                const highlightSpan = document.createElement('span');
                highlightSpan.className = 'line-highlight';
                lineNumSpans.item(number - 1).after(highlightSpan);
              }
              if (highlightNumber) {
                lineNumSpans.item(number - 1).classList.add('line-num-highlight');
              }
            }
          }
        })
      }
    }
  }

  // 表示する行数を指定して表示領域(code 要素に height)を設定する関数
  function setMaxHeight(el, wrapper, pre) {
    if (!pre.hasAttribute('data-max-lines')) return;
    if (!wrapper) return;
    // 表示領域(code 要素に height)を設定
    updateVisibleHeight(pre, el, true);
    if(pre.classList.contains('no-scroll')) return;
    // コードの下に情報領域(div.scroll-footer)を表示
    if(showScrollFooter && !pre.classList.contains('no-scroll-footer') || !showScrollFooter && pre.classList.contains('show-scroll-footer')) {
      const footerText = pre.hasAttribute('data-footer-text') ? pre.getAttribute('data-footer-text') : scrollableText;
      wrapper.insertAdjacentHTML('beforeend', `<div class="scroll-footer"><span class="scroll-footer-text">${footerText}</span></div>`);
      const scrollFooter = wrapper.querySelector('.scroll-footer');
      const maxLineValue = pre.getAttribute('data-max-lines');
      const lineNumSpans = el.getElementsByClassName('line-num');
      if(showLineNumInfo && !pre.classList.contains('no-line-info') || !showLineNumInfo && pre.classList.contains('show-line-info')) {
        scrollFooter.insertAdjacentHTML('afterbegin', `<span class="line-count">Showing ${maxLineValue} of ${lineNumSpans.length} lines</span>`);
      }
    }
  }

  // ラッパー要素にラベルやツールバーを表示する関数
  let index = 0;
  function setUpWrapper(el, wrapper, pre) {
    const preClass = pre.classList;
    const wrapperClass = wrapper.classList;
    if(preWrapOnInit) preClass.add('pre-wrap');
    // ラベルの追加
    if (pre.hasAttribute('data-label')) {
      const label = pre.getAttribute('data-label');
      let element;
      if (pre.hasAttribute('data-label-url')) {
        element = document.createElement('a');
        element.href = pre.getAttribute('data-label-url');
        element.classList.add('hljs-label-url');
        if (preClass.contains('target-blank')) {
          element.target = "_blank";
          element.rel = "noopener";
        }
      } else {
        element = document.createElement('span');
        element.classList.add('hljs-label');
      }
      element.textContent = label;
      wrapper.appendChild(element);
      wrapperClass.add('has-label');
    }
    // no-line-num クラスを指定した要素の行番号を非表示
    if (preClass.contains('no-line-num')) {
      el.classList.add('hide-line-num');
    }

    // ツールバーの追加
    if (useToolbar) {
      if (!wrapperClass.contains('no-toolbar')) {
        const toolbar = document.createElement('div');
        toolbar.setAttribute('class', 'highlight-toolbar');
        const noLineNumChecked = noLineNumOnInit ? '' : ' checked';
        let lineWrapChecked = preWrapOnInit ? ' checked' : '';
        if(preClass.contains('pre')) {
          lineWrapChecked = '';
        }else if(preClass.contains('pre-wrap')){
          lineWrapChecked = ' checked';
        }
        toolbar.innerHTML = `<input type="checkbox" id="line-auto-wrap${index}" name="line-auto-wrap"${lineWrapChecked}>
<label for="line-auto-wrap${index}">${lineAutoWrapLabel}</label>`;
        const noLineNum = preClass.contains('no-line-num');
        if (!noLineNum) {
          toolbar.insertAdjacentHTML('afterbegin', `<input type="checkbox" id="line-num-check${index}" name="line-num-check"${noLineNumChecked}>
<label for="line-num-check${index}">${lineNumLabel}</label>`);
        }
        wrapper.insertBefore(toolbar, wrapper.firstElementChild);
        const lineNumCheck = toolbar.querySelector('[name="line-num-check"]');
        if (lineNumCheck) {
          lineNumCheck.addEventListener('change', (e) => {
            if (e.currentTarget.checked) {
              el.classList.remove('hide-line-num');
            } else {
              el.classList.add('hide-line-num');
            }
          });
          if (noLineNumOnInit) {
            el.classList.add('hide-line-num');
          }
        }
        const lineAutoWrapCheck = toolbar.querySelector('[name="line-auto-wrap"]');
        lineAutoWrapCheck.addEventListener('change', (e) => {
          // white-space プロパティを変更
          if (e.currentTarget.checked) {
            pre.style.setProperty('white-space', 'pre-wrap');
          } else {
            pre.style.setProperty('white-space', 'pre');
          }
          // 表示領域の高さを更新
          updateVisibleHeight(pre, el)
        });
        const langSpan = wrapper.querySelector('.lng-span');
        if (langSpan) {
          toolbar.insertBefore(langSpan, toolbar.firstElementChild)
        }
        const copyBtn = wrapper.querySelector('.hljs-copy-btn');
        if (copyBtn) {
          toolbar.appendChild(copyBtn)
        }
      }
    }
    index ++;
  }
}

// Highlight.js の初期化とカスタマイズを適用する関数。
// targetWrapper は単一の要素のみに適用する場合に指定(WordPress のエディタでのプレビュー用に使用する場合に指定)
function mySetupHighlightJs(settings, targetWrapper = false) {
  const { wrapperClassName, useInlineHighlight, inlineHighlightClassName } = settings;
  // 全てのラッパー要素を取得
  const wrappers = document.getElementsByClassName(wrapperClassName);
  // インラインでハイライトする場合
  if (useInlineHighlight) {
    // pre 要素なしの code 要素でハイライト
    const inlineHighlightElems = document.getElementsByClassName(inlineHighlightClassName);
    for (const elem of inlineHighlightElems) {
      if (elem.parentElement.nodeName !== 'PRE') {
        hljs.highlightElement(elem);
      }
    }
  }
  // Highlight.js の初期化とセットアップ
  if (wrappers.length > 0 && !targetWrapper) {
    for (const wrapper of wrappers) {
      hljs.highlightElement(wrapper.querySelector('pre code'));
    }
  }else if (targetWrapper) {
    const code = targetWrapper.querySelector('code');
    if(code) {
      hljs.highlightElement(code);
    }
  }
}

// 開閉パネル(アコーディオンパネル)を指定されたクラスを持つ要素(または第2引数で渡された単一の要素)に追加する関数
function myAddAccordionPanel(targetClassName, elem = null) {
  // details 要素に付与するクラス
  const detailsClass = 'toggle-code-animation';
  // details 要素内のコンテンツを格納する div 要素に付与するクラス
  const detailsContentClass = 'details-content';
  // details 要素内のコンテンツを格納する要素のラッパーに付与するクラス
  const detailsContentWrapperClass = 'details-content-wrapper';
  // 第2引数の elem が指定されていなければ、第1引数のクラス名を使って要素を取得してパネルを追加
  if(!elem) {
    // パネルを追加する要素を取得
    const targetElems = document.getElementsByClassName(targetClassName);
    for (const elem of targetElems) {
      addPanel(elem);
    }
  }else{
    // 第2引数の elem が指定されていれば、その要素にパネルを追加
    addPanel(elem);
  }
  // アコーディオンパネルを受け取った要素に追加する関数
  function addPanel(elem) {
    // アコーディオンパネルを開くボタンのテキスト
    let summaryOpenText = "Open";
    // アコーディオンパネルを閉じるボタンのテキスト
    let summaryCloseText = "Close";
    if (elem) {
      if(elem.hasAttribute('data-open-text')) {
        summaryOpenText = elem.getAttribute('data-open-text');
      }
      if(elem.hasAttribute('data-close-text')) {
        summaryCloseText = elem.getAttribute('data-close-text');
      }
      // details 要素を作成
      const detailsElem = document.createElement('details');
      detailsElem.classList.add(detailsClass);
      // 作成した details 要素の HTML(summary 要素と div 要素)を設定
      detailsElem.innerHTML = `<summary data-close-text="${summaryCloseText}">${summaryOpenText}</summary>
  <div class="${detailsContentWrapperClass}">
    <div class="${detailsContentClass}"></div>
  </div>`;
      // コードブロックのラッパー要素を details 要素でラップする
      elem.insertAdjacentElement('beforebegin', detailsElem);
      detailsElem.querySelector('.' + detailsContentClass).appendChild(elem);
    }
  }
}

// アコーディオンアニメーションの関数の定義(elem は特定の要素のみに適用する場合に指定)
function mySetupToggleDetailsAnimation(elem) {
  // ボタンのラベル(summary 要素のテキストが空の場合)
  const accodionOpenBtnDefaultLabel = 'Open';
  // 閉じるボタンのラベル(summary 要素に data-close-text 属性が指定されていない場合)
  const accodionCloseBtnDefaultLabel = 'Close';
  // toggle-code-animation クラスの details 要素を全て取得
  const details = document.getElementsByClassName('toggle-code-animation');
  // 引数 elem が指定されていればその要素のみを対象に setupAccordion() を呼び出す(WordPress のプレビューモード用)
  if(elem) {
    setupAccordion(elem);
  }else{
    for(const elem of details) {
      setupAccordion(elem);
    }
  }
  // アコーディオンアニメーションを設定
  function setupAccordion(elem) {
    const summary = elem.querySelector('summary');
    const content = elem.querySelector('.details-content');
    const summaryText = summary.textContent.trim() ? summary.textContent : accodionOpenBtnDefaultLabel;
    if (!summary.textContent.trim()) summary.textContent = summaryText;
    const summaryCloseText = summary.dataset.closeText ? summary.dataset.closeText : accodionCloseBtnDefaultLabel;
    let isAnimating = false;
    summary.addEventListener('click', (e) => {
      e.preventDefault();
      if (isAnimating === true) {
        return;
      }
      if (elem.open) {
        summary.textContent = summaryText;
        isAnimating = true;
        const closeDetails = content.animate(
          {
            opacity: [1, 0],
            height: [content.offsetHeight + 'px', 0],
          },
          {
            duration: 300,
            easing: 'ease-in',
          }
        );
        const rotateIcon = summary.animate(
          { rotate: ["90deg", "0deg"] },
          {
            duration: 300,
            pseudoElement: "::before",
            easing: 'ease-in',
            fill: 'forwards',
          }
        );
        closeDetails.onfinish = () => {
          elem.removeAttribute('open');
          isAnimating = false;
        }
      } else {
        elem.setAttribute('open', 'true');
        summary.textContent = summaryCloseText;
        isAnimating = true;
        const openDetails = content.animate(
          {
            opacity: [0, 1],
            height: [0, content.offsetHeight + 'px'],
          },
          {
            duration: 300,
            easing: 'ease-in',
          }
        );
        const rotateIcon = summary.animate(
          { rotate: ["0deg", "90deg"] },
          {
            duration: 300,
            pseudoElement: "::before",
            easing: 'ease-in',
            fill: 'forwards',
          }
        );
        openDetails.onfinish = () => {
          isAnimating = false;
        }
      }
    });
  }
};

行番号の枠線を表示しない

custom-nbs.css でボーダーの指定(232行目)を削除した例です。この場合、枠線がないので問題ありません。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hilight.js Sample</title>
  <!-- Hilight.js テーマ CSS の読み込み -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css" integrity="sha512-Jk4AqjWsdSzSWCSuQTfYRIF84Rq/eV0G2+tu07byYwHcbTGfdmLrHjUSwvzp5HvbiqK4ibmNwdcG49Y5RGYPTg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  <style>
    /* 以下のサンプルの CSS(custom.css)をコピペ */
  </style>
</head>
<body>

  <div class="hljs-wrap">
    <pre><code>ハイライト表示するコードを記述</code></pre>
  </div>

  <!-- Hilight.js JavaScript の読み込み -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <script>
    // 以下のサンプルの JavaScript(custom.js)をコピペ
  </script>
</body>
</html>

疑似要素に height: 100%

行番号の疑似要素(span.line-num::before)に height: 100% を指定すると、

.hljs-wrap pre span.line-num::before {
  /*・・・中略・・・ */
  height: 100%;
}

以下のように、自動折り返しを有効にしてもボーダーは途切れませんが、

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hilight.js Sample</title>
  <!-- Hilight.js テーマ CSS の読み込み -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css" integrity="sha512-Jk4AqjWsdSzSWCSuQTfYRIF84Rq/eV0G2+tu07byYwHcbTGfdmLrHjUSwvzp5HvbiqK4ibmNwdcG49Y5RGYPTg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  <style>
    /* 以下のサンプルの CSS(custom.css)をコピペ */
  </style>
</head>
<body>

  <div class="hljs-wrap">
    <pre><code>ハイライト表示するコードを記述</code></pre>
  </div>

  <!-- Hilight.js JavaScript の読み込み -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <script>
    // 以下のサンプルの JavaScript(custom.js)をコピペ
  </script>
</body>
</html>

data-max-lines 属性で表示する行数を指定した場合、height: 100% により最後の部分に余分な空白が表示されてしまいます。

以下は data-max-lines="10" を指定した場合の例です。スクロールすると最後の行の下に余分な空白があるのを確認できます

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hilight.js Sample</title>
  <!-- Hilight.js テーマ CSS の読み込み -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css" integrity="sha512-Jk4AqjWsdSzSWCSuQTfYRIF84Rq/eV0G2+tu07byYwHcbTGfdmLrHjUSwvzp5HvbiqK4ibmNwdcG49Y5RGYPTg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  <style>
    /* 以下のサンプルの CSS(custom.css)をコピペ */
  </style>
</head>
<body>

  <div class="hljs-wrap">
    <pre><code>ハイライト表示するコードを記述</code></pre>
  </div>

  <!-- Hilight.js JavaScript の読み込み -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <script>
    // 以下のサンプルの JavaScript(custom.js)をコピペ
  </script>
</body>
</html>

Highlight.js のカスタマイズ サンプル ページへ