| 1 |
document.addEventListener("DOMContentLoaded", () => { |
| 2 |
const spBackToTop = document.querySelector("#sp-smart-post-back-to-top-btn"); |
| 3 |
if (!spBackToTop) { return }; |
| 4 |
|
| 5 |
const spBackToTopIcon = spBackToTop.querySelector(".sp-smart-post-back-to-top-icon"); |
| 6 |
const spBackToTopLabel = spBackToTop.querySelector(".sp-smart-post-back-to-top-text.sp-back-to-top-text"); |
| 7 |
const spBackToBottomLabel = spBackToTop.querySelector(".sp-smart-post-back-to-top-text.sp-go-to-bottom-text"); |
| 8 |
|
| 9 |
const transitionDelay = (parseInt(spBackToTop.dataset?.transition) * 1000) || 1000; |
| 10 |
const goToBottom = spBackToTop.dataset?.goBottom || false; |
| 11 |
const entranceAnimation = spBackToTop.dataset?.animation || "fade-in"; |
| 12 |
|
| 13 |
const animateTransform = { |
| 14 |
"fade-in": "translate(0px, 0px)", |
| 15 |
"slide-in-left": "translate(10px, 0px)", |
| 16 |
"slide-in-right": "translate(-10px, 0px)", |
| 17 |
"slide-in-up": "translate(0px, 10px)", |
| 18 |
}; |
| 19 |
|
| 20 |
spBackToTop.style.transition = `all 0.3s ease`; |
| 21 |
spBackToTopIcon.style.transition = `transform 0.3s ease`; |
| 22 |
|
| 23 |
let lastScrollY = window.scrollY; |
| 24 |
let scrollDirection = null; |
| 25 |
|
| 26 |
// Helper to toggle labels |
| 27 |
const toggleLabels = (toBottom) => { |
| 28 |
if (!spBackToTopLabel || !spBackToBottomLabel) { return }; |
| 29 |
// spBackToTopLabel.style.display = toBottom ? "none" : "block"; |
| 30 |
// spBackToBottomLabel.style.display = toBottom ? "block" : "none"; |
| 31 |
spBackToTopLabel.classList.add( toBottom ? "sp-hide-label" : "sp-show-label" ); |
| 32 |
spBackToTopLabel.classList.remove( toBottom ? "sp-show-label" : "sp-hide-label" ); |
| 33 |
spBackToBottomLabel.classList.add( toBottom ? "sp-show-label" : "sp-hide-label" ); |
| 34 |
spBackToBottomLabel.classList.remove( toBottom ? "sp-hide-label" : "sp-show-label" ); |
| 35 |
}; |
| 36 |
|
| 37 |
function updateBackToTopButton() { |
| 38 |
const scrollY = window.scrollY; |
| 39 |
if (scrollY > 350) { |
| 40 |
spBackToTop.style.display = "inline-block"; |
| 41 |
// eslint-disable-next-line |
| 42 |
spBackToTop.offsetHeight; // trigger reflow |
| 43 |
spBackToTop.style.opacity = "1"; |
| 44 |
spBackToTop.style.transform = "translate(0px, 0px)"; |
| 45 |
} else { |
| 46 |
spBackToTop.style.opacity = "0"; |
| 47 |
spBackToTop.style.transform = animateTransform[entranceAnimation]; |
| 48 |
setTimeout(() => { |
| 49 |
if (window.scrollY <= 250) { spBackToTop.style.display = "none" }; |
| 50 |
}, 300); |
| 51 |
} |
| 52 |
} |
| 53 |
|
| 54 |
// Smooth scroll function |
| 55 |
function customScrollToTop(duration, direction = null) { |
| 56 |
const start = window.scrollY; |
| 57 |
const target = direction === "down" ? document.body.scrollHeight : 0; |
| 58 |
const distance = target - start; |
| 59 |
const startTime = performance.now(); |
| 60 |
|
| 61 |
function animateScroll(currentTime) { |
| 62 |
const elapsed = currentTime - startTime; |
| 63 |
const progress = Math.min(elapsed / duration, 1); |
| 64 |
const ease = progress < 0.5 |
| 65 |
? 4 * progress ** 3 |
| 66 |
: 1 - Math.pow(-2 * progress + 2, 3) / 2; |
| 67 |
window.scrollTo(0, start + distance * ease); |
| 68 |
if (elapsed < duration) { |
| 69 |
requestAnimationFrame(animateScroll) |
| 70 |
}; |
| 71 |
} |
| 72 |
|
| 73 |
requestAnimationFrame(animateScroll); |
| 74 |
} |
| 75 |
|
| 76 |
function isButtonAtEndOfPage(tolerance = 50, delay = 0) { |
| 77 |
return new Promise((resolve) => { |
| 78 |
setTimeout(() => { |
| 79 |
const rect = spBackToTop.getBoundingClientRect(); |
| 80 |
const absoluteBottom = rect.top + window.scrollY + rect.height; |
| 81 |
const docHeight = document.documentElement.scrollHeight; |
| 82 |
resolve((docHeight - absoluteBottom) <= tolerance); |
| 83 |
}, delay + 100); |
| 84 |
}); |
| 85 |
} |
| 86 |
|
| 87 |
async function buttonChangeDirection() { |
| 88 |
if (await isButtonAtEndOfPage(100, transitionDelay)) { |
| 89 |
scrollDirection = "up"; |
| 90 |
spBackToTopIcon.style.transform = `rotateX(0deg)`; |
| 91 |
toggleLabels(false); |
| 92 |
} |
| 93 |
} |
| 94 |
|
| 95 |
// Scroll direction tracking |
| 96 |
window.addEventListener("scroll", () => { |
| 97 |
if (goToBottom) { |
| 98 |
const currentScrollY = window.scrollY; |
| 99 |
if (currentScrollY > lastScrollY) { |
| 100 |
scrollDirection = "down"; |
| 101 |
spBackToTopIcon.style.transform = `rotateX(180deg)`; |
| 102 |
toggleLabels(true); |
| 103 |
} else if (currentScrollY < lastScrollY) { |
| 104 |
scrollDirection = "up"; |
| 105 |
spBackToTopIcon.style.transform = `rotateX(0deg)`; |
| 106 |
toggleLabels(false); |
| 107 |
} |
| 108 |
lastScrollY = currentScrollY; |
| 109 |
} |
| 110 |
buttonChangeDirection(); |
| 111 |
}); |
| 112 |
|
| 113 |
// Unified click handler (fixed duplicate listener) |
| 114 |
spBackToTop.addEventListener("click", (e) => { |
| 115 |
e.preventDefault(); |
| 116 |
customScrollToTop(transitionDelay, goToBottom ? scrollDirection : null); |
| 117 |
buttonChangeDirection(); |
| 118 |
}); |
| 119 |
|
| 120 |
// Init setup |
| 121 |
toggleLabels(false); |
| 122 |
updateBackToTopButton(); |
| 123 |
buttonChangeDirection(); |
| 124 |
|
| 125 |
// Throttled scroll listener for visibility |
| 126 |
let scrollTimeout; |
| 127 |
window.addEventListener("scroll", () => { |
| 128 |
if (scrollTimeout) { cancelAnimationFrame(scrollTimeout) }; |
| 129 |
scrollTimeout = requestAnimationFrame(updateBackToTopButton); |
| 130 |
}); |
| 131 |
}); |