以下はハイライトされる行の高さを取得する処理を行わない例です。
この場合、ハイライト表示を指定した行が、自動折り返しする場合、折り返し部分はハイライトしません。
<!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>
CSS で、.line-highlight にハイライトする行の高さを指定しています。この高さを削除するか、.line-highlight の設定を削除すると、行番号部分のみがハイライトされます。
/* updated (changed) on: 2024/03/29 */
/* pre 要素のラッパー要素(div)に指定するクラス( CSS ネスティングを使用)*/
.hljs-wrap {
position: relative;
pre {
overflow-wrap: break-word;
overflow-x: hidden;
padding: 0;
margin: 0;
}
pre.pre-wrap {
white-space: pre-wrap;
}
pre.pre {
white-space: pre;
}
pre code {
padding-left: 3rem;
position: relative;
white-space: inherit;
overflow-y: hidden;
}
&.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: #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;
}
.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;
}
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;
}
.line-highlight {
position: absolute;
left: 2.5rem;
width: calc(100% - 2.5rem);
margin-left: -2.5rem;
width: 100%;
/* 高さを指定 */
height: 1.1rem;
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");
}
.hljs-comment {
color: #6b788f;
}
}
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);
}
}
/* シンプルバージョン 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';
// 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 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 lastLineOffsetTop = dummy2.offsetTop;
dummy.remove();
dummy2.remove();
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;
}
}
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");
// 表示領域の高さを更新
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;
}
}
});
}
};
コード部分をハイライトしない
CSS で .line-highlight の高さを削除すると、以下のように行番号部分のみがハイライトされます。
<!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>