JavaScript Examples
Ready to use JavaScript routines that show what you can do with the JavaScript and Load the JavaScript options.
Each script below is tagged with the load directive it’s written for:
- Before
-
Runs before the document is rendered, by being appended to
<head>first. Use Before when the script needs to act on the page as it currently is, before the new content replaces it — for example, to capture state (like scroll position) that would otherwise be lost. - After (Default)
-
Runs after the document is rendered. Use After when the script needs to find and manipulate elements from the rendered content, such as headings or sections.
How to use an example
-
Open the Asciidoctor Browser Extension options and go to Add a JavaScript.
-
Paste the content of one of the scripts below (or download one) and save it.
-
Select the script from the JavaScript dropdown.
-
Set Load the JavaScript to Before or After, matching the example.
Restore your reading position after auto-reload (Before)
Asciidoctor Browser Extension re-renders the whole document on every auto-reload, which normally jumps you back to the top — annoying when you’re iterating on a long document. This script captures your scroll position right before the old content is replaced, then restores it once the new content is in place. That’s only possible because the load directive is set to Before: it must run while the old content (and your scroll position on it) still exists.
// Restore your reading position after each auto-reload, instead of jumping back to the top.
(() => {
if (window.__asciidoctorRestoreScrollCleanup) {
window.__asciidoctorRestoreScrollCleanup()
}
const storageKey = 'asciidoctor-scroll-position'
const savedPosition = Number(sessionStorage.getItem(storageKey))
// Save the current position right before the content is replaced.
sessionStorage.setItem(storageKey, String(window.scrollY))
if (!savedPosition) {
return // first load, or already at the top: nothing to restore
}
const observer = new MutationObserver(() => {
window.scrollTo(0, savedPosition)
observer.disconnect()
})
observer.observe(document.body, { childList: true, subtree: true })
window.__asciidoctorRestoreScrollCleanup = () => observer.disconnect()
})()
| Download this script from restore-scroll-position.js |
Scroll to headings with the keyboard (After)
Use Up and Down to smoothly scroll between headings (h1 to h5).
This needs the headings to already exist in the rendered document, so the load directive is set to After.
// Scroll to the next/previous section with keyboard up/down.
(() => {
// Auto-reload re-runs this script on every update; remove the previous
// instance's listener first so they don't stack up across reloads.
if (window.__asciidoctorNavigationCleanup) {
window.__asciidoctorNavigationCleanup()
}
const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5')).filter(
(heading) => !heading.classList.contains('float'),
)
const headingsLength = headings.length
let current = 0
const onKeydown = (e) => {
if (e.key === 'ArrowUp' && current > 0) {
e.preventDefault()
current -= 1
window.scrollTo({ top: headings[current].offsetTop, behavior: 'smooth' })
} else if (e.key === 'ArrowDown' && current < headingsLength - 1) {
e.preventDefault()
current += 1
window.scrollTo({ top: headings[current].offsetTop, behavior: 'smooth' })
}
}
window.addEventListener('keydown', onKeydown)
window.__asciidoctorNavigationCleanup = () =>
window.removeEventListener('keydown', onKeydown)
})()
| Download this script from navigation.js |
Highlight the current section in the TOC (After)
Highlight the chapter/section you’re currently reading in the table of contents (:toc:) as you scroll, so it always shows where you are in a long document.
This needs the TOC and headings to already exist in the rendered document, so the load directive is set to After.
// Highlight the current chapter/section in the TOC while scrolling.
(() => {
// Auto-reload re-runs this script on every update; tear down the previous
// instance first so listeners/styles don't stack up across reloads.
if (window.__asciidoctorTocScrollspyCleanup) {
window.__asciidoctorTocScrollspyCleanup()
}
const toc = document.getElementById('toc')
if (!toc) {
return // :toc: is not enabled for this document
}
const headings = Array.from(document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id]'))
.map((heading) => ({ heading, link: toc.querySelector(`a[href="#${heading.id}"]`) }))
.filter(({ link }) => link)
if (headings.length === 0) {
return
}
const styleElement = document.createElement('style')
styleElement.id = 'toc-scrollspy-style'
styleElement.textContent =
'#toc a.is-active { font-weight: bold; border-left: 3px solid currentColor; padding-left: 0.5em; margin-left: -0.5em; }'
document.head.appendChild(styleElement)
let activeLink
const setActive = (link) => {
if (link === activeLink) {
return
}
if (activeLink) {
activeLink.classList.remove('is-active')
}
if (link) {
link.classList.add('is-active')
}
activeLink = link
}
let ticking = false
const updateActiveHeading = () => {
ticking = false
const scrollPosition = window.scrollY + 96 // clears a sticky header, if any
let current = headings[0]
for (const entry of headings) {
if (entry.heading.offsetTop <= scrollPosition) {
current = entry
} else {
break
}
}
setActive(current.link)
}
const onScroll = () => {
if (!ticking) {
ticking = true
requestAnimationFrame(updateActiveHeading)
}
}
window.addEventListener('scroll', onScroll, { passive: true })
updateActiveHeading()
window.__asciidoctorTocScrollspyCleanup = () => {
window.removeEventListener('scroll', onScroll)
styleElement.remove()
if (activeLink) {
activeLink.classList.remove('is-active')
}
}
})()
| Download this script from toc-scrollspy.js |