fix(editor): improve scroll animation interpolation (#11562)
This commit is contained in:
@@ -103,11 +103,53 @@ const getTargetViewport = (
|
|||||||
return { scrollX, scrollY, zoom: state.zoom };
|
return { scrollX, scrollY, zoom: state.zoom };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolates the viewport from `from` to `target` at the (already-eased)
|
||||||
|
* blend amount `factor` (0 = `from`, 1 = `target`).
|
||||||
|
*
|
||||||
|
* Zoom is interpolated geometrically (so it feels uniform), and rather than
|
||||||
|
* tweening scrollX/scrollY directly we tween the *focal point* — the scene
|
||||||
|
* point under the viewport center — and derive scroll from it. Mixing a linear
|
||||||
|
* scroll with a geometric zoom makes the focal point swoop sideways
|
||||||
|
* mid-animation (most visible when zooming out); gliding the focal point keeps
|
||||||
|
* it steady. `width/2/zoom - scroll` is the inverse of `centerScrollOn` without
|
||||||
|
* offsets, so factor 0/1 land exactly on `from`/`target`.
|
||||||
|
*/
|
||||||
|
export const interpolateViewport = ({
|
||||||
|
from,
|
||||||
|
target,
|
||||||
|
factor,
|
||||||
|
}: {
|
||||||
|
from: Pick<AppState, "scrollX" | "scrollY" | "zoom" | "width" | "height">;
|
||||||
|
target: Viewport;
|
||||||
|
factor: number;
|
||||||
|
}): Viewport => {
|
||||||
|
const zoom = (from.zoom.value *
|
||||||
|
Math.pow(
|
||||||
|
target.zoom.value / from.zoom.value,
|
||||||
|
factor,
|
||||||
|
)) as NormalizedZoomValue;
|
||||||
|
|
||||||
|
const fromCenterX = from.width / 2 / from.zoom.value - from.scrollX;
|
||||||
|
const fromCenterY = from.height / 2 / from.zoom.value - from.scrollY;
|
||||||
|
const toCenterX = from.width / 2 / target.zoom.value - target.scrollX;
|
||||||
|
const toCenterY = from.height / 2 / target.zoom.value - target.scrollY;
|
||||||
|
|
||||||
|
const centerX = fromCenterX + (toCenterX - fromCenterX) * factor;
|
||||||
|
const centerY = fromCenterY + (toCenterY - fromCenterY) * factor;
|
||||||
|
|
||||||
|
return {
|
||||||
|
scrollX: from.width / 2 / zoom - centerX,
|
||||||
|
scrollY: from.height / 2 / zoom - centerY,
|
||||||
|
zoom: { value: zoom },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/** Eases the viewport from its current position to `target` over `duration`,
|
/** Eases the viewport from its current position to `target` over `duration`,
|
||||||
* driving the transition through the shared AnimationController so it doesn't
|
* driving the transition through the shared AnimationController so it doesn't
|
||||||
* slow down other processes. */
|
* slow down other processes. */
|
||||||
const animateToViewport = (
|
const animateToViewport = (
|
||||||
from: Pick<AppState, "scrollX" | "scrollY" | "zoom">,
|
from: Pick<AppState, "scrollX" | "scrollY" | "zoom" | "width" | "height">,
|
||||||
target: Viewport,
|
target: Viewport,
|
||||||
duration: number,
|
duration: number,
|
||||||
onFrame: (
|
onFrame: (
|
||||||
@@ -125,17 +167,8 @@ const animateToViewport = (
|
|||||||
const factor = easeOut(clamp(progress, 0, 1));
|
const factor = easeOut(clamp(progress, 0, 1));
|
||||||
|
|
||||||
onFrame({
|
onFrame({
|
||||||
|
...interpolateViewport({ from, target, factor }),
|
||||||
shouldCacheIgnoreZoom: progress < 1, // ignore zoom caching while animating
|
shouldCacheIgnoreZoom: progress < 1, // ignore zoom caching while animating
|
||||||
scrollX: from.scrollX + (target.scrollX - from.scrollX) * factor,
|
|
||||||
scrollY: from.scrollY + (target.scrollY - from.scrollY) * factor,
|
|
||||||
// zoom interpolates geometrically so the transition feels natural
|
|
||||||
zoom: {
|
|
||||||
value: (from.zoom.value *
|
|
||||||
Math.pow(
|
|
||||||
target.zoom.value / from.zoom.value,
|
|
||||||
factor,
|
|
||||||
)) as NormalizedZoomValue,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// returning a falsy value signals the AnimationController to remove the
|
// returning a falsy value signals the AnimationController to remove the
|
||||||
|
|||||||
Reference in New Issue
Block a user