maxwellito / vivus

JavaScript library to make drawing animation on SVG
MIT License
15.25k stars 1.13k forks source link

Possible limitation when using paths containing subpaths #80

Closed bahamadon closed 8 years ago

bahamadon commented 8 years ago

Hello maxwellito.

First let me say how much I appreciate your work on vivus. It's a pleasure to work with and a delight to see in action.

I recently encountered an issue when I attempted to animate a path that contained subpaths (i.e., one that contained discontinuous segments linked by "M" commands). There were two anomalies: (1) All the subpaths were drawn simultaneously rather than in sequence along the longer path; and (2) the animation concluded well after the paths were drawn (as indicated by when the completion callback fired). This behaviour occurred under MacOS El Capitan using Safari 9.0.2.

After some testing, I think the two problems are a result of an ambiguity in the SVG specification (see this thread) and the way stroke-dasharrary is implemented in my browser. As I understand it, the method getTotalLength computes the total length of subpaths, excluding the gaps defined by any MoveTos. However, the dasharray pattern is repeated at the start of each subpath. Each subpath displays a new dash, making it appear as if it was being drawn independently and in parallel with the others (similar to "async" mode). But, since the length of the dasharray is longer than any one subpath, the animation is still stepping through its frames even when all the subpaths appear to be fully drawn. This causes the delay before the completion callback executes.

Or is it that I just don't understand something fundamental about vivus?

If this is a limitation due to the way dasharray is implemented in some browsers, then it seems to me there are two options for drawing a discontinuous path: Continue to use a path containing subpaths, but recognize that the individual subpaths will be drawn in parallel and that the timing may be off; or, refactor the svg path so that each subpath is a separate path element and script the timing using the "scenario" option of vivus.

Regardless, the result is well worth the effort. Thanks again for creating this library.

maxwellito commented 8 years ago

Hi, First thanks for these kind words :) About the problem you're mentioning, I think I encounter this problem in the path on a weird SVG, but not sure. Can you provide me an example please? I think it would be more helpful :) Thanks

bahamadon commented 8 years ago

Sure! Unfortunately, I'm away from my computer for several hours, but I'll send you a file as soon as I get back.

Don

Sent from my iPhone

On Dec 28, 2015, at 1:37 PM, maxwellito notifications@github.com wrote:

Hi, First thanks for these kind words :) About the problem you're mentioning, I think I encounter this problem in the path on a weird SVG, but not sure. Can you provide me an example please? I think it would be more helpful :) Thanks

— Reply to this email directly or view it on GitHub.

bahamadon commented 8 years ago

Hello.

In the hope that this will be helpful, I’m sending you two files.

One (propeller.html) is a complex svg that illustrates the issue. The svg contains two animated paths (id=“blades” and id=“jackets”), a circle (id=“hub”) and an ignored path (id=“initials”). Paths “blades” and “jackets” contain three subpaths of connected Bezier curves. Path “blades” displays three propeller blades, while path “jackets” shows the outline of each blade’s heating jacket. The svg is animated as async, with a callback using the display hack from the vivus website.

Since “blades” and “jackets” are single paths, I expected the first blade to be drawn, followed by the second, and then the third. Path “jackets” would start drawing at the same time and, similarly, would be drawn with each jacket in sequence. “Hub” would start at the same time and all three elements would finish at the same time, at which point the initials “CT” would appear.

Note, though, that what happens is that all three blades are drawn in parallel, not in sequence. The same happens for the three jacket outlines. Also, the hub seems to be drawn very slowly, and completes well after the blades and jackets paths.

The second file (three_lines.html) is much simpler and easier to follow. It contains one path, with three disconnected lines. The completion callback is a simple alert dialog.

Since this is a single path, my expectation was that first one segment would be drawn, then the next, and finally the last. Instead, all three are drawn at the same time. And notice how long it takes for the completion callback to be triggered.

As I mentioned in my issue report, I think what is happening is that the dasharray starts at the beginning of each subpath, so that it appears as if three animations are going on. In reality, though, the algorithm is acting as if one long animation is in progress. So, at the time all three lines appear to be fully drawn, the algorithm still has two-thirds of its steps to complete. Hence the delay in the completion callback.

If this hypothesis is correct, then the problem isn’t caused by vivus; it’s really a consequence of the svg specification and the way a browser would implement it in the case of a path containing subpaths. From some of the discussion I read in response to the thread I referenced, it seems like the svg specification hasn’t addressed this fully.

Regardless, I think an adequate workaround in vivus is to construct all paths without subpaths. A limitation, to be sure, but not an onerous one.

I hope all this is understandable. I haven’t been coding Javascript for very long, so I could be seriously misreading things. Regardless, I hope this is helpful.

On Dec 28, 2015, at 1:37 PM, maxwellito notifications@github.com wrote:

Hi, First thanks for these kind words :) About the problem you're mentioning, I think I encounter this problem in the path on a weird SVG, but not sure. Can you provide me an example please? I think it would be more helpful :) Thanks

— Reply to this email directly or view it on GitHub https://github.com/maxwellito/vivus/issues/80#issuecomment-167662446.

bahamadon commented 8 years ago

The two files referenced in the above reply were sent by email as attachments. For reference, I'm also including them here.

propeller.html.gz three_lines.html.gz

maxwellito commented 8 years ago

Hi Don,

Thanks for this detailed response. It's rich and documented :)

About three_lines.html & propeller.html:

I was talking about this problem, I had the same. Thanks for your example because I finally understood it. On my use case the SVG was too complex so I didn't dive into it. So from a simple path, you can have more than one line delimited by M. Make sense. I can get the list of each path segment from the property pathSegList. Unfortunately, I cannot get their length. But even if I could get it, Vivus couldn't animate them independently. This is due to the SVG implementation, try to play with the strokeDasharray and strokeDashoffset and you will see.

But there is a card to play :) Part of the Pathbuilder would be to parse path tags to split the path segments into independent path tags. But it will cause issues if the tag got an ID or use 'scenario' attributes, because it will duplicate it. It's tricky.

For now, you better construct all path dependently. Sorry :(

Btw, can I ask you which vector graphics editor you're using? I never managed to get many lines in the same path tag with Illustrator.

bahamadon commented 8 years ago

It sounds like we've come to the same conclusion: The issue arises from the way SVG implementations handle strokeDasharray and strokeDashoffset rather than anything vivus is doing. To provide a visually attractive dash pattern, the SVG implementations start each path segment with the beginning of the dasharray. It looks like this is just the cost of being able to use the dasharray as a way to animate path drawing.

I hadn't thought of using Pathbuilder as a way around this. It's a clever solution, although, as you say, it probably would cause issues further down the pike. For now, I think a workflow that avoids subpaths is fine. I can certainly live with it.

Since you asked, I use Sketch as my SVG authoring tool. It's lighter than Illustrator and seems well designed for the kind of vector art used in webpages. But I have a bit of a confession to make about the examples I sent you. I created propeller.html using the techniques described in jxnblk's tutorial on how to build svg icons using react. I wanted the mathematical precision that a scripting environment provides. I cobbled together _threelines.html using a text editor. But Sketch was great for checking the work...

Thanks for your attention to this. It's probably a really narrow use case, but I appreciate your taking the time to work it through for me.

Don

maxwellito commented 8 years ago

Hi Don,

Sorry to cannot help you more. But if you want to go for hacking the Pathformer I'll be happy to help :)

Thanks for sharing the jxnblk's tutorial, that's very interesting. About Sketch, you're not the first to tell me the benefits of it, I really need to give a try. But Illustrator is my third hand.

Max

bahamadon commented 8 years ago

Thanks, Max. Really appreciate your looking into it.

Don