Compare commits

...

1 Commits

Author SHA1 Message Date
mr. m
dee1d2c279 chore: Glance motion animations to native, b=no-bug, c=glance 2026-01-03 18:37:01 +01:00
2 changed files with 65 additions and 90 deletions

View File

@@ -35,7 +35,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
ARC_HEIGHT_RATIO: 0.2, // Arc height = distance * ratio (capped at MAX_ARC_HEIGHT) 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() { init() {
this.#setupEventListeners(); this.#setupEventListeners();
@@ -253,19 +253,21 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
const xOffset = gZenVerticalTabsManager._prefsRightSide ? 20 : -20; const xOffset = gZenVerticalTabsManager._prefsRightSide ? 20 : -20;
gZenUIManager.motion.animate( setTimeout(() => {
container, gZenUIManager.elementAnimate(
{ container,
opacity: [0, 1], {
x: [xOffset, 0], opacity: [0, 1],
}, x: [xOffset, 0],
{ },
duration: 0.2, {
type: 'spring', duration: 200,
delay: this.#GLANCE_ANIMATION_DURATION - 0.2, easing: 'ease-in-out',
bounce: 0, fill: 'forwards',
} delay: this.#GLANCE_ANIMATION_DURATION - 200,
); }
);
});
} }
/** /**
@@ -403,7 +405,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
'.browserSidebarContainer' '.browserSidebarContainer'
); );
gZenUIManager.motion.animate( gZenUIManager.elementAnimate(
parentSidebarContainer, parentSidebarContainer,
{ {
scale: [1, 0.98], scale: [1, 0.98],
@@ -411,8 +413,8 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
}, },
{ {
duration: this.#GLANCE_ANIMATION_DURATION, duration: this.#GLANCE_ANIMATION_DURATION,
type: 'spring', easing: 'ease-in-out',
bounce: 0.2, fill: 'forwards',
} }
); );
} }
@@ -428,8 +430,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
this.overlay.removeAttribute('fade-out'); this.overlay.removeAttribute('fade-out');
this.browserWrapper.setAttribute('animate', true); this.browserWrapper.setAttribute('animate', true);
this.browserWrapper.style.top = `${top}px`; this.browserWrapper.style.transform = `translate(${left}px, ${top}px)`;
this.browserWrapper.style.left = `${left}px`;
this.browserWrapper.style.width = `${width}px`; this.browserWrapper.style.width = `${width}px`;
this.browserWrapper.style.height = `${height}px`; this.browserWrapper.style.height = `${height}px`;
@@ -441,9 +442,18 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
* Store the original position for later restoration * Store the original position for later restoration
*/ */
#storeOriginalPosition() { #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 = { this.#glances.get(this.#currentGlanceID).originalPosition = {
top: this.browserWrapper.style.top, top,
left: this.browserWrapper.style.left, left,
width: this.browserWrapper.style.width, width: this.browserWrapper.style.width,
height: this.browserWrapper.style.height, height: this.browserWrapper.style.height,
}; };
@@ -473,14 +483,14 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
this.browserWrapper.prepend(imageDataElement); this.browserWrapper.prepend(imageDataElement);
this.#glances.get(this.#currentGlanceID).elementImageData = data.elementData; this.#glances.get(this.#currentGlanceID).elementImageData = data.elementData;
gZenUIManager.motion.animate( gZenUIManager.elementAnimate(
imageDataElement, imageDataElement,
{ {
opacity: [1, 0], opacity: [1, 0],
}, },
{ {
duration: this.#GLANCE_ANIMATION_DURATION / 2, 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`; 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 * Execute the main glance animation
* @param {Object} data - Glance data * @param {Object} data - Glance data
@@ -521,21 +521,18 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
// Create curved animation sequence // Create curved animation sequence
const arcSequence = this.#createGlanceArcSequence(data, 'opening'); 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 // 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, // nice fade-in effect to the content. But if it doesn't exist,
// we just fall back to always showing the browser directly. // we just fall back to always showing the browser directly.
if (data.elementData) { if (data.elementData) {
gZenUIManager.motion gZenUIManager
.animate( .elementAnimate(
this.contentWrapper, this.contentWrapper,
{ opacity: [0, 1] }, { opacity: [0, 1] },
{ {
duration: this.#GLANCE_ANIMATION_DURATION / 2, duration: this.#GLANCE_ANIMATION_DURATION / 2,
easing: 'easeInOut', easing: 'ease-in-out',
} }
) )
.then(() => { .then(() => {
@@ -544,10 +541,9 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
} }
this.#animateParentBackground(); this.#animateParentBackground();
gZenUIManager.motion gZenUIManager
.animate(this.browserWrapper, arcSequence, { .elementAnimate(this.browserWrapper, arcSequence, {
duration: gZenUIManager.testingEnabled ? 0 : this.#GLANCE_ANIMATION_DURATION, duration: gZenUIManager.testingEnabled ? 0 : this.#GLANCE_ANIMATION_DURATION,
ease: 'easeInOut',
}) })
.then(() => { .then(() => {
this.#finalizeGlanceOpening(imageDataElement, browserElement, resolve); this.#finalizeGlanceOpening(imageDataElement, browserElement, resolve);
@@ -607,28 +603,29 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
); );
const sequence = { const sequence = {
top: [], transform: [],
left: [],
width: [], width: [],
height: [], height: [],
transform: [],
}; };
const steps = this.#ARC_CONFIG.ARC_STEPS; const steps = this.#ARC_CONFIG.ARC_STEPS;
const arcDirection = shouldArcDownward ? 1 : -1; const arcDirection = shouldArcDownward ? 1 : -1;
function easeInOutQuad(t) { function easeOutBack(x) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; const c1 = 0.4;
const c3 = c1 + 1;
return 1 + c3 * (x - 1) ** 3 + c1 * (x - 1) ** 2;
} }
function easeOutCubic(t) { function easeInQuint(x) {
return 1 - Math.pow(1 - t, 6); return x * x * x * x * x;
} }
// First, create the main animation steps // First, create the main animation steps
for (let i = 0; i <= steps; i++) { for (let i = 0; i <= steps; i++) {
const progress = i / steps; const progress = i / steps;
const eased = direction === 'opening' ? easeInOutQuad(progress) : easeOutCubic(progress); const eased = direction === 'opening' ? easeOutBack(progress) : easeInQuint(progress);
// Calculate size interpolation // Calculate size interpolation
const currentWidth = startPosition.width + (endPosition.width - startPosition.width) * eased; const currentWidth = startPosition.width + (endPosition.width - startPosition.width) * eased;
@@ -643,32 +640,11 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
const y = const y =
startPosition.y + distanceY * eased + arcDirection * arcHeight * (1 - (2 * eased - 1) ** 2); startPosition.y + distanceY * eased + arcDirection * arcHeight * (1 - (2 * eased - 1) ** 2);
sequence.transform.push(`translate(-50%, -50%) scale(1)`); sequence.transform.push(`translate(${x}px, ${y}px)`);
sequence.top.push(`${y}px`);
sequence.left.push(`${x}px`);
sequence.width.push(`${currentWidth}px`); sequence.width.push(`${currentWidth}px`);
sequence.height.push(`${currentHeight}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; return sequence;
} }
@@ -724,8 +700,6 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
imageDataElement.remove(); imageDataElement.remove();
} }
this.browserWrapper.style.transformOrigin = '';
browserElement.style.minWidth = ''; browserElement.style.minWidth = '';
browserElement.style.minHeight = ''; browserElement.style.minHeight = '';
@@ -892,14 +866,14 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
*/ */
#animateSidebarButtons(sidebarButtons) { #animateSidebarButtons(sidebarButtons) {
if (sidebarButtons) { if (sidebarButtons) {
gZenUIManager.motion gZenUIManager
.animate( .elementAnimate(
sidebarButtons, sidebarButtons,
{ opacity: [1, 0] }, { opacity: [1, 0] },
{ {
duration: 0.2, duration: 100,
type: 'spring', easing: 'ease-in-out',
bounce: this.#GLANCE_ANIMATION_DURATION - 0.1, fill: 'forwards',
} }
) )
.then(() => { .then(() => {
@@ -928,8 +902,8 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
* @param {Element} browserSidebarContainer - The sidebar container * @param {Element} browserSidebarContainer - The sidebar container
*/ */
#animateParentBackgroundClose(browserSidebarContainer) { #animateParentBackgroundClose(browserSidebarContainer) {
gZenUIManager.motion gZenUIManager
.animate( .elementAnimate(
browserSidebarContainer, browserSidebarContainer,
{ {
scale: [0.98, 1], scale: [0.98, 1],
@@ -937,8 +911,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
}, },
{ {
duration: this.#GLANCE_ANIMATION_DURATION / 1.5, duration: this.#GLANCE_ANIMATION_DURATION / 1.5,
type: 'spring', easing: 'ease-in-out',
bounce: 0,
} }
) )
.then(() => { .then(() => {
@@ -965,10 +938,9 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
const closingData = this.#createClosingDataFromOriginalPosition(originalPosition); const closingData = this.#createClosingDataFromOriginalPosition(originalPosition);
const arcSequence = this.#createGlanceArcSequence(closingData, 'closing'); const arcSequence = this.#createGlanceArcSequence(closingData, 'closing');
gZenUIManager.motion gZenUIManager
.animate(this.browserWrapper, arcSequence, { .elementAnimate(this.browserWrapper, arcSequence, {
duration: this.#GLANCE_ANIMATION_DURATION, duration: this.#GLANCE_ANIMATION_DURATION,
ease: 'easeOut',
}) })
.then(() => { .then(() => {
// Remove element preview after closing animation // Remove element preview after closing animation
@@ -1485,7 +1457,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
this.browserWrapper.style.width = `${browserRect.width}px`; this.browserWrapper.style.width = `${browserRect.width}px`;
this.browserWrapper.style.height = `${browserRect.height}px`; this.browserWrapper.style.height = `${browserRect.height}px`;
await gZenUIManager.motion.animate( await gZenUIManager.elementAnimate(
this.browserWrapper, this.browserWrapper,
{ {
width: ['85%', '100%'], width: ['85%', '100%'],
@@ -1493,8 +1465,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
}, },
{ {
duration: this.#GLANCE_ANIMATION_DURATION, duration: this.#GLANCE_ANIMATION_DURATION,
type: 'spring', easing: 'ease-in-out',
bounce: 0,
} }
); );

View File

@@ -125,7 +125,7 @@
left: 0; left: 0;
flex: unset !important; flex: unset !important;
/* Promote to its own layer during transitions to reduce jank */ /* Promote to its own layer during transitions to reduce jank */
will-change: transform, top, left; will-change: transform;
width: 85%; width: 85%;
height: 100%; height: 100%;
@@ -148,6 +148,10 @@
box-shadow: var(--zen-big-shadow); box-shadow: var(--zen-big-shadow);
} }
&:not([has-finished-animation='true']) .browserStack {
transform: translate(-50%, -50%);
}
& browser { & browser {
background: light-dark(rgb(255, 255, 255), rgb(32, 32, 32)) !important; background: light-dark(rgb(255, 255, 255), rgb(32, 32, 32)) !important;
width: 100%; width: 100%;