Skip to content
Merged
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
165 changes: 104 additions & 61 deletions src/animation/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,86 +156,91 @@ const delay = [

const speed = [ 'none', 'slow', 'slower', 'fast', 'faster' ];

const animateElements = () => {
const elements = document.querySelectorAll( '.animated' );

createCustomAnimationNode( elements );

for ( const element of elements ) {
classes = element.classList;
element.animationClasses = [];
/** @type {Array<{element: HTMLElement, triggerOffset: number|string}>} */
const elementsScroll = [];
let scrollListenerAttached = false;

const processElement = ( element ) => {
// Skip if already processed
if ( element.classList.contains( 'o-anim-ready' ) ) {
return;
}

if ( ! isElementInViewport( element ) ) {
const animationClass = animations.find( ( i ) => {
return Array.from( classes ).find( ( o ) => o === i );
});
const classes = element.classList;
element.animationClasses = [];

const delayClass = delay.find( ( i ) => {
return Array.from( classes ).find( ( o ) => o === i );
});
if ( ! isElementInViewport( element ) ) {
const animationClass = animations.find( ( i ) => {
return Array.from( classes ).find( ( o ) => o === i );
});

const speedClass = speed.find( ( i ) => {
return Array.from( classes ).find( ( o ) => o === i );
});
const delayClass = delay.find( ( i ) => {
return Array.from( classes ).find( ( o ) => o === i );
});

if ( animationClass ) {
element.animationClasses.push( animationClass );
element.classList.remove( animationClass );
}
const speedClass = speed.find( ( i ) => {
return Array.from( classes ).find( ( o ) => o === i );
});

if ( delayClass ) {
element.animationClasses.push( delayClass );
element.classList.remove( delayClass );
}
if ( animationClass ) {
element.animationClasses.push( animationClass );
element.classList.remove( animationClass );
}

if ( speedClass ) {
element.animationClasses.push( speedClass );
element.classList.remove( speedClass );
}
if ( delayClass ) {
element.animationClasses.push( delayClass );
element.classList.remove( delayClass );
}

element.classList.add( 'hidden-animated' );
if ( speedClass ) {
element.animationClasses.push( speedClass );
element.classList.remove( speedClass );
}

classes.add( 'o-anim-ready' );
element.classList.add( 'hidden-animated' );
}

outAnimation.forEach( ( i ) => {
const isOut = element.className.includes( i );
classes.add( 'o-anim-ready' );

if ( isOut ) {
element.addEventListener( 'animationend', () => {
element.classList.remove( i );
});
}
});
outAnimation.forEach( ( i ) => {
const isOut = element.className.includes( i );

if ( classes.contains( 'o-anim-hover' ) ) {
element.classList.remove( 'hidden-animated' ); // We asume that elements with hover animation are visible by default.
element.classList.remove( 'animated' );
if ( isOut ) {
element.addEventListener( 'animationend', () => {
element.classList.remove( i );
}, { once: true });
}
});

const { animationName } = element.style;
element.style.animationName = 'none';
if ( classes.contains( 'o-anim-hover' ) ) {
element.classList.remove( 'hidden-animated' ); // We assume that elements with hover animation are visible by default.
element.classList.remove( 'animated' );

element.addEventListener( 'mouseenter', () => {
element.classList.add( 'animated' );
element.style.animationName = animationName;
});
}
const { animationName } = element.style;
element.style.animationName = 'none';

element.addEventListener( 'animationend', () => {
element.classList.remove( 'animated' );
element.addEventListener( 'mouseenter', () => {
element.classList.add( 'animated' );
element.style.animationName = animationName;
});
}

/** @type {Array<HTMLDivElement>} */
const elementsScroll = [];
element.addEventListener( 'animationend', () => {
element.classList.remove( 'animated' );
});

for ( const element of elements ) {
if (
element.animationClasses &&
0 < element.animationClasses.length
) {
elementsScroll.push({ element, triggerOffset: getTriggerOffset( element ) });
}
// Add to scroll tracking if needed
if (
element.animationClasses &&
0 < element.animationClasses.length
) {
elementsScroll.push({ element, triggerOffset: getTriggerOffset( element ) });
}
};

const attachScrollListener = () => {
if ( scrollListenerAttached ) {
return;
}

window.addEventListener( 'scroll', () => {
Expand Down Expand Up @@ -269,6 +274,35 @@ const animateElements = () => {
});
});
});

scrollListenerAttached = true;
};

const animateElements = () => {
const elements = document.querySelectorAll( '.animated' );

createCustomAnimationNode( elements );

for ( const element of elements ) {
processElement( element );
}

attachScrollListener();
};

const refreshLightboxAnimations = () => {
// Only process new, animated elements that haven't been initialized yet
const elements = document.querySelectorAll( '.animated:not(.o-anim-ready)' );

if ( elements.length === 0 ) {
return;
}

createCustomAnimationNode( elements );

for ( const element of elements ) {
processElement( element );
}
};

const isElementInViewport = ( el ) => {
Expand Down Expand Up @@ -402,4 +436,13 @@ const calculateOffset = ( offset ) => {
return offset;
};

// Re-animate elements when a lightbox is opened, as the content might be different.
const lightboxes = document.querySelectorAll('.wp-lightbox-container');
lightboxes.forEach((container) => {
container.addEventListener('click', () => {
refreshLightboxAnimations();
});
Comment on lines +439 to +444
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change adds/changes frontend behavior for animations when the WordPress lightbox opens, but there’s no automated coverage to prevent regressions. Since the repo already has Playwright E2E coverage for animations, please add an E2E that applies an Otter animation to a core Image/Gallery block with “Enlarge on click” enabled and asserts the media is visible when the lightbox opens (and ideally after closing/reopening).

Copilot uses AI. Check for mistakes.
});
Comment on lines +440 to +445
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting in this new block doesn’t match the surrounding file’s spacing conventions (e.g., window.addEventListener( 'load', … )). Please align with the existing style (spaces inside parentheses, consistent indentation) to keep the file consistent.

Copilot uses AI. Check for mistakes.
Comment thread
girishpanchal30 marked this conversation as resolved.


window.addEventListener( 'load', animateElements );
Loading