Skip to content

Commit b96ed22

Browse files
authoredJun 28, 2020
Line Numbers: Improved resize performance (#2125)
1 parent 681adee commit b96ed22

File tree

2 files changed

+135
-60
lines changed

2 files changed

+135
-60
lines changed
 

‎plugins/line-numbers/prism-line-numbers.js

+134-59
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,86 @@
1616
*/
1717
var NEW_LINE_EXP = /\n(?!$)/g;
1818

19+
1920
/**
20-
* Resizes line numbers spans according to height of line of code
21-
* @param {Element} element <pre> element
21+
* Global exports
2222
*/
23-
var _resizeElement = function (element) {
24-
var codeStyles = getStyles(element);
25-
var whiteSpace = codeStyles['white-space'];
23+
var config = Prism.plugins.lineNumbers = {
24+
/**
25+
* Get node for provided line number
26+
* @param {Element} element pre element
27+
* @param {Number} number line number
28+
* @return {Element|undefined}
29+
*/
30+
getLine: function (element, number) {
31+
if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
32+
return;
33+
}
34+
35+
var lineNumberRows = element.querySelector('.line-numbers-rows');
36+
var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
37+
var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);
38+
39+
if (number < lineNumberStart) {
40+
number = lineNumberStart;
41+
}
42+
if (number > lineNumberEnd) {
43+
number = lineNumberEnd;
44+
}
45+
46+
var lineIndex = number - lineNumberStart;
47+
48+
return lineNumberRows.children[lineIndex];
49+
},
2650

27-
if (whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line') {
51+
/**
52+
* Resizes the line numbers of the given element.
53+
*
54+
* This function will not add line numbers. It will only resize existing ones.
55+
* @param {HTMLElement} element A `<pre>` element with line numbers.
56+
* @returns {void}
57+
*/
58+
resize: function (element) {
59+
resizeElements([element]);
60+
},
61+
62+
/**
63+
* Whether the plugin can assume that the units font sizes and margins are not depended on the size of
64+
* the current viewport.
65+
*
66+
* Setting this to `true` will allow the plugin to do certain optimizations for better performance.
67+
*
68+
* Set this to `false` if you use any of the following CSS units: `vh`, `vw`, `vmin`, `vmax`.
69+
*
70+
* @type {boolean}
71+
*/
72+
assumeViewportIndependence: true
73+
};
74+
75+
/**
76+
* Resizes the given elements.
77+
*
78+
* @param {HTMLElement[]} elements
79+
*/
80+
function resizeElements(elements) {
81+
elements = elements.filter(function (e) {
82+
var codeStyles = getStyles(e);
83+
var whiteSpace = codeStyles['white-space'];
84+
return whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line';
85+
});
86+
87+
if (elements.length == 0) {
88+
return;
89+
}
90+
91+
var infos = elements.map(function (element) {
2892
var codeElement = element.querySelector('code');
2993
var lineNumbersWrapper = element.querySelector('.line-numbers-rows');
3094
if (!codeElement || !lineNumbersWrapper) {
31-
return;
95+
return undefined;
3296
}
97+
98+
/** @type {HTMLElement} */
3399
var lineNumberSizer = element.querySelector('.line-numbers-sizer');
34100
var codeLines = codeElement.textContent.split(NEW_LINE_EXP);
35101

@@ -40,18 +106,63 @@
40106
codeElement.appendChild(lineNumberSizer);
41107
}
42108

109+
lineNumberSizer.innerHTML = '0';
43110
lineNumberSizer.style.display = 'block';
44111

45-
codeLines.forEach(function (line, lineNumber) {
46-
lineNumberSizer.textContent = line || '\n';
47-
var lineSize = lineNumberSizer.getBoundingClientRect().height;
48-
lineNumbersWrapper.children[lineNumber].style.height = lineSize + 'px';
112+
var oneLinerHeight = lineNumberSizer.getBoundingClientRect().height;
113+
lineNumberSizer.innerHTML = '';
114+
115+
return {
116+
element: element,
117+
lines: codeLines,
118+
lineHeights: [],
119+
oneLinerHeight: oneLinerHeight,
120+
sizer: lineNumberSizer,
121+
};
122+
}).filter(Boolean);
123+
124+
infos.forEach(function (info) {
125+
var lineNumberSizer = info.sizer;
126+
var lines = info.lines;
127+
var lineHeights = info.lineHeights;
128+
var oneLinerHeight = info.oneLinerHeight;
129+
130+
lineHeights[lines.length - 1] = undefined;
131+
lines.forEach(function (line, index) {
132+
if (line && line.length > 1) {
133+
var e = lineNumberSizer.appendChild(document.createElement('span'));
134+
e.style.display = 'block';
135+
e.textContent = line;
136+
} else {
137+
lineHeights[index] = oneLinerHeight;
138+
}
49139
});
140+
});
141+
142+
infos.forEach(function (info) {
143+
var lineNumberSizer = info.sizer;
144+
var lineHeights = info.lineHeights;
145+
146+
var childIndex = 0;
147+
for (var i = 0; i < lineHeights.length; i++) {
148+
if (lineHeights[i] === undefined) {
149+
lineHeights[i] = lineNumberSizer.children[childIndex++].getBoundingClientRect().height;
150+
}
151+
}
152+
});
153+
154+
infos.forEach(function (info) {
155+
var lineNumberSizer = info.sizer;
156+
var wrapper = info.element.querySelector('.line-numbers-rows');
50157

51-
lineNumberSizer.textContent = '';
52158
lineNumberSizer.style.display = 'none';
53-
}
54-
};
159+
lineNumberSizer.innerHTML = '';
160+
161+
info.lineHeights.forEach(function (height, lineNumber) {
162+
wrapper.children[lineNumber].style.height = height + 'px';
163+
});
164+
});
165+
}
55166

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

179+
var lastWidth = undefined;
68180
window.addEventListener('resize', function () {
69-
Array.prototype.forEach.call(document.querySelectorAll('pre.' + PLUGIN_NAME), _resizeElement);
181+
if (config.assumeViewportIndependence && lastWidth === window.innerWidth) {
182+
return;
183+
}
184+
lastWidth = window.innerWidth;
185+
186+
resizeElements(Array.prototype.slice.call(document.querySelectorAll('pre.' + PLUGIN_NAME)));
70187
});
71188

72189
Prism.hooks.add('complete', function (env) {
@@ -75,7 +192,7 @@
75192
}
76193

77194
var code = /** @type {Element} */ (env.element);
78-
var pre = /** @type {Element} */ (code.parentNode);
195+
var pre = /** @type {HTMLElement} */ (code.parentNode);
79196

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

115232
env.element.appendChild(lineNumbersWrapper);
116233

117-
_resizeElement(pre);
234+
resizeElements([pre]);
118235

119236
Prism.hooks.run('line-numbers', env);
120237
});
@@ -124,46 +241,4 @@
124241
env.plugins.lineNumbers = true;
125242
});
126243

127-
/**
128-
* Global exports
129-
*/
130-
Prism.plugins.lineNumbers = {
131-
/**
132-
* Returns the node of the given line number in the given element.
133-
* @param {Element} element A `<pre>` element with line numbers.
134-
* @param {Number} number
135-
* @returns {Element | undefined}
136-
*/
137-
getLine: function (element, number) {
138-
if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
139-
return;
140-
}
141-
142-
var lineNumberRows = element.querySelector('.line-numbers-rows');
143-
var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
144-
var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);
145-
146-
if (number < lineNumberStart) {
147-
number = lineNumberStart;
148-
}
149-
if (number > lineNumberEnd) {
150-
number = lineNumberEnd;
151-
}
152-
153-
var lineIndex = number - lineNumberStart;
154-
155-
return lineNumberRows.children[lineIndex];
156-
},
157-
/**
158-
* Resizes the line numbers of the given element.
159-
*
160-
* This function will not add line numbers. It will only resize existing ones.
161-
* @param {Element} element A `<pre>` element with line numbers.
162-
* @returns {void}
163-
*/
164-
resize: function (element) {
165-
_resizeElement(element);
166-
}
167-
};
168-
169244
}());

‎plugins/line-numbers/prism-line-numbers.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.