fix(transition): correct draw and crossfade rendering bugs

draw: scale getTotalLength() by CTM when vector-effect is non-scaling-stroke
  When vector-effect: non-scaling-stroke is set the stroke is rendered in
  screen pixels but getTotalLength() returns SVG user units. Without the
  correction the dasharray/dashoffset values are in the wrong unit space,
  causing the path to animate in discontinuous segments. Scale len by the
  x-basis magnitude of the CTM (sqrt(a²+b²)) to match the rendered length.
  Fixes #6868

crossfade: add mix-blend-mode: plus-lighter to eliminate opacity dip
  At the midpoint of a standard-alpha crossfade (t=0.5, opacity=1) the two
  elements' opacities sum to 0.5 + (1-0.5)*0.5 = 0.75, producing a
  visible dim flash. With additive (plus-lighter) blending the opacities
  sum linearly: 0.5 + 0.5 = 1.0 at every point in the transition.
  Fixes #6668
pull/18889/head
okxint 3 days ago
parent 890d7f8b98
commit be74d2781d

@ -197,6 +197,15 @@ export function scale(
export function draw(node, { delay = 0, speed, duration, easing = cubic_in_out } = {}) {
let len = node.getTotalLength();
const style = getComputedStyle(node);
// When vector-effect: non-scaling-stroke is set, the stroke lives in screen
// pixels but getTotalLength() returns SVG user units. Scale len by the CTM
// x-basis magnitude so dasharray/dashoffset match the rendered stroke length.
if (style.vectorEffect === 'non-scaling-stroke') {
const ctm = /** @type {SVGGraphicsElement} */ (node).getCTM?.();
if (ctm) {
len = len * Math.sqrt(ctm.a * ctm.a + ctm.b * ctm.b);
}
}
if (style.strokeLinecap !== 'butt') {
len += parseInt(style.strokeWidth);
}
@ -275,6 +284,7 @@ export function crossfade({ fallback, ...defaults }) {
easing,
css: (t, u) => `
opacity: ${t * opacity};
mix-blend-mode: plus-lighter;
transform-origin: top left;
transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${
t + (1 - t) * dh

Loading…
Cancel
Save