Widget:ScrollToTopSidebar: Difference between revisions
(Scroll-to-top fill starts when the button comes into view, not at the top of the content) |
(Scroll-to-top: floating pill at the bottom centre of the viewport, progress fill from when it appears) |
||
| Line 1: | Line 1: | ||
<style> | <style> | ||
/* A darker band fills | /* Floating pill at the bottom centre of the viewport. A darker band fills it from the | ||
left as you read down the content column (--stt-p, set below); the off-white base | |||
keeps it legible over the text it floats above. */ | |||
#scrollToTop { | #scrollToTop { | ||
background: linear-gradient(to right, rgba(186, 27, 29, .3) var(--stt-p, 0%), rgba(186, 27, 29, .1) var(--stt-p, 0%)); | position: fixed; | ||
left: 50%; | |||
bottom: 1.5rem; | |||
transform: translateX(-50%); | |||
width: 11rem; | |||
height: 44px; | |||
border-radius: 22px; | |||
overflow: hidden; | |||
box-shadow: 0 2px 10px rgba(0, 0, 0, .18); | |||
background: | |||
linear-gradient(to right, rgba(186, 27, 29, .3) var(--stt-p, 0%), rgba(186, 27, 29, .1) var(--stt-p, 0%)), | |||
#fffcf5; | |||
} | } | ||
#scrollToTop:hover { | #scrollToTop:hover { | ||
background: linear-gradient(to right, rgba(186, 27, 29, .5) var(--stt-p, 0%), rgba(186, 27, 29, .3) var(--stt-p, 0%)); | background: | ||
linear-gradient(to right, rgba(186, 27, 29, .5) var(--stt-p, 0%), rgba(186, 27, 29, .3) var(--stt-p, 0%)), | |||
#fffcf5; | |||
} | |||
@media (max-width: 767.98px) { | |||
#scrollToTop { display: none !important; } | |||
} | } | ||
</style> | </style> | ||
| Line 14: | Line 32: | ||
scrollToTopButtonSb.setAttribute('id', 'scrollToTop'); | scrollToTopButtonSb.setAttribute('id', 'scrollToTop'); | ||
// | // The button floats over the page, so it lives on the body; the sidebar slot that | ||
// invokes this widget is left empty and hidden. | |||
const container = document.getElementById('scrollToTopContainer'); | const container = document.getElementById('scrollToTopContainer'); | ||
document.body.appendChild(scrollToTopButtonSb); | |||
if (container) container.style.display = 'none'; | |||
// Handle initial hash navigation | // Handle initial hash navigation | ||
| Line 30: | Line 50: | ||
}); | }); | ||
// Progress runs from the moment the button | // Progress runs from the moment the button appears (STT_SHOW) to the end of the main | ||
// content column (the .col-md-9 beside the sidebar, or the whole page). | |||
// content column | const STT_SHOW = 300; | ||
const sttRow = container.closest('.row'); | const sttRow = container && container.closest('.row'); | ||
const sttContent = (sttRow && sttRow.querySelector(':scope > .col-md-9')) || document.documentElement; | const sttContent = (sttRow && sttRow.querySelector(':scope > .col-md-9')) || document.documentElement; | ||
function sttProgress() { | function sttProgress() { | ||
const y = window.pageYOffset | const y = window.pageYOffset; | ||
const end = y + sttContent.getBoundingClientRect().bottom - window.innerHeight; | |||
if (end <= STT_SHOW) return 1; | |||
return Math.min(1, Math.max(0, (y - STT_SHOW) / (end - STT_SHOW))); | |||
const end = y + sttContent.getBoundingClientRect().bottom - | |||
if (end <= | |||
return Math.min(1, Math.max(0, (y - | |||
} | } | ||
| Line 52: | Line 67: | ||
function sttUpdate() { | function sttUpdate() { | ||
sttTicking = false; | sttTicking = false; | ||
if (window.pageYOffset > | if (window.pageYOffset > STT_SHOW) { | ||
scrollToTopButtonSb.style.display = 'block'; | scrollToTopButtonSb.style.display = 'block'; | ||
scrollToTopButtonSb.style.setProperty('--stt-p', (sttProgress() * 100).toFixed(2) + '%'); | scrollToTopButtonSb.style.setProperty('--stt-p', (sttProgress() * 100).toFixed(2) + '%'); | ||
Revision as of 18:25, 30 September 2026
<style> /* Floating pill at the bottom centre of the viewport. A darker band fills it from the
left as you read down the content column (--stt-p, set below); the off-white base keeps it legible over the text it floats above. */
- scrollToTop {
position: fixed; left: 50%; bottom: 1.5rem; transform: translateX(-50%); width: 11rem; height: 44px; border-radius: 22px; overflow: hidden; box-shadow: 0 2px 10px rgba(0, 0, 0, .18); background: linear-gradient(to right, rgba(186, 27, 29, .3) var(--stt-p, 0%), rgba(186, 27, 29, .1) var(--stt-p, 0%)), #fffcf5; }
- scrollToTop:hover {
background: linear-gradient(to right, rgba(186, 27, 29, .5) var(--stt-p, 0%), rgba(186, 27, 29, .3) var(--stt-p, 0%)), #fffcf5; } @media (max-width: 767.98px) { #scrollToTop { display: none !important; } } </style> <script> // Create the scroll to top button const scrollToTopButtonSb = document.createElement('button'); scrollToTopButtonSb.innerHTML = 'to top '; scrollToTopButtonSb.setAttribute('id', 'scrollToTop');
// The button floats over the page, so it lives on the body; the sidebar slot that // invokes this widget is left empty and hidden. const container = document.getElementById('scrollToTopContainer'); document.body.appendChild(scrollToTopButtonSb); if (container) container.style.display = 'none';
// Handle initial hash navigation window.addEventListener('load', () => { if (window.location.hash) { setTimeout(() => { const element = document.querySelector(window.location.hash); if (element) { element.scrollIntoView({ behavior: 'smooth' }); } }, 100); } });
// Progress runs from the moment the button appears (STT_SHOW) to the end of the main // content column (the .col-md-9 beside the sidebar, or the whole page). const STT_SHOW = 300; const sttRow = container && container.closest('.row'); const sttContent = (sttRow && sttRow.querySelector(':scope > .col-md-9')) || document.documentElement;
function sttProgress() { const y = window.pageYOffset; const end = y + sttContent.getBoundingClientRect().bottom - window.innerHeight; if (end <= STT_SHOW) return 1; return Math.min(1, Math.max(0, (y - STT_SHOW) / (end - STT_SHOW))); }
// Show/hide the button and advance its fill with scroll position let sttTicking = false; function sttUpdate() { sttTicking = false; if (window.pageYOffset > STT_SHOW) { scrollToTopButtonSb.style.display = 'block'; scrollToTopButtonSb.style.setProperty('--stt-p', (sttProgress() * 100).toFixed(2) + '%'); } else { scrollToTopButtonSb.style.display = 'none'; } } window.addEventListener('scroll', () => { if (!sttTicking) { sttTicking = true; window.requestAnimationFrame(sttUpdate); } }, { passive: true });
// Scroll to top when the button is clicked scrollToTopButtonSb.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); </script>