Skip to content

Commit 6b11d9e

Browse files
committed
Fix blog page scrollbar getting cut off when resizing
1 parent 727187a commit 6b11d9e

File tree

1 file changed

+8
-151
lines changed

1 file changed

+8
-151
lines changed

frontend/src/components/Blog.js

Lines changed: 8 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -33,129 +33,11 @@ function Blog() {
3333
});
3434
}, []);
3535

36-
// Ensure page-level horizontal overflow is hidden while this component is mounted
37-
useEffect(() => {
38-
const prevHtmlOverflowX = document.documentElement.style.overflowX;
39-
const prevBodyOverflowX = document.body.style.overflowX;
40-
const prevBodyMarginRight = document.body.style.marginRight;
41-
document.documentElement.style.overflowX = 'hidden';
42-
document.body.style.overflowX = 'hidden';
43-
// ensure no body margin that could shift scrollbar
44-
document.body.style.marginRight = '0';
45-
return () => {
46-
document.documentElement.style.overflowX = prevHtmlOverflowX;
47-
document.body.style.overflowX = prevBodyOverflowX;
48-
document.body.style.marginRight = prevBodyMarginRight;
49-
};
50-
}, []);
51-
52-
// Compute header/footer heights at runtime and set container offsets so content isn't overlapped
53-
const [offsets, setOffsets] = React.useState({ top: null, bottom: null });
54-
55-
useEffect(() => {
56-
if (typeof window === 'undefined') return;
57-
58-
const headerSelectors = ['header', '[data-app-header]', '.app-header', '.MuiAppBar-root', '.MuiPaper-root'];
59-
const footerSelectors = ['footer', '[data-app-footer]', '.app-footer', '.app-footer-root'];
60-
61-
function findEl(selectors, type = 'footer') {
62-
// try provided selectors first
63-
for (const s of selectors) {
64-
const el = document.querySelector(s);
65-
if (el) return el;
66-
}
67-
// try by class name containing 'footer' or 'header'
68-
const keyword = type === 'header' ? 'header' : 'footer';
69-
const byClass = Array.from(document.querySelectorAll('[class]')).find(el => {
70-
try {
71-
return el.className && String(el.className).toLowerCase().includes(keyword);
72-
} catch (e) {
73-
return false;
74-
}
75-
});
76-
if (byClass) return byClass;
77-
// try role attributes
78-
const role = type === 'header' ? '[role="banner"]' : '[role="contentinfo"]';
79-
const byRole = document.querySelector(role);
80-
if (byRole) return byRole;
81-
82-
// final fallback: find an element tucked at the bottom of the viewport
83-
const candidates = Array.from(document.querySelectorAll('body *')).filter(el => {
84-
const r = el.getBoundingClientRect();
85-
// visible and non-zero size
86-
return r.width > 10 && r.height > 10 && r.bottom >= (window.innerHeight - 1);
87-
});
88-
if (candidates.length) {
89-
// pick the one with the greatest top (closest to bottom)
90-
candidates.sort((a, b) => b.getBoundingClientRect().top - a.getBoundingClientRect().top);
91-
return candidates[0];
92-
}
93-
return null;
94-
}
95-
96-
function compute() {
97-
const headerEl = findEl(headerSelectors, 'header');
98-
const footerEl = findEl(footerSelectors, 'footer');
99-
const top = headerEl ? Math.ceil(headerEl.getBoundingClientRect().height) : null;
100-
let bottom = null;
101-
if (footerEl) {
102-
// use the footer element's height so our container sits above it
103-
const fr = footerEl.getBoundingClientRect();
104-
bottom = Math.ceil(fr.height);
105-
// if footer is positioned above bottom (e.g., floating), ensure bottom includes distance from viewport bottom
106-
const distanceFromBottom = Math.max(0, Math.ceil(window.innerHeight - fr.bottom));
107-
bottom = bottom + distanceFromBottom;
108-
}
109-
// enforce a small minimum so container never touches the footer exactly
110-
const MIN_BOTTOM = 4;
111-
if (bottom != null) bottom = Math.max(bottom, MIN_BOTTOM);
112-
113-
// account for iOS safe-area inset-bottom if present
114-
const safeAreaInset = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--safe-area-inset-bottom') || '0', 10) || 0;
115-
if (safeAreaInset > 0 && bottom != null) bottom += safeAreaInset;
116-
117-
setOffsets({ top, bottom });
118-
}
119-
120-
compute();
121-
const ro = new ResizeObserver(() => compute());
122-
// observe header/footer if present
123-
const headerEl = findEl(headerSelectors);
124-
const footerEl = findEl(footerSelectors);
125-
if (headerEl) ro.observe(headerEl);
126-
if (footerEl) ro.observe(footerEl);
127-
128-
// MutationObserver to detect layout changes inside footer (class changes, child changes)
129-
const mo = new MutationObserver(() => compute());
130-
if (footerEl) mo.observe(footerEl, { attributes: true, childList: true, subtree: true });
131-
132-
// Debounced resize handler (run compute immediately and once after resize stops)
133-
let resizeTimer = null;
134-
function onResize() {
135-
compute();
136-
if (resizeTimer) clearTimeout(resizeTimer);
137-
resizeTimer = setTimeout(() => {
138-
compute();
139-
resizeTimer = null;
140-
}, 200);
141-
}
142-
143-
window.addEventListener('resize', onResize);
144-
145-
return () => {
146-
window.removeEventListener('resize', onResize);
147-
try { ro.disconnect(); } catch (e) { }
148-
try { mo.disconnect(); } catch (e) { }
149-
if (resizeTimer) clearTimeout(resizeTimer);
150-
};
151-
}, []);
152-
15336
if (loading) {
15437
return (
15538
<Box
15639
sx={{
157-
width: 'auto',
158-
height: 'calc(100vh - var(--app-header-height, 75px) - var(--app-footer-height, 150px))',
40+
minHeight: '100vh',
15941
display: 'flex',
16042
alignItems: 'center',
16143
justifyContent: 'center',
@@ -171,8 +53,7 @@ function Blog() {
17153
return (
17254
<Box
17355
sx={{
174-
width: 'auto',
175-
height: 'calc(100vh - var(--app-header-height, 75px) - var(--app-footer-height, 150px))',
56+
minHeight: '100vh',
17657
display: 'flex',
17758
alignItems: 'center',
17859
justifyContent: 'center',
@@ -189,38 +70,15 @@ function Blog() {
18970
return (
19071
<Box
19172
sx={{
192-
position: 'fixed',
193-
top: offsets.top != null ? `${offsets.top}px` : 'var(--app-header-height, 75px)',
194-
bottom: offsets.bottom != null ? `${offsets.bottom}px` : 'var(--app-footer-height, 10px)',
195-
left: 0,
196-
right: 0,
197-
boxSizing: 'border-box',
73+
minHeight: '100vh',
19874
backgroundColor: '#f9f9f9',
199-
overflowX: 'hidden',
200-
// keep layout stacking context
201-
zIndex: 1,
202-
...(process.env.NODE_ENV === 'development' ? { outline: '2px dashed rgba(0,0,0,0.08)' } : {}),
75+
paddingLeft: '2rem',
76+
paddingRight: '2rem',
77+
paddingTop: '1rem',
78+
paddingBottom: '1rem',
79+
boxSizing: 'border-box',
20380
}}
20481
>
205-
<Box
206-
sx={{
207-
position: 'absolute',
208-
inset: 0,
209-
boxSizing: 'border-box',
210-
paddingLeft: '2rem',
211-
paddingRight: '2rem',
212-
paddingTop: '1rem',
213-
paddingBottom: '1rem',
214-
overflowY: 'auto',
215-
overflowX: 'hidden',
216-
WebkitOverflowScrolling: 'touch',
217-
'&::-webkit-scrollbar': { width: '12px' },
218-
'&::-webkit-scrollbar-track': { background: 'transparent' },
219-
'&::-webkit-scrollbar-thumb': { backgroundColor: '#c1c1c1', borderRadius: '6px' },
220-
scrollbarWidth: 'auto',
221-
scrollbarColor: '#c1c1c1 transparent',
222-
}}
223-
>
22482
<ReactMarkdown
22583
remarkPlugins={[remarkGfm]}
22684
rehypePlugins={[rehypeRaw]}
@@ -442,7 +300,6 @@ function Blog() {
442300
>
443301
{readmeContent}
444302
</ReactMarkdown>
445-
</Box>
446303
</Box>
447304
);
448305
}

0 commit comments

Comments
 (0)