Skip to content

Commit

Permalink
Line Numbers: Improved resize performance (#2125)
Browse files Browse the repository at this point in the history
  • Loading branch information
RunDevelopment committed Jun 28, 2020
1 parent 681adee commit b96ed22
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 60 deletions.
193 changes: 134 additions & 59 deletions plugins/line-numbers/prism-line-numbers.js
Expand Up @@ -16,20 +16,86 @@
*/
var NEW_LINE_EXP = /\n(?!$)/g;


/**
* Resizes line numbers spans according to height of line of code
* @param {Element} element <pre> element
* Global exports
*/
var _resizeElement = function (element) {
var codeStyles = getStyles(element);
var whiteSpace = codeStyles['white-space'];
var config = Prism.plugins.lineNumbers = {
/**
* Get node for provided line number
* @param {Element} element pre element
* @param {Number} number line number
* @return {Element|undefined}
*/
getLine: function (element, number) {
if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
return;
}

var lineNumberRows = element.querySelector('.line-numbers-rows');
var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);

if (number < lineNumberStart) {
number = lineNumberStart;
}
if (number > lineNumberEnd) {
number = lineNumberEnd;
}

var lineIndex = number - lineNumberStart;

return lineNumberRows.children[lineIndex];
},

if (whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line') {
/**
* Resizes the line numbers of the given element.
*
* This function will not add line numbers. It will only resize existing ones.
* @param {HTMLElement} element A `<pre>` element with line numbers.
* @returns {void}
*/
resize: function (element) {
resizeElements([element]);
},

/**
* Whether the plugin can assume that the units font sizes and margins are not depended on the size of
* the current viewport.
*
* Setting this to `true` will allow the plugin to do certain optimizations for better performance.
*
* Set this to `false` if you use any of the following CSS units: `vh`, `vw`, `vmin`, `vmax`.
*
* @type {boolean}
*/
assumeViewportIndependence: true
};

/**
* Resizes the given elements.
*
* @param {HTMLElement[]} elements
*/
function resizeElements(elements) {
elements = elements.filter(function (e) {
var codeStyles = getStyles(e);
var whiteSpace = codeStyles['white-space'];
return whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line';
});

if (elements.length == 0) {
return;
}

var infos = elements.map(function (element) {
var codeElement = element.querySelector('code');
var lineNumbersWrapper = element.querySelector('.line-numbers-rows');
if (!codeElement || !lineNumbersWrapper) {
return;
return undefined;
}

/** @type {HTMLElement} */
var lineNumberSizer = element.querySelector('.line-numbers-sizer');
var codeLines = codeElement.textContent.split(NEW_LINE_EXP);

Expand All @@ -40,18 +106,63 @@
codeElement.appendChild(lineNumberSizer);
}

lineNumberSizer.innerHTML = '0';
lineNumberSizer.style.display = 'block';

codeLines.forEach(function (line, lineNumber) {
lineNumberSizer.textContent = line || '\n';
var lineSize = lineNumberSizer.getBoundingClientRect().height;
lineNumbersWrapper.children[lineNumber].style.height = lineSize + 'px';
var oneLinerHeight = lineNumberSizer.getBoundingClientRect().height;
lineNumberSizer.innerHTML = '';

return {
element: element,
lines: codeLines,
lineHeights: [],
oneLinerHeight: oneLinerHeight,
sizer: lineNumberSizer,
};
}).filter(Boolean);

infos.forEach(function (info) {
var lineNumberSizer = info.sizer;
var lines = info.lines;
var lineHeights = info.lineHeights;
var oneLinerHeight = info.oneLinerHeight;

lineHeights[lines.length - 1] = undefined;
lines.forEach(function (line, index) {
if (line && line.length > 1) {
var e = lineNumberSizer.appendChild(document.createElement('span'));
e.style.display = 'block';
e.textContent = line;
} else {
lineHeights[index] = oneLinerHeight;
}
});
});

infos.forEach(function (info) {
var lineNumberSizer = info.sizer;
var lineHeights = info.lineHeights;

var childIndex = 0;
for (var i = 0; i < lineHeights.length; i++) {
if (lineHeights[i] === undefined) {
lineHeights[i] = lineNumberSizer.children[childIndex++].getBoundingClientRect().height;
}
}
});

infos.forEach(function (info) {
var lineNumberSizer = info.sizer;
var wrapper = info.element.querySelector('.line-numbers-rows');

lineNumberSizer.textContent = '';
lineNumberSizer.style.display = 'none';
}
};
lineNumberSizer.innerHTML = '';

info.lineHeights.forEach(function (height, lineNumber) {
wrapper.children[lineNumber].style.height = height + 'px';
});
});
}

/**
* Returns style declarations for the element
Expand All @@ -65,8 +176,14 @@
return window.getComputedStyle ? getComputedStyle(element) : (element.currentStyle || null);
};

var lastWidth = undefined;
window.addEventListener('resize', function () {
Array.prototype.forEach.call(document.querySelectorAll('pre.' + PLUGIN_NAME), _resizeElement);
if (config.assumeViewportIndependence && lastWidth === window.innerWidth) {
return;
}
lastWidth = window.innerWidth;

resizeElements(Array.prototype.slice.call(document.querySelectorAll('pre.' + PLUGIN_NAME)));
});

Prism.hooks.add('complete', function (env) {
Expand All @@ -75,7 +192,7 @@
}

var code = /** @type {Element} */ (env.element);
var pre = /** @type {Element} */ (code.parentNode);
var pre = /** @type {HTMLElement} */ (code.parentNode);

// works only for <code> wrapped inside <pre> (not inline)
if (!pre || !/pre/i.test(pre.nodeName)) {
Expand Down Expand Up @@ -114,7 +231,7 @@

env.element.appendChild(lineNumbersWrapper);

_resizeElement(pre);
resizeElements([pre]);

Prism.hooks.run('line-numbers', env);
});
Expand All @@ -124,46 +241,4 @@
env.plugins.lineNumbers = true;
});

/**
* Global exports
*/
Prism.plugins.lineNumbers = {
/**
* Returns the node of the given line number in the given element.
* @param {Element} element A `<pre>` element with line numbers.
* @param {Number} number
* @returns {Element | undefined}
*/
getLine: function (element, number) {
if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
return;
}

var lineNumberRows = element.querySelector('.line-numbers-rows');
var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);

if (number < lineNumberStart) {
number = lineNumberStart;
}
if (number > lineNumberEnd) {
number = lineNumberEnd;
}

var lineIndex = number - lineNumberStart;

return lineNumberRows.children[lineIndex];
},
/**
* Resizes the line numbers of the given element.
*
* This function will not add line numbers. It will only resize existing ones.
* @param {Element} element A `<pre>` element with line numbers.
* @returns {void}
*/
resize: function (element) {
_resizeElement(element);
}
};

}());
2 changes: 1 addition & 1 deletion plugins/line-numbers/prism-line-numbers.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b96ed22

Please sign in to comment.