sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
79.69k stars 4.22k forks source link

Transition:draw shows weird behaviour when vector-effect: non-scaling-stroke is applied. #6868

Open gdagosto opened 3 years ago

gdagosto commented 3 years ago

Describe the bug

When applying vector-effect: non-scaling-stroke to a svg path, svelte's transition:draw shows weird behaviour. Instead of starting the animation from the beggining, it splits it into multiple parts, animating each part separately.

I noticed this behaviour while testing animating Rich Harris Pancake library, since it applies non-scaling-stroke on the charts. (https://github.com/Rich-Harris/pancake)

Reproduction

Line in blue doesn't have any vector effect (vector-effect: none). Line in red has vector-effect: non-scaling-stroke applied to it.

https://svelte.dev/repl/177e5996f77e4870b1579bc91ecc53b3?version=3.44.0

Logs

No response

System Info

System:
    OS: Windows 10 10.0.19043
    CPU: (12) x64 AMD Ryzen 5 3600 6-Core Processor
    Memory: 5.28 GB / 15.95 GB
  Binaries:
    Node: 14.8.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.4 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
    npm: 6.14.7 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 94.0.4606.81
    Edge: Spartan (44.19041.1266.0), Chromium (94.0.992.50)
    Internet Explorer: 11.0.19041.1202
  npmPackages:
    rollup: ^2.47.0 => 2.58.0
    svelte: ^3.38.2 => 3.44.0

Severity

annoyance

decepulis commented 2 years ago

As a pancake user, I started looking into this myself!

draw relies on node.getTotalLength() to provide the length of the path. However, a non-scaling-stroke scales the path to a new coordinate system.

I found that we can translate between the two coordinate systems by leveraging the fact that getBoundingClientRectangle returns to us the size in DOM units and getBBox returns the size in SVG units:

const { width: domWidth } = node.getBoundingClientRect();
const { width: svgWidth } = node.getBBox();
const scale = domWidth / svgWidth;
len = len * scale;

Note: I've literally never used it or heard of it before... but I noticed that node.getCTM() actually returns the same scaling factor. That being said, I didn't pay attention in linear algebra, so when getCTM tells me it returns a matrix... I run away.

It's not perfect for some reason. I'm still trying to figure that out. I went ahead and implemented by own version of the draw function for the REPL that you linked above, and as you can see, there's no weird artifacts (which means that it's good enough for my purposes), but the timing is still slightly off. ~I'll report back if I figure out why that is.~


EDIT: whoops, yep, figured out why that is. My solution seems to work in situations where the viewBox aspect ratio is the same as the DOM aspect ratio. (Updated REPL to demonstrate). Where that isn't the case, the vertical and horizontal scaling factors are different, and therefore, the path length calculation is different.