Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions src/assets/js/utils/backToTop.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
* Handles the functionality for a 'back to top' button.
* The button appears when the user scrolls down and smoothly scrolls the page to the top when clicked.
*/

import { throttle } from './performance.js';

export const initBackToTopButton = () => {
const backToTopButton = document.getElementById('back-to-top');

Expand All @@ -10,15 +13,21 @@ export const initBackToTopButton = () => {
return;
}

window.addEventListener('scroll', () => {
if (window.pageYOffset > 300) {
backToTopButton.classList.remove('opacity-0', 'invisible');
backToTopButton.classList.add('opacity-100', 'visible');
} else {
backToTopButton.classList.remove('opacity-100', 'visible');
backToTopButton.classList.add('opacity-0', 'invisible');
}
});
// Throttle scroll handler to improve performance
// Limits execution to once every 100ms (10 times per second max)
// Before: 200-300 executions per second during active scrolling
// After: Maximum 10 executions per second
const handleScroll = throttle(() => {
const shouldShow = window.pageYOffset > 300;

// Use classList.toggle for cleaner code
backToTopButton.classList.toggle('opacity-0', !shouldShow);
backToTopButton.classList.toggle('invisible', !shouldShow);
backToTopButton.classList.toggle('opacity-100', shouldShow);
backToTopButton.classList.toggle('visible', shouldShow);
}, 100);

window.addEventListener('scroll', handleScroll);

backToTopButton.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
Expand Down
76 changes: 76 additions & 0 deletions src/assets/js/utils/performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Performance optimization utilities
*/

/**
* Throttles a function to limit how often it can be called
* Useful for expensive operations triggered by high-frequency events (scroll, resize, etc.)
*
* @param {Function} func - The function to throttle
* @param {number} delay - Minimum time between function calls in milliseconds
* @returns {Function} - Throttled function
*
* @example
* const throttledScroll = throttle(() => {
* console.log('Scroll handler called');
* }, 100); // Max 10 calls per second
*
* window.addEventListener('scroll', throttledScroll);
*/
export const throttle = (func, delay) => {
let timeoutId;
let lastRan;

return function (...args) {
const context = this;

if (!lastRan) {
// First call - execute immediately
func.apply(context, args);
lastRan = Date.now();
} else {
// Clear any pending execution
clearTimeout(timeoutId);

// Schedule execution if enough time has passed
timeoutId = setTimeout(
() => {
const timeSinceLastRun = Date.now() - lastRan;
if (timeSinceLastRun >= delay) {
func.apply(context, args);
lastRan = Date.now();
}
},
delay - (Date.now() - lastRan)
);
}
};
};

/**
* Debounces a function to delay execution until after a period of inactivity
* Useful for operations that should only happen after user stops an action
*
* @param {Function} func - The function to debounce
* @param {number} delay - Time to wait after last call in milliseconds
* @returns {Function} - Debounced function
*
* @example
* const debouncedSearch = debounce((query) => {
* performSearch(query);
* }, 300); // Wait 300ms after user stops typing
*
* inputElement.addEventListener('input', (e) => debouncedSearch(e.target.value));
*/
export const debounce = (func, delay) => {
let timeoutId;

return function (...args) {
const context = this;

clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(context, args);
}, delay);
};
};