📜 ⬆️ ⬇️

As we wrote SVG widgets for javascript

Disclaimer

Honestly, I initially had a task to write about our product in Habré. Initially, I planned to limit the usual PR essay, describing the main features and functions of our component, without focusing on the details. Of course, such a story would allow to fully reveal all the chips of our product. On the other hand, he would certainly have caused you to yawn at about the third paragraph.

Therefore, I will try to make this article useful not only for the author, but also for the readers. I will describe not so much what we did, how much we did. We begin, of course, with the task that we faced.


Task

These are the widgets you can do
Imagine that you are a web programmer who implements a complex SCADA system, a dashboard (I’m sorry, but I never met a clear translation of this word into Russian), an interactive metrics management system, or you just need to insert a clock on your site with a tricky by design. In this case, you need to add all sorts of scales there, twirls with arrows (in English this is called Gauge), watches and other "devices", perhaps even interactive ones.
')
At first glance, this problem is solved quite simply. For example, there is a free component Google Gauge and many different pieces that fall on request in the same Google. On the other hand, in most of these libraries, the range of options is usually limited. As soon as you need to do something of your own - the principle “it's easier to write oneself” begins to work.

Therefore, our task was to create a system that would allow not only to customize the existing “twists”, but also to create them from scratch . The system was supposed to allow the widget to be assembled from graphic primitives (geometric shapes, arrows, special elements like “test tubes”, “springs”), etc. In addition, we wanted to develop a scripting language with which the programmer could link the values ​​of individual widget elements (for example, the number in the text field and the position of the arrow in the scale).

So, the team consisting of two programmers, QA (who connected a little later) and the product manager (who constantly prevented everyone from working, forcing everything to be redone, "because the user is uncomfortable or difficult") , began development right after the New Year holidays.

The matter was facilitated by the fact that by the time we started working on the project, we already had a similar product for Windows Forms. We successfully dragged out the widget object model and the widget editor for Windows.

We were faced with several technical tasks that were to be resolved:

This is what JavaScript code looks like for working hours.

Framework

Before we get to work, we had to choose the tools. I would venture to call holivar and indignant cries that we "do not know how to cook JS", but still I will say. Writing on “pure” JavaScript is a task that requires excessive assiduity, attentiveness and too much of the “operative memory” of the programmer. From the experience of custom development, I can convincingly say that high-quality development of an RIA application in pure JavaScript takes one and a half to two times more time than creating a system of the same quality using Flash or Silverlight (yes it rests with the world) .

In our case, the matter was aggravated by the fact that we had a professional Silverlight team that had to be retrained (alas, Microsoft strategists sometimes know how to annoy their own developers). Accordingly, in order to reach the same level in working with “clean” JS, it would take several years and a couple of “merged” products.

However, we have found a very good way out of the situation, which, by the way, we will actively recommend to all .NET developers writing RIA in JavaScript. This output was a framework called Script # , which is being developed by Microsoft employees. It allows you to write C # code and translate it in JavaScript. And, I will say absolutely honestly: the quality of the JS-code at the output is very, very decent. Of course, the set of functions there is quite limited, but it saves the ability to implement individual methods entirely in JS, after which they are translated without change into the resulting file. In general, if you write in C #, I recommend to try.

Architecture


SVG vs. Canvas
Weather widget It was immediately clear that the development should be carried out using HTML5. The first question we, of course, asked was the choice between SVG and Canvas.

On the one hand, SVG was the best suited for working with widgets assembled from separate objects and primitives in the editor. On the other hand, we feared for the performance of SVG and its support in different browsers. I really did not want to be in a situation where the component normally works only in the latest version of Google Chrome. Moreover, the important point was the support of mobile devices: iOS, Android and other zoo.

If you look at the Canvas, the hardware support in many browsers was an undoubted plus, and the obvious drawback is the need for complex redrawing of objects during animation (for example, when moving the arrow, you will have to redraw some of the objects under it).

After some reflection, we realized that the entire rendering system should be written in abstractions that are not dependent on the specifics of SVG or Canvas (hello, Cap). After that, a separate set of classes is implemented for drawing and redrawing objects using a specific technology. In the first version, we decided to limit ourselves to SVG, because we were very attracted by the ability to turn the arrows using the angle attribute, rather than redrawing the image for each turn. In addition, we wanted to use the standard SVG animation (alas, with this there were big problems) and CSS to control the styles of the widget.

Thus, while all widgets are drawn on pure SVG.

Object model
The structure of the widget looks like this Taking into account the fact that we already had a ready-made system for Windows Forms, from which at least we were going to actively use the editor, it was decided to base the same object model and adapt it a bit to the Web.

Given that the development was conducted on Script #, the porting of the model was reduced to a banal "copy-paste". All attempts to leave one code base, unfortunately, were unsuccessful. There were too many differences in behavior, drawing and other trifles. Because of this, a bunch of conditional directives and branches appeared, which only made it harder to understand the code and did more harm than good. As a result, it was decided that the principle of DRY will not be offended. As time has shown, we did everything correctly - there are practically no pieces in the code of the web part that are identical to the Windows part.

However, to be objective, there are successful examples of code separation between C # and Script # products.

Serialization
These spring balls also move To store the WinForms widgets that served as the starting point of our work, we used our own binary format. Files in this format were read and saved by the widget editor. On the other hand, I really wanted the web version to have the widget set in the “native” way for JavaScript. Obviously, for this we have chosen JSON.

It was originally planned that JSON with the description of the widget will be created by the user (that is, by the programmer using our product), but then this idea was slightly moved away. JSON with the description of the object is created when exporting the widget in the editor. In the first version, we followed the path of least resistance and screwed the standard JSON-Serializer, which comes with the .NET Framework 3.5. Without going into the implementation details, I’ll say that the widget's object model is serialized in JSON in the standard way and the result is given to the programmer. The programmer when adding a widget to the page passes this JSON as a constructor parameter (in JavaScript). When creating a widget, JSON is parsed by the engine, and a set of SVG primitives is built on it.

One of the most basic shortcomings, the fight against which is already planned for the near future, is the abundance of unnecessary unnecessary information in the resulting JSON description. This is due to the fact that we use standard serialization mechanisms and get a copy of the “as is” object, including a bunch of unnecessary properties (alas, we have to serialize all public fields). Because of this, the JSON description looks monstrous and takes up quite a lot of space. We even specially made a JSON inspector so that programmers could see how their widget is arranged by loading JSON with the description into a special form.

By the way, we added the ability to change any JSON parameters at the time of creating the widget, so it’s very easy, having a general description, to vary the details: for example, change the scale of the scale, make the arrow invisible, etc.

Implementation


Animation
We took types and the description of animation from jQuery It is clear that the arrow in the scale should not (in most cases) move from one value to another by a “quantum jump”, but should smoothly pass all intermediate values ​​and, going a little beyond the desired value, return there elegantly. In addition, it is important that the widget does not eat up 146% of the processor, performing this smooth movement.

Generally, we initially tried to honestly use CSS-animation ... Alas, we suffered a crushing defeat, losing two weeks. This is probably the most striking moment, where features of web development, generated by a zoo from browsers that do not want to work with one standard, are fully manifested.
As a result, after it became finally clear that the quod licet Chrome non licet Internet Explorer , we wrote our own animation with preference and courtesans in JavaScript. This greatly untied our hands. For example, we were able to implement almost all variants of acceleration-deceleration and other beauty.

The main fears that everything will slow down turned out to be in vain and, as soon as we stopped redrawing the entire widget to a small change in the position of the arrow, everything just began to fly. Conclusion - normally optimized redrawing “manually” by SetInterval is almost as good as CSS animation performance, but it works the same in all browsers.

CSS
The arrows of all widgets are configured using CSS
Our task was to give the programmer the ability to configure the colors of the individual elements of the widget in CSS. In general, there are no problems here. There is an SVG element, it has a class, there is a CSS with settings for this class. The only thing that disappointed is the inability to properly set the gradient in the CSS for SVG. According to the specification, in order to use a gradient fill for SVG objects, it is first necessary to declare it first in the same SVG.

Therefore, I had to write a separate method in JavaScript, which would allow to set the gradient fill for objects. Of course, it still allowed the programmer to set this fill, but it was unfortunately not possible to render all the “design” settings in CSS, as we wanted initially.

Another very unpleasant moment was the absence of a conical gradient fill in the SVG. This is an extremely convenient fill method for instruments that use scales with colors that change smoothly, for example, from red to green. Accordingly, it was used in a variety of widgets for WinForms. We had to approximate a conic gradient linear, which is not always successful.

Interactivity
-  , We basically wrote widgets so that the user could, by pulling the arrow or slider, set a new value. Of course, the widget calls the callback and notifies the JavaScript.

Processing mousedown and mouseup was done for the div into which the widget was inserted. When you click the mouse button, we determined which element the user clicked on and, accordingly, lined up further logic depending on that.

At the same time, it was quite a difficult task to determine when the user clicks on a region with a relatively complex boundary. In “normal” languages, the HitTest method is usually used for this, but unfortunately it does not exist in JavaScript for SVG.

Since the boundary of the area is usually not set by a polygon, but by some Bezier curve, it was not possible to apply simple algorithms like summation of angles. Bounding Box is quite suitable for testing clicks on most objects, but there are exceptions in the form of large and thin crescent-shaped crescent-shaped elements.

In the end, it was decided to write HitText for each component separately, recalling the analytic geometry and approximating the object with simpler primitives.

Another small problem arose when we began to redraw the widget in response to user actions. For example, the user “grabs” the arrow and starts spinning it with great speed. Naturally, you need to redraw a lot of things. The positive point of using vector SVG was the ability to redraw (and in most cases just rotate) only the objects being moved and not to touch the rest, while using the Canvas we would have to redraw the object under the arrow as well. On the other hand, sometimes I had to redraw quite a lot.

Of course, initially everything was incredibly slowed down, but after a couple of hours of optimization it became easy to fly. And it flew until we allowed users to add complex scripts to the widget, which forced not only the directly moving arrow to be redrawn, but also a bunch of other components associated with it by scripts.

By the way, an interesting note: the local version of JavaScript on a page loaded from the file system runs much slower than on the same page in the same browser, but downloaded from the server. Who knows why, please share in the comments.

In order to implement support for gestures for touch devices, it is necessary to handle completely different events, for example, touchstart, touchend, touchmove, etc. Therefore, this support had to be written separately.

Scripts and internal logic
. SharpShooter Gauges was famous for supporting internal scripts. These scripts were the key moment in the development of the widget, since they determined the binding of the position of the graphic elements to the scales and guides.

In addition, the scripts allowed us to interconnect the sizes of elements, format the output text, and so on. The presence of scripts made it possible to create a widget of almost any complexity, describing the interactions between individual objects and primitives inside it, without shifting this task onto the shoulders of the programmer who embeds the widget in his product (encapsulation, yeah) .

Initially, we had the idea to completely translate internal scripting into JavaScript syntax. But, thinking about our old users, who will not understand if their WinForms widgets suddenly stop working in the new version of the system, we decided to leave the internal scripting as it is and translate the scripts to JS when exporting. However, the volume of this heroic initiative exceeded all imaginable and inconceivable terms and fairly delayed the release.

Despite the unconditional power of internal scripting, there are also outspoken cons.

First, the problem is in implicitly casting objects (for example, dates) to a string. The fact is that in JavaScript and .NET, the date is converted to a string differently, which adds a fair amount of incompatibility to the Windows and HTML5 versions.

Secondly, there was initially a sharp deterioration in the performance of the widget in the presence of complex script links. However, we did not lose heart and set about optimizing. About a week later, complex scripts had practically no effect on the animation, and the slowdowns were noticeable only with a very fast drag and drop of objects in IE. In chrome, everything worked perfectly. We decided to stop at this.

Selected minor issues
-\"\" After reading this article, the guys made a few comments about other problems that would be worth mentioning, so in this section I will describe the most typical of them.

The inability to calculate the size of the text. In SVG, as in many other graphics systems, there is no way to determine in advance what size the text will take after drawing. Since the version for WinForms relied a little more than completely on this GDI function, we had to invent clever schemes to implement it. Now the whole text is drawn twice. Once - in order to find out the dimensions, and the second time already with normal positioning.

Problems with RGBA on iOS devices. Toward the middle of development, testing widgets on different device models, we noticed that on the iPhone 4 and iPad, our fill was completely black. Of course, the widgets because of this looked very gothic and funny, but still completely wrong. After some research, we realized that the RGBA format, which was used to determine the color, is not supported in Safari under iOS4 + (surprisingly, everything worked on the iPhone 3G). As a result, we divided the RGBA value into two separate (color and transparency) wherever we used it. It helped.

findings

I have already said that I would like to avoid the bias of the article in “marketing bullshit” and make it more or less useful from a technical point of view. Therefore, here are some conclusions that can help when working with SVG in JavaScript.

Conclusion 1. Most modern browsers support normal work with SVG and give visually the same result.
Conclusion 2. Freymvork Script # copes with its task, allowing you to use C # for RIA development for JavaScript and HTML5.
Conclusion 3. Standard CSS-animation is still too raw and browser-dependent to use it for work. “Manual” animation, built on redrawing part of the SVG, works the same everywhere and, with minimal optimization, does not slow down at all (even in IE).
Conclusion 4. From CSS you can properly configure the style of SVG elements. In order to configure the gradient fill, it must first be declared in the same SVG, so one CSS is indispensable. Conical fill, alas, is not supported.
Conclusion 5. iOS devices (except for the oldest) do not support RGBA color. Therefore, when setting styles, you need to set RGB and Alpha separately.
Conclusion last. We made a cool product that we like ourselves :).
And, of course, you can see what happened here .

Source: https://habr.com/ru/post/149975/


All Articles