chore: Glance motion animations to native, b=no-bug, c=glance

This commit is contained in:
mr. m
2026-01-03 18:37:01 +01:00
parent babd7ad871
commit dee1d2c279
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)
});
#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',
}
);

View File

@@ -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%;