3

NOTE: This is not a duplicate of questions like this. I saw those and fixed my viewbox attribute to be viewBox. That led to the question I have here.

I am trying to build an SGF (Smart Game Format) editor using Angular 4 to practice the technology. I want the app to be responsive so I can use it nearly as well on a smartphone as I can on a desktop, so I similarly want the graphics to be responsive. It seemed that SVG would be a good choice (what with "scalable" being in the name), so I experimented until I got some effects I liked:

a 33% scale image of my board SVG with the famous Ear-Reddening Move

Here is a JSFiddle of the full-size starting point.

Before copying this into my Angular application, I did some research to determine whether I would need to do anything else to make my SVG responsive. CreativeBlog stated that I would need to replace my width and height attributes with a viewBox attribute, so I did so. I also noted the following paragraph, but decided not to eliminate by constant references to millimeters as units:

First, unless there are compelling reasons to do otherwise, set measurements to 'pixels' in your vector tool. While it doesn't matter to SVG (which will measure the viewBox and elements happily in almost any measurement system) it makes sense to develop the SVG drawing using common CSS units, rather than the print default of inches; and it also makes it much easier to add @media queries and other interventions later.

I ran my Angular application, and was surprised to see this fairly useless view:

the upper-left corner of my board, rendered full-size in a small rectangle

Here is a JSFiddle that shows the slight modification.

After several misadventures in fixes using preserveAspectRatio, inserting an svg prefix for the SVG namespace, and trying to find an Angular approach to resize the image, I tried surrendering by removing all the millimeter unit designations. The result was one step forward, two steps back: the image was now mostly responsive (it still is not adjusting for the available height correctly), but in turn my filters for the board and stone textures were ruined:

the board with response width but ruined textures

This JSFiddle shows the current SVG plus the surrounding Bootstrap divs and minus the added svg prefix since JSFiddle does not understand that prefix.

How can I get a SVG that will responsively resize as I need while still having the filters that seem to require my millimeter resolution work?

And, while I am willing to ask a new question for what may be a separate issue, if someone knows how to make the width adjust for the available screen height the way it looks like preserveAspectRatio is supposed to, I would appreciate it if that one slipped that answer in, too :)

sadakatsu
  • 1,255
  • 18
  • 36

2 Answers2

1

Filters and sizing is a fiddly thing. Things do not look the same if applied to larger and smaller elements.

What you did when you removed all the mm units is scaling your grafic down by a factor of approx. 4. To be exact, it depends on your screen resolution. For a resolution of 90 dpi, the factor is 3.5433. If you had scaled every value with unit mm in your source with that value, the result would have been what you intended.

Technical background: <filter> elements have an attribute primitiveUnits with a default value of userSpaceOnUse. This means that things like the positioning of the spotlight and other values defined in your filters are expressed effectivly in pixel units, while the elements they were applied to had sizes in mm. (This is much simplified, but I am not going into explaining viewports, transformations and user space coordinates here.) Removing the mm got the relative sizing of filter and element out of scale.

I would advise against trying to recalculate these filter values. There are a lot of values, and it is anything but obvious which of them express lengths and are meant to represent pixels.

Go back to your starting version. If you want to recalculate things, it is better to scale all the mm values. These are more, but you can identify them.

Then, start your SVG file like this, assuming 90dpi:

<svg
    version="1.1"
    width="100%"
    height="100%"
    viewBox="0 0 1503 1608.67"
    preserveAspectRatio="xMinYMin meet"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">

This way you give control of the size on the screen to the element surrounding the SVG, while every unit inside is in relation to the viewBox.

Your woodgrain filter will overflow the board area unless you do this:

    <filter id="woodGrain" x="0" y="0" width="1" height="1">

In case you are wondering, these units are by default multiples of the element bounding box.

The lighting filter had a syntax error. There is no out attribute for filter primitives. It is called result:

    <filter id="lighting">
        <feGaussianBlur in="SourceGraphic" stdDeviation="3" result="bump" />

Finally, the board rectangle should be written with absolute sizes:

    <rect
        width="1503"
        height="1608.67"
        style="fill: rgb(220,168,71); filter: url(#woodGrain);" />

Lastly, a comment you can ignore if you go back to your first version. It is here only as a general lecture on SVG sizing.

Your last version shows a misunderstanding: <g> elements have no size. You cannot set width, height and viewBox there. If you use percentage sizes in child elements like the <rect>, they will refer to the size of the <svg> element. And that does not have the size of the viewBox, but what is set with the width and height attributes. The viewBox is fitted inside that according to the preserveAspectRatio rule, but parts that overflow the viewBox are still visible and only clipped at the border of the <svg>.

ccprog
  • 20,308
  • 4
  • 27
  • 44
  • Thanks for your help. As you probably guess, I am an SVG beginner. I used https://stackoverflow.com/a/29252825/385043 to get the ratio of pixels-to-millimeters for my screen (3.77813). Then I wrote a Python script to generate a version of the file with all the millimeter units replaced. I then added all the modifications except for the "out -> result" fix to get exactly what I wanted: https://jsfiddle.net/8wL8drf0/ . The weird thing is that if I make your "out -> result" fix, I break my specular lighting effect. Clearly, that "bump" is doing something. Any ideas? – sadakatsu Oct 03 '17 at 01:33
  • What browser are you viewing the SVG with? With Firefox, using `out` as the attribute name results in the stones not being rendered. With Edge and Chrome, there (almost) is no visible difference. There deffinitely is no `out` attribute for filter primitives. So the behavior is a result of reacting to errors. The *later* filter primitives have `in="bump"` but cannot find that source channel since there is no `result="bump"` in an earlier primitive. FF crashes, Chome and Edge use a default value instead. But what you describe seems to make no sense. – ccprog Oct 03 '17 at 13:42
0

To be honest, working with SVG filters in this use case is a waste of computing resources. Design them as SVG if you want, but then save them as high-res PNGs. The computing power for downscaling these images is much less than the one needed to compute the filter effect.

If you use a seamless pattern for your woodgrain effect, you will end up with three images of at most a few hundred kB. So it is not even a bandwidth problem, and mobile device users will thank you.

ccprog
  • 20,308
  • 4
  • 27
  • 44