Skip to content

Commit 8c41000

Browse files
Merge pull request #136 from ThisIs-Developer/perf-ux-a11y-optimizations
perf: cache editor layout geometry and preload styles to eliminate forced reflows
2 parents 6dbb4c4 + 6b6e535 commit 8c41000

4 files changed

Lines changed: 150 additions & 70 deletions

File tree

desktop-app/resources/index.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
<title>Markdown Viewer</title>
1717
<link href="/assets/icon.jpg" rel="icon" type="image/jpg">
1818
<!-- Updated libraries to latest versions with Subresource Integrity (SRI) -->
19-
<link rel="stylesheet" href="/libs/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
20-
<link rel="stylesheet" href="/libs/github-markdown.min.css" integrity="sha384-hZuxRjC/Dsr4zEx1JlUhDQqkvqBPp2VLHsgXfnxPq1ULDy1eIdWCiux7nvO1RIZP" crossorigin="anonymous">
19+
<link rel="preload" href="/libs/bootstrap.min.css" as="style" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
20+
<noscript><link rel="stylesheet" href="/libs/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"></noscript>
21+
<link rel="preload" href="/libs/github-markdown.min.css" as="style" integrity="sha384-hZuxRjC/Dsr4zEx1JlUhDQqkvqBPp2VLHsgXfnxPq1ULDy1eIdWCiux7nvO1RIZP" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
22+
<noscript><link rel="stylesheet" href="/libs/github-markdown.min.css" integrity="sha384-hZuxRjC/Dsr4zEx1JlUhDQqkvqBPp2VLHsgXfnxPq1ULDy1eIdWCiux7nvO1RIZP" crossorigin="anonymous"></noscript>
2123
<link rel="preload" href="/libs/bootstrap-icons.min.css" as="style" integrity="sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
2224
<noscript><link rel="stylesheet" href="/libs/bootstrap-icons.min.css" integrity="sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+" crossorigin="anonymous"></noscript>
2325
<link rel="stylesheet" href="/styles.css">

desktop-app/resources/js/script.js

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3680,8 +3680,8 @@ document.addEventListener("DOMContentLoaded", function () {
36803680
return;
36813681
}
36823682
const text = markdownEditor.value || '';
3683-
const scrollTop = markdownEditor.scrollTop;
3684-
const scrollLeft = markdownEditor.scrollLeft;
3683+
const scrollTop = cachedScrollTop;
3684+
const scrollLeft = cachedScrollLeft;
36853685
const fragment = document.createDocumentFragment();
36863686
let lastIndex = 0;
36873687
findMatches.forEach(function(match, index) {
@@ -3701,8 +3701,8 @@ document.addEventListener("DOMContentLoaded", function () {
37013701

37023702
function syncHighlightScroll() {
37033703
if (!editorHighlightLayer) return;
3704-
editorHighlightLayer.scrollTop = markdownEditor.scrollTop;
3705-
editorHighlightLayer.scrollLeft = markdownEditor.scrollLeft;
3704+
editorHighlightLayer.scrollTop = cachedScrollTop;
3705+
editorHighlightLayer.scrollLeft = cachedScrollLeft;
37063706
}
37073707

37083708
function updateLineNumberGutter(lineCount) {
@@ -3765,42 +3765,71 @@ document.addEventListener("DOMContentLoaded", function () {
37653765
}
37663766

37673767
const lineCache = new Map();
3768-
let lastEditorWidth = 0;
3769-
let charWidth = 0;
3770-
let maxCharsPerLine = 0;
3768+
let cachedPaddingLeft = 10;
3769+
let cachedPaddingRight = 10;
3770+
let cachedCharWidth = 0;
3771+
let cachedLineHeight = 21;
3772+
let cachedEditorWidth = 0;
3773+
let cachedMaxCharsPerLine = 80;
3774+
let cachedScrollTop = 0;
3775+
let cachedScrollLeft = 0;
3776+
let isGeometryInitialized = false;
3777+
3778+
function initEditorGeometry() {
3779+
if (!markdownEditor) return;
3780+
const styles = window.getComputedStyle(markdownEditor);
3781+
cachedPaddingLeft = parseFloat(styles.paddingLeft) || 10;
3782+
cachedPaddingRight = parseFloat(styles.paddingRight) || 10;
3783+
3784+
// Measure character width
3785+
const testSpan = document.createElement('span');
3786+
testSpan.style.fontFamily = styles.fontFamily;
3787+
testSpan.style.fontSize = styles.fontSize;
3788+
testSpan.style.visibility = 'hidden';
3789+
testSpan.style.position = 'absolute';
3790+
testSpan.style.whiteSpace = 'pre';
3791+
testSpan.textContent = 'a'.repeat(100);
3792+
document.body.appendChild(testSpan);
3793+
cachedCharWidth = testSpan.getBoundingClientRect().width / 100;
3794+
document.body.removeChild(testSpan);
3795+
3796+
// Calculate line height
3797+
const computed = parseFloat(styles.lineHeight);
3798+
if (!Number.isNaN(computed)) {
3799+
cachedLineHeight = computed;
3800+
} else {
3801+
const fontSize = parseFloat(styles.fontSize) || 14;
3802+
cachedLineHeight = fontSize * 1.5;
3803+
}
3804+
3805+
isGeometryInitialized = true;
3806+
lineCache.clear();
3807+
}
3808+
3809+
function refreshEditorWidth() {
3810+
if (!markdownEditor) return;
3811+
if (!isGeometryInitialized) {
3812+
initEditorGeometry();
3813+
}
3814+
cachedEditorWidth = markdownEditor.clientWidth;
3815+
const availableWidth = cachedEditorWidth - cachedPaddingLeft - cachedPaddingRight;
3816+
cachedMaxCharsPerLine = Math.max(1, Math.floor(availableWidth / cachedCharWidth));
3817+
3818+
cachedScrollTop = markdownEditor.scrollTop;
3819+
cachedScrollLeft = markdownEditor.scrollLeft;
3820+
}
37713821

37723822
function updateLineNumbers() {
37733823
if (!lineNumbers || !markdownEditor) return;
37743824
const lines = (markdownEditor.value || '').split('\n');
37753825
const lineCount = Math.max(1, lines.length);
37763826

3777-
const currentWidth = markdownEditor.clientWidth;
3778-
const styles = window.getComputedStyle(markdownEditor);
3779-
const paddingLeft = parseFloat(styles.paddingLeft) || 10;
3780-
const paddingRight = parseFloat(styles.paddingRight) || 10;
3781-
const availableWidth = currentWidth - paddingLeft - paddingRight;
3782-
3783-
// Measure character width exactly once per resize / layout width change
3784-
if (currentWidth !== lastEditorWidth || charWidth === 0) {
3785-
lineCache.clear();
3786-
lastEditorWidth = currentWidth;
3787-
3788-
const testSpan = document.createElement('span');
3789-
testSpan.style.fontFamily = styles.fontFamily;
3790-
testSpan.style.fontSize = styles.fontSize;
3791-
testSpan.style.visibility = 'hidden';
3792-
testSpan.style.position = 'absolute';
3793-
testSpan.style.whiteSpace = 'pre';
3794-
testSpan.textContent = 'a'.repeat(100);
3795-
document.body.appendChild(testSpan);
3796-
charWidth = testSpan.getBoundingClientRect().width / 100;
3797-
document.body.removeChild(testSpan);
3798-
3799-
maxCharsPerLine = Math.max(1, Math.floor(availableWidth / charWidth));
3827+
if (cachedEditorWidth === 0) {
3828+
refreshEditorWidth();
38003829
}
38013830

38023831
updateLineNumberGutter(lineCount);
3803-
const lineHeight = getLineHeight(styles);
3832+
const lineHeight = cachedLineHeight;
38043833

38053834
const existingItems = lineNumbers.children;
38063835
const existingCount = existingItems.length;
@@ -3825,7 +3854,7 @@ document.addEventListener("DOMContentLoaded", function () {
38253854
const lineText = lines[i];
38263855
let wrapHeight = lineCache.get(lineText);
38273856
if (wrapHeight === undefined) {
3828-
const wrapCount = getWrappedLineCountMonospace(lineText, maxCharsPerLine);
3857+
const wrapCount = getWrappedLineCountMonospace(lineText, cachedMaxCharsPerLine);
38293858
wrapHeight = wrapCount * lineHeight;
38303859
lineCache.set(lineText, wrapHeight);
38313860
}
@@ -3855,7 +3884,7 @@ document.addEventListener("DOMContentLoaded", function () {
38553884

38563885
function syncLineNumberScroll() {
38573886
if (!lineNumbers) return;
3858-
lineNumbers.scrollTop = markdownEditor.scrollTop;
3887+
lineNumbers.scrollTop = cachedScrollTop;
38593888
}
38603889

38613890
// Class encapsulating Search & Replace Engine
@@ -4344,6 +4373,7 @@ document.addEventListener("DOMContentLoaded", function () {
43444373
const targetScrollTop = Math.max(0, (lineIndex * lineHeight) - (editorHeight / 2) + (lineHeight / 2));
43454374

43464375
markdownEditor.scrollTop = targetScrollTop;
4376+
cachedScrollTop = targetScrollTop;
43474377
syncHighlightScroll();
43484378
syncLineNumberScroll();
43494379
} catch (e) {
@@ -4938,12 +4968,14 @@ document.addEventListener("DOMContentLoaded", function () {
49384968
const previewPercent = 100 - editorWidthPercent;
49394969
editorPaneElement.style.flex = `0 0 calc((100% - var(--dock-width, 0px)) * ${editorWidthPercent / 100} - 4px)`;
49404970
previewPaneElement.style.flex = `0 0 calc((100% - var(--dock-width, 0px)) * ${previewPercent / 100} - 4px)`;
4971+
refreshEditorWidth();
49414972
scheduleLineNumberUpdate();
49424973
}
49434974

49444975
function resetPaneWidths() {
49454976
editorPaneElement.style.flex = '';
49464977
previewPaneElement.style.flex = '';
4978+
refreshEditorWidth();
49474979
}
49484980

49494981
function openMobileMenu() {
@@ -5035,6 +5067,8 @@ document.addEventListener("DOMContentLoaded", function () {
50355067

50365068
initTabs();
50375069
if (loadGlobalState().syncScrollingEnabled === false) toggleSyncScrolling();
5070+
initEditorGeometry();
5071+
refreshEditorWidth();
50385072
updateMobileStats();
50395073
updateFindHighlights();
50405074
syncHighlightScroll();
@@ -5066,6 +5100,8 @@ document.addEventListener("DOMContentLoaded", function () {
50665100
}
50675101

50685102
window.addEventListener('resize', () => {
5103+
initEditorGeometry();
5104+
refreshEditorWidth();
50695105
scheduleLineNumberUpdate();
50705106
if (window.innerWidth < 1080 && isFrDocked && isFindModalOpen) {
50715107
toggleFrDockMode(true);
@@ -5136,6 +5172,8 @@ document.addEventListener("DOMContentLoaded", function () {
51365172
});
51375173

51385174
editorPane.addEventListener("scroll", function() {
5175+
cachedScrollTop = this.scrollTop;
5176+
cachedScrollLeft = this.scrollLeft;
51395177
syncEditorToPreview();
51405178
syncHighlightScroll();
51415179
syncLineNumberScroll();

index.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@
7878
<title>Markdown Viewer</title>
7979
<link href="assets/icon.jpg" rel="icon" type="image/jpg">
8080
<!-- Updated libraries to latest versions with Subresource Integrity (SRI) -->
81-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
82-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.3.0/github-markdown.min.css" integrity="sha384-hZuxRjC/Dsr4zEx1JlUhDQqkvqBPp2VLHsgXfnxPq1ULDy1eIdWCiux7nvO1RIZP" crossorigin="anonymous">
81+
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css" as="style" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
82+
<noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"></noscript>
83+
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.3.0/github-markdown.min.css" as="style" integrity="sha384-hZuxRjC/Dsr4zEx1JlUhDQqkvqBPp2VLHsgXfnxPq1ULDy1eIdWCiux7nvO1RIZP" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
84+
<noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.3.0/github-markdown.min.css" integrity="sha384-hZuxRjC/Dsr4zEx1JlUhDQqkvqBPp2VLHsgXfnxPq1ULDy1eIdWCiux7nvO1RIZP" crossorigin="anonymous"></noscript>
8385
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" as="style" integrity="sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+" crossorigin="anonymous" onload="this.onload=null;this.rel='stylesheet'">
8486
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" integrity="sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+" crossorigin="anonymous"></noscript>
8587
<link rel="stylesheet" href="styles.css">

0 commit comments

Comments
 (0)