Skip to content

Instantly share code, notes, and snippets.

@nextab
Created June 18, 2025 15:47
Show Gist options
  • Save nextab/ea6bba07125522092b900f2f27b5fe3c to your computer and use it in GitHub Desktop.
Save nextab/ea6bba07125522092b900f2f27b5fe3c to your computer and use it in GitHub Desktop.
(function() {
// Variables to track scroll state and rotation
let lastScrollTop = 0;
let scrollTimeout;
let rotationAngles = {}; // Store rotation angles for each SVG
const ROTATION_SPEED = 0.5; // Degrees per scroll event
let animatedSections = new Map(); // Track sections being animated
// Function to get or initialize rotation angle for an element
function getRotationAngle(element) {
const id = element.dataset.rotationId ||
(element.dataset.rotationId = Math.random().toString(36).substring(2, 11));
if (rotationAngles[id] === undefined) {
rotationAngles[id] = 0;
}
return rotationAngles[id];
}
// Function to handle mobile navigation clicks
function handleMobileNavClick(element) {
// Since the "opened" class is added after the click,
// we need to wait for the next tick to check the class
setTimeout(() => {
// Find the parent mobile_nav element which gets the 'opened' class
const mobileNav = element.closest('.mobile_nav');
if (mobileNav && mobileNav.classList.contains('opened')) {
document.body.classList.add('mobile_nav_opened');
} else {
document.body.classList.remove('mobile_nav_opened');
}
}, 100);
}
// Function to check mobile nav state
function checkMobileNavState() {
const mobileNav = document.querySelector('header.et-l .mobile_nav');
if (mobileNav && mobileNav.classList.contains('opened')) {
document.body.classList.add('mobile_nav_opened');
} else {
document.body.classList.remove('mobile_nav_opened');
}
}
// Function to set rotation angle for an element
function setRotationAngle(element, angle) {
const id = element.dataset.rotationId;
if (id) {
rotationAngles[id] = angle;
}
}
// Function to apply rotation to an element
function applyRotation(element, angle) {
element.style.transform = `translatex(-50%) translatey(-50%) rotate(${angle}deg)`;
}
// Function to calculate scroll progress for an element
function calculateScrollProgress(element) {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
// Calculate when the bottom of the section is 20% from the bottom of the viewport
const startPoint = windowHeight * 0.8;
// Calculate when the bottom of the section is 20% from the top of the viewport
const endPoint = windowHeight * 0.2;
// If the section's bottom is above the start point, progress is 0
if (rect.bottom > startPoint) {
return 0;
}
// If the section's bottom is above the end point, progress is 1
if (rect.bottom < endPoint) {
return 1;
}
// Calculate progress between 0 and 1
return 1 - ((rect.bottom - endPoint) / (startPoint - endPoint));
}
// Function to update lawnmower and grass positions based on scroll progress
function updateLawnmowerPosition(section, progress) {
// Calculate positions based on progress (0 to 1)
// For lawnmower: right position from -60px to (100% + 60px)
const totalWidth = section.offsetWidth + 120; // section width + 60px on each side
const lawnmowerPosition = -60 + (totalWidth * progress);
// For grass: mask position from 0 to -500px
const grassMaskPosition = -500 * progress;
// Apply styles directly using CSS custom properties
section.style.setProperty('--lawnmower-position', `${lawnmowerPosition}px`);
section.style.setProperty('--grass-mask-position', `${grassMaskPosition}px`);
}
// Function to handle scroll events
function handleScroll() {
const scrollPosition = window.scrollY || document.documentElement.scrollTop;
// Add scrolling class to body
if (scrollPosition > 0) {
document.body.classList.add('scrolling');
} else {
document.body.classList.remove('scrolling');
}
// Determine scroll direction
const scrollingDown = scrollPosition > lastScrollTop;
lastScrollTop = scrollPosition;
// Update SVG rotation based on scroll direction
document.querySelectorAll('header.et-l .logo-container svg').forEach(element => {
// Get current rotation angle
let angle = getRotationAngle(element);
// Update angle based on scroll direction
if (scrollingDown) {
angle -= ROTATION_SPEED; // Counter-clockwise
} else {
angle += ROTATION_SPEED; // Clockwise
}
// Normalize angle to keep it between 0 and 360
angle = angle % 360;
// Store updated angle
setRotationAngle(element, angle);
// Apply rotation
applyRotation(element, angle);
});
// Check for grass sections in view and update their animation
updateGrassSectionsAnimation();
// Clear the timeout if it's set
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
// Set a timeout to mark the end of scrolling
scrollTimeout = setTimeout(function() {
// When scrolling stops, we don't need to do anything special
// as the positions are already applied directly to the elements
}, 300);
}
// Function to check if an element is in the viewport
function isInViewport(element) {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
// Element is in view if its bottom is visible and not too close to the top
return (
rect.bottom > 0 &&
rect.bottom <= windowHeight
);
}
// Function to update grass sections animation based on scroll position
function updateGrassSectionsAnimation() {
// Get all sections with grass_bottom class and the last section
const grassSections = document.querySelectorAll('#main-content .et_pb_section.grass_bottom');
// const lastSection = document.querySelector('#main-content .et_builder_inner_content > .et_pb_section:last-child');
// Process grass_bottom sections
grassSections.forEach(section => {
if (isInViewport(section) && !section.classList.contains('mowed')) {
// Add to animated sections if not already there
if (!animatedSections.has(section)) {
section.classList.add('animating');
animatedSections.set(section, true);
}
// Calculate and apply progress
const progress = calculateScrollProgress(section);
updateLawnmowerPosition(section, progress);
// Mark as mowed when animation completes
if (progress >= 1 && !section.classList.contains('mowed')) {
section.classList.remove('animating');
section.classList.add('mowed');
}
}
});
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
// Initialize last scroll position
lastScrollTop = window.scrollY || document.documentElement.scrollTop;
// Add scroll event listener
window.addEventListener('scroll', handleScroll, { passive: true });
// Initialize SVG elements
document.querySelectorAll('.logo-container svg').forEach(element => {
// Remove any existing transform to start fresh
element.style.transform = 'translatex(-50%) translatey(-50%) rotate(0deg)';
// Initialize rotation tracking
getRotationAngle(element);
});
// Initial check for grass sections animation
updateGrassSectionsAnimation();
// Approach 1: Use event capturing phase (runs before bubbling phase)
document.addEventListener('click', function(event) {
const target = event.target;
if (target.classList && target.classList.contains('mobile_menu_bar')) {
console.log('Capture phase detected mobile_menu_bar click');
handleMobileNavClick(target);
}
}, true); // true enables capture phase
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment