How to draw non-scalable circle in SVG with Javascript

后端 未结 4 1813
星月不相逢
星月不相逢 2020-12-16 20:51

I\'m developing a map, in Javascript using SVG to draw the lines.

I would like to add a feature where you can search for a road, and if the road is found, a circle a

相关标签:
4条回答
  • 2020-12-16 20:54

    It's discussed here and here

    It looks like current browsers don't do the expected thing, so one needs to apply the inverse transform of the zoom (scale) on the contents of the <marker>, eg. transorm: scaleX(5) on the user of the <marker> etc. will need to be accompanied by a transform: translate(...) scaleX(0.2) inside the <pattern>, also factoring in possible x/y/width/height/transform-origin values inside the pattern if needed

    0 讨论(0)
  • 2020-12-16 20:57

    If you are looking for a fully static way of doing this, you might be able to combine non-scaling-stroke with markers to get this, since the markers can be relative to the stroke-width.

    In other words, you could wrap the circles in a <marker> element and then use those markers where you need them.

    <svg width="500" height="500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000">
        <marker id="Triangle"
          viewBox="0 0 10 10" refX="0" refY="5" 
          markerUnits="strokeWidth"
          markerWidth="4" markerHeight="3"
          orient="auto">
          <path d="M 0 0 L 10 5 L 0 10 z" />
        </marker>
            <path d="M 100 100 l 200 0" vector-effect="non-scaling-stroke"
            fill="none" stroke="black" stroke-width="10" 
            marker-end="url(#Triangle)"  />
            <path d="M 100 200 l 200 0" 
            fill="none" stroke="black" stroke-width="10" 
            marker-end="url(#Triangle)"  />
    </svg>
    

    The same can also be viewed and tweaked here. The svg spec isn't fully explicit about what should happen in this case (since markers are not in SVG Tiny 1.2, and vector-effect isn't in SVG 1.1). My current line of thinking was that it should probably affect the size of the marker, but it seems no viewers do that at the moment (try in a viewer that supports vector-effect, e.g Opera or Chrome).

    0 讨论(0)
  • 2020-12-16 21:01

    Looks like some work was done in webkit (maybe related to this bug: 320635) and the new transform doesn't stick around when simply appended like that

    transform.baseVal.appendItem
    

    This seems to work better. Even works in IE 10.

    EDIT: Fixed the code for more general case of multiple translate transformations in the front and possible other transformations after. First matrix transformation after all translates must be reserved for unscale though.

    translate(1718.07 839.711) translate(0 0) matrix(0.287175 0 0 0.287175 0 0) rotate(45 100 100)

    function unscale()
    {
        var xf = this.ownerSVGElement.createSVGTransform();
        var m = this.ownerSVGElement.getTransformToElement(this.parentNode);
        m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
        xf.setMatrix(m);
    
        // Keep a single transform matrix in the stack for fighting transformations
        // Be sure to apply this transform after existing transforms (translate)
        var SVG_TRANSFORM_MATRIX = 1;
        var SVG_TRANSFORM_TRANSLATE = 2;
        var baseVal = this.transform.baseVal;
        if(baseVal.numberOfItems == 0)
            baseVal.appendItem(xf);
        else
        {
            for(var i = 0; i < baseVal.numberOfItems; ++i)
            {
                if(baseVal.getItem(i).type == SVG_TRANSFORM_TRANSLATE && i == baseVal.numberOfItems - 1)
            {
                    baseVal.appendItem(xf);
                }
    
                if(baseVal.getItem(i).type != SVG_TRANSFORM_TRANSLATE)
                {
                    if(baseVal.getItem(i).type == SVG_TRANSFORM_MATRIX)
                        baseVal.replaceItem(xf, i);
                    else
                        baseVal.insertItemBefore(xf, i);
                    break;
                }
            }
        }
    }
    

    EDIT2: Chrome killed getTransformToElement for some reason, so the matrix needs to be retrieved manually:

    var m = this.parentNode.getScreenCTM().inverse().multiply(this.ownerSVGElement.getScreenCTM());
    
    0 讨论(0)
  • 2020-12-16 21:05

    It took me a while, but I finally got the math clean. This solution requires three things:

    1. Include this script in your page (along with the SVGPan.js script), e.g.
      <script xlink:href="SVGPanUnscale.js"></script>
    2. Identify the items you want not to scale (e.g. place them in a group with a special class or ID, or put a particular class on each element) and then tell the script how to find those items, e.g.
      unscaleEach("g.non-scaling > *, circle.non-scaling");
    3. Use transform="translate(…,…)" to place each element on the diagram, not cx="…" cy="…".

    With just those steps, zooming and panning using SVGPan will not affect the scale (or rotation, or skew) of marked elements.

    Demo: http://phrogz.net/svg/scale-independent-elements.svg

    Library

    // Copyright 2012 © Gavin Kistner, !@phrogz.net
    // License: http://phrogz.net/JS/_ReuseLicense.txt
    
    // Undo the scaling to selected elements inside an SVGPan viewport
    function unscaleEach(selector){
      if (!selector) selector = "g.non-scaling > *";
      window.addEventListener('mousewheel',     unzoom, false);
      window.addEventListener('DOMMouseScroll', unzoom, false);
      function unzoom(evt){
        // getRoot is a global function exposed by SVGPan
        var r = getRoot(evt.target.ownerDocument);
        [].forEach.call(r.querySelectorAll(selector), unscale);
      }
    }
    
    // Counteract all transforms applied above an element.
    // Apply a translation to the element to have it remain at a local position
    function unscale(el){
      var svg = el.ownerSVGElement;
      var xf = el.scaleIndependentXForm;
      if (!xf){
        // Keep a single transform matrix in the stack for fighting transformations
        // Be sure to apply this transform after existing transforms (translate)
        xf = el.scaleIndependentXForm = svg.createSVGTransform();
        el.transform.baseVal.appendItem(xf);
      }
      var m = svg.getTransformToElement(el.parentNode);
      m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
      xf.setMatrix(m);
    }
    

    Demo Code

    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      <title>Scale-Independent Elements</title>
      <style>
        polyline { fill:none; stroke:#000; vector-effect:non-scaling-stroke; }
        circle, polygon { fill:#ff9; stroke:#f00; opacity:0.5 }
      </style>
      <g id="viewport" transform="translate(500,300)">
        <polyline points="-100,-50 50,75 100,50" />
        <g class="non-scaling">
          <circle  transform="translate(-100,-50)" r="10" />
          <polygon transform="translate(100,50)" points="0,-10 10,0 0,10 -10,0" />
        </g>
        <circle class="non-scaling" transform="translate(50,75)" r="10" />
      </g>
      <script xlink:href="SVGPan.js"></script>
      <script xlink:href="SVGPanUnscale.js"></script>
      <script>
        unscaleEach("g.non-scaling > *, circle.non-scaling");
      </script>
    </svg>
    
    0 讨论(0)
提交回复
热议问题