From dee1d2c279f0b33ba1c7a0331a7757ca38551379 Mon Sep 17 00:00:00 2001 From: "mr. m" Date: Sat, 3 Jan 2026 18:37:01 +0100 Subject: [PATCH] chore: Glance motion animations to native, b=no-bug, c=glance --- src/zen/glance/ZenGlanceManager.mjs | 149 +++++++++++----------------- src/zen/glance/zen-glance.css | 6 +- 2 files changed, 65 insertions(+), 90 deletions(-) diff --git a/src/zen/glance/ZenGlanceManager.mjs b/src/zen/glance/ZenGlanceManager.mjs index 1f38cb56a..40efc438b 100644 --- a/src/zen/glance/ZenGlanceManager.mjs +++ b/src/zen/glance/ZenGlanceManager.mjs @@ -35,7 +35,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { ARC_HEIGHT_RATIO: 0.2, // Arc height = distance * ratio (capped at MAX_ARC_HEIGHT) }); - #GLANCE_ANIMATION_DURATION = Services.prefs.getIntPref('zen.glance.animation-duration') / 1000; + #GLANCE_ANIMATION_DURATION = Services.prefs.getIntPref('zen.glance.animation-duration'); init() { this.#setupEventListeners(); @@ -253,19 +253,21 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { const xOffset = gZenVerticalTabsManager._prefsRightSide ? 20 : -20; - gZenUIManager.motion.animate( - container, - { - opacity: [0, 1], - x: [xOffset, 0], - }, - { - duration: 0.2, - type: 'spring', - delay: this.#GLANCE_ANIMATION_DURATION - 0.2, - bounce: 0, - } - ); + setTimeout(() => { + gZenUIManager.elementAnimate( + container, + { + opacity: [0, 1], + x: [xOffset, 0], + }, + { + duration: 200, + easing: 'ease-in-out', + fill: 'forwards', + delay: this.#GLANCE_ANIMATION_DURATION - 200, + } + ); + }); } /** @@ -403,7 +405,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { '.browserSidebarContainer' ); - gZenUIManager.motion.animate( + gZenUIManager.elementAnimate( parentSidebarContainer, { scale: [1, 0.98], @@ -411,8 +413,8 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { }, { duration: this.#GLANCE_ANIMATION_DURATION, - type: 'spring', - bounce: 0.2, + easing: 'ease-in-out', + fill: 'forwards', } ); } @@ -428,8 +430,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { this.overlay.removeAttribute('fade-out'); this.browserWrapper.setAttribute('animate', true); - this.browserWrapper.style.top = `${top}px`; - this.browserWrapper.style.left = `${left}px`; + this.browserWrapper.style.transform = `translate(${left}px, ${top}px)`; this.browserWrapper.style.width = `${width}px`; this.browserWrapper.style.height = `${height}px`; @@ -441,9 +442,18 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { * Store the original position for later restoration */ #storeOriginalPosition() { + let transform = this.browserWrapper.style.transform; + let [top, left] = [0, 0]; + if (transform && transform.startsWith('translate(')) { + const match = transform.match(/translate\(([-\d.]+)px,\s*([-\d.]+)px\)/); + if (match) { + left = parseFloat(match[1]); + top = parseFloat(match[2]); + } + } this.#glances.get(this.#currentGlanceID).originalPosition = { - top: this.browserWrapper.style.top, - left: this.browserWrapper.style.left, + top, + left, width: this.browserWrapper.style.width, height: this.browserWrapper.style.height, }; @@ -473,14 +483,14 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { this.browserWrapper.prepend(imageDataElement); this.#glances.get(this.#currentGlanceID).elementImageData = data.elementData; - gZenUIManager.motion.animate( + gZenUIManager.elementAnimate( imageDataElement, { opacity: [1, 0], }, { duration: this.#GLANCE_ANIMATION_DURATION / 2, - easing: 'easeInOut', + easing: 'ease-in-out', } ); @@ -500,16 +510,6 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { browserElement.style.minHeight = `${minHeight}px`; } - /** - * Get the transform origin for the animation - * @param {Object} data - Glance data with position and dimensions - * @returns {string} The transform origin CSS value - */ - #getTransformOrigin(data) { - const { clientX, clientY } = data; - return `${clientX}px ${clientY}px`; - } - /** * Execute the main glance animation * @param {Object} data - Glance data @@ -521,21 +521,18 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { // Create curved animation sequence const arcSequence = this.#createGlanceArcSequence(data, 'opening'); - const transformOrigin = this.#getTransformOrigin(data); - - this.browserWrapper.style.transformOrigin = transformOrigin; // Only animate if there is element data, so we can apply a // nice fade-in effect to the content. But if it doesn't exist, // we just fall back to always showing the browser directly. if (data.elementData) { - gZenUIManager.motion - .animate( + gZenUIManager + .elementAnimate( this.contentWrapper, { opacity: [0, 1] }, { duration: this.#GLANCE_ANIMATION_DURATION / 2, - easing: 'easeInOut', + easing: 'ease-in-out', } ) .then(() => { @@ -544,10 +541,9 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { } this.#animateParentBackground(); - gZenUIManager.motion - .animate(this.browserWrapper, arcSequence, { + gZenUIManager + .elementAnimate(this.browserWrapper, arcSequence, { duration: gZenUIManager.testingEnabled ? 0 : this.#GLANCE_ANIMATION_DURATION, - ease: 'easeInOut', }) .then(() => { this.#finalizeGlanceOpening(imageDataElement, browserElement, resolve); @@ -607,28 +603,29 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { ); const sequence = { - top: [], - left: [], + transform: [], width: [], height: [], - transform: [], }; const steps = this.#ARC_CONFIG.ARC_STEPS; const arcDirection = shouldArcDownward ? 1 : -1; - function easeInOutQuad(t) { - return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; + function easeOutBack(x) { + const c1 = 0.4; + const c3 = c1 + 1; + + return 1 + c3 * (x - 1) ** 3 + c1 * (x - 1) ** 2; } - function easeOutCubic(t) { - return 1 - Math.pow(1 - t, 6); + function easeInQuint(x) { + return x * x * x * x * x; } // First, create the main animation steps for (let i = 0; i <= steps; i++) { const progress = i / steps; - const eased = direction === 'opening' ? easeInOutQuad(progress) : easeOutCubic(progress); + const eased = direction === 'opening' ? easeOutBack(progress) : easeInQuint(progress); // Calculate size interpolation const currentWidth = startPosition.width + (endPosition.width - startPosition.width) * eased; @@ -643,32 +640,11 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { const y = startPosition.y + distanceY * eased + arcDirection * arcHeight * (1 - (2 * eased - 1) ** 2); - sequence.transform.push(`translate(-50%, -50%) scale(1)`); - sequence.top.push(`${y}px`); - sequence.left.push(`${x}px`); + sequence.transform.push(`translate(${x}px, ${y}px)`); sequence.width.push(`${currentWidth}px`); sequence.height.push(`${currentHeight}px`); } - let scale = 1; - const bounceSteps = 60; - if (direction === 'opening') { - for (let i = 0; i < bounceSteps; i++) { - const progress = i / bounceSteps; - // Scale up slightly then back to normal - scale = 1 + 0.003 * Math.sin(progress * Math.PI); - // If we are at the last step, ensure scale is exactly 1 - if (i === bounceSteps - 1) { - scale = 1; - } - sequence.transform.push(`translate(-50%, -50%) scale(${scale})`); - sequence.top.push(sequence.top[sequence.top.length - 1]); - sequence.left.push(sequence.left[sequence.left.length - 1]); - sequence.width.push(sequence.width[sequence.width.length - 1]); - sequence.height.push(sequence.height[sequence.height.length - 1]); - } - } - return sequence; } @@ -724,8 +700,6 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { imageDataElement.remove(); } - this.browserWrapper.style.transformOrigin = ''; - browserElement.style.minWidth = ''; browserElement.style.minHeight = ''; @@ -892,14 +866,14 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { */ #animateSidebarButtons(sidebarButtons) { if (sidebarButtons) { - gZenUIManager.motion - .animate( + gZenUIManager + .elementAnimate( sidebarButtons, { opacity: [1, 0] }, { - duration: 0.2, - type: 'spring', - bounce: this.#GLANCE_ANIMATION_DURATION - 0.1, + duration: 100, + easing: 'ease-in-out', + fill: 'forwards', } ) .then(() => { @@ -928,8 +902,8 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { * @param {Element} browserSidebarContainer - The sidebar container */ #animateParentBackgroundClose(browserSidebarContainer) { - gZenUIManager.motion - .animate( + gZenUIManager + .elementAnimate( browserSidebarContainer, { scale: [0.98, 1], @@ -937,8 +911,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { }, { duration: this.#GLANCE_ANIMATION_DURATION / 1.5, - type: 'spring', - bounce: 0, + easing: 'ease-in-out', } ) .then(() => { @@ -965,10 +938,9 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { const closingData = this.#createClosingDataFromOriginalPosition(originalPosition); const arcSequence = this.#createGlanceArcSequence(closingData, 'closing'); - gZenUIManager.motion - .animate(this.browserWrapper, arcSequence, { + gZenUIManager + .elementAnimate(this.browserWrapper, arcSequence, { duration: this.#GLANCE_ANIMATION_DURATION, - ease: 'easeOut', }) .then(() => { // Remove element preview after closing animation @@ -1485,7 +1457,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { this.browserWrapper.style.width = `${browserRect.width}px`; this.browserWrapper.style.height = `${browserRect.height}px`; - await gZenUIManager.motion.animate( + await gZenUIManager.elementAnimate( this.browserWrapper, { width: ['85%', '100%'], @@ -1493,8 +1465,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature { }, { duration: this.#GLANCE_ANIMATION_DURATION, - type: 'spring', - bounce: 0, + easing: 'ease-in-out', } ); diff --git a/src/zen/glance/zen-glance.css b/src/zen/glance/zen-glance.css index 1a9188295..2a73fcc9c 100644 --- a/src/zen/glance/zen-glance.css +++ b/src/zen/glance/zen-glance.css @@ -125,7 +125,7 @@ left: 0; flex: unset !important; /* Promote to its own layer during transitions to reduce jank */ - will-change: transform, top, left; + will-change: transform; width: 85%; height: 100%; @@ -148,6 +148,10 @@ box-shadow: var(--zen-big-shadow); } + &:not([has-finished-animation='true']) .browserStack { + transform: translate(-50%, -50%); + } + & browser { background: light-dark(rgb(255, 255, 255), rgb(32, 32, 32)) !important; width: 100%;