Skip to content

Commit cf5b595

Browse files
committed
feat: add long-form reading aids
1 parent 57b2302 commit cf5b595

File tree

1 file changed

+44
-11
lines changed

1 file changed

+44
-11
lines changed

scripts/scripts.js

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,16 @@ import {
2424

2525
// Constants here
2626
const LCP_BLOCKS = ['hero', 'logo-wall']; // add your LCP blocks to the list
27+
const LONG_FORM_TEMPLATE_CLASSES = new Set(['docs-template', 'guides-template', 'blog-template', 'skills-template']);
28+
const LONG_FORM_TEMPLATE_METADATA = new Set(['docs', 'documentation', 'guide', 'guides', 'blog', 'skills']);
29+
const LONG_FORM_SCROLL_THRESHOLD = 0.35;
2730

2831
const AUDIENCES = {
2932
mobile: () => window.innerWidth < 600,
3033
desktop: () => window.innerWidth >= 600,
3134
// define your custom audiences here as needed
3235
};
3336

34-
const LONG_FORM_TEMPLATES = ['docs-template', 'guides-template', 'blog-template'];
35-
3637
window.hlx.plugins.add('performance', {
3738
condition: () => window.name.includes('performance'),
3839
load: 'eager',
@@ -185,36 +186,56 @@ export function addMessageBoxOnGuideTemplate(main) {
185186
main.append(messageBox);
186187
}
187188

188-
const isLongFormPage = () => LONG_FORM_TEMPLATES
189-
.some((tplClass) => document.body.classList.contains(tplClass));
189+
const isLongFormPage = () => {
190+
const { body } = document;
191+
if (!body) return false;
192+
const hasTemplateClass = [...LONG_FORM_TEMPLATE_CLASSES]
193+
.some((tplClass) => body.classList.contains(tplClass));
194+
if (hasTemplateClass) return true;
195+
const templateMetadata = getMetadata('template');
196+
if (!templateMetadata) return false;
197+
return LONG_FORM_TEMPLATE_METADATA.has(templateMetadata.toLowerCase());
198+
};
190199

191200
const hasScrollableLongFormContent = () => {
192201
const scrollable = document.documentElement.scrollHeight - window.innerHeight;
193-
return scrollable > window.innerHeight * 0.35;
202+
return scrollable > window.innerHeight * LONG_FORM_SCROLL_THRESHOLD;
194203
};
195204

196205
function initReadingProgress() {
197206
if (!isLongFormPage() || document.querySelector('.reading-progress')) return;
198207

199208
const progressBar = createTag('div', {
200209
class: 'reading-progress',
210+
role: 'progressbar',
211+
'aria-label': 'Reading progress',
212+
'aria-valuemin': '0',
213+
'aria-valuemax': '100',
214+
'aria-valuenow': '0',
215+
'aria-hidden': 'true',
216+
});
217+
const fill = createTag('span', {
218+
class: 'reading-progress-fill',
201219
'aria-hidden': 'true',
202220
});
203-
const fill = createTag('span', { class: 'reading-progress-fill' });
204221
progressBar.append(fill);
205222
document.body.prepend(progressBar);
206223

207224
const updateProgress = () => {
208-
const scrollable = document.documentElement.scrollHeight - window.innerHeight;
225+
const scrollable = Math.max(document.documentElement.scrollHeight - window.innerHeight, 0);
209226
const canScroll = scrollable > 0;
210227
const isLongEnough = hasScrollableLongFormContent();
211-
progressBar.classList.toggle('is-hidden', !isLongEnough);
212-
if (!canScroll || !isLongEnough) {
228+
const shouldHide = !canScroll || !isLongEnough;
229+
progressBar.classList.toggle('is-hidden', shouldHide);
230+
progressBar.setAttribute('aria-hidden', shouldHide ? 'true' : 'false');
231+
if (shouldHide) {
213232
fill.style.transform = 'scaleX(0)';
233+
progressBar.setAttribute('aria-valuenow', '0');
214234
return;
215235
}
216236
const progress = Math.min(window.scrollY / scrollable, 1);
217237
fill.style.transform = `scaleX(${progress})`;
238+
progressBar.setAttribute('aria-valuenow', `${Math.round(progress * 100)}`);
218239
};
219240

220241
updateProgress();
@@ -232,6 +253,11 @@ function initReadingProgress() {
232253

233254
window.addEventListener('scroll', handleScroll, { passive: true });
234255
window.addEventListener('resize', updateProgress);
256+
257+
if (window.ResizeObserver && document.body) {
258+
const resizeObserver = new ResizeObserver(updateProgress);
259+
resizeObserver.observe(document.body);
260+
}
235261
}
236262

237263
function initBackToTopButton() {
@@ -249,6 +275,8 @@ function initBackToTopButton() {
249275
const label = createTag('span', { class: 'back-to-top-button-label' }, 'Back to top');
250276
button.append(icon, label);
251277
document.body.append(button);
278+
button.setAttribute('aria-hidden', 'true');
279+
button.setAttribute('tabindex', '-1');
252280

253281
const prefersReducedMotionQuery = window.matchMedia
254282
? window.matchMedia('(prefers-reduced-motion: reduce)')
@@ -281,15 +309,20 @@ function initBackToTopButton() {
281309
button.setAttribute('tabindex', '-1');
282310
return;
283311
}
284-
button.removeAttribute('aria-hidden');
285-
button.removeAttribute('tabindex');
312+
button.setAttribute('aria-hidden', 'false');
313+
button.setAttribute('tabindex', '0');
286314
const shouldShow = window.scrollY > window.innerHeight * 0.8;
287315
button.classList.toggle('is-visible', shouldShow);
288316
};
289317

290318
toggleVisibility();
291319
window.addEventListener('scroll', toggleVisibility, { passive: true });
292320
window.addEventListener('resize', toggleVisibility);
321+
322+
if (window.ResizeObserver && document.body) {
323+
const resizeObserver = new ResizeObserver(toggleVisibility);
324+
resizeObserver.observe(document.body);
325+
}
293326
}
294327

295328
export function addHeadingAnchorLink(elem) {

0 commit comments

Comments
 (0)