Search code examples
jquery-uisvgscaling

Having trouble with moving and scaling svg elements with jQuery UI sliders?


I have an SVG element with defined text and a path. Please see my example code.

The text works as desired, scales from the center and can be positioned accurately but I have I am having terrible trouble with the path.

When I touch the slider to move x or y the scale gets thrown off and it always scales from the top left corner. I've tried numerous examples online but I have had no luck so I cleaned up the example code.

I would like to be able scale the path from it's center and when I change it's position it doesn't affect the scale.

$( ".slider-x" ).slider({
      min: -530,
      max: 530,
      value: 165,
      slide: function(){
        var x = $( ".slider-x" ).slider( "value" ),
            y = $( ".slider-y" ).slider( "value" );
        $('.svg-size').attr("transform","translate("+x+","+y+")");
      }
});
$( ".slider-y" ).slider({
      min: -250,
      max: 250,
      value: -50,
      slide: function(){
        var x = $( ".slider-x" ).slider( "value" ),
            y = $( ".slider-y" ).slider( "value" );
        $('.svg-size').attr("transform","translate("+x+","+y+")");
      }
});
$( ".slider-scale" ).slider({
      min: 1,
      max: 100,
      value: 40,
      slide: function(){
        var x = $( ".slider-x" ).slider( "value" ),
            y = $( ".slider-y" ).slider( "value" );
        var scale = $( ".slider-scale" ).slider( "value" );
        $('.svg-size ').attr("transform","translate("+x+","+y+") scale(" + scale/100 + ")");
      }
});
$( ".slider-scale-title-text" ).slider({
      min: 1,
      max: 100,
      value: 40,
      slide: function(){
        var scale = $( ".slider-scale-title-text" ).slider( "value" );
        $('.svg-business-title').css("font-size", scale+ "px");
      }
});
$( ".slider-text-x" ).slider({
      min: -100,
      max: 635,
      value: 250,
      slide: function(){
        var x = $( ".slider-text-x" ).slider( "value" ),
            y = $( ".slider-text-y" ).slider( "value" );
        $('.title-text').attr("x",x);
      }
  
});
$( ".slider-text-y" ).slider({
      min: 0,
      max: 210,
      value: 150,
      slide: function(){
        var x = $( ".slider-text-x" ).slider( "value" ),
            y = $( ".slider-text-y" ).slider( "value" );
        $('.title-text').attr("y",y);
      }
      
});
h3 {
  font-family: verdana;
}
.svg-business-title {
  fill: brown;
  stroke-width: 4;
}
.svg-strap-line {
  fill: red;
  stroke-width: 4;
}
.svg-custom-logo-wrapper {
  fill: #94d31b;
  position:absolute;
  left:0; top:0; width:100%; height:100%;
}
.svg-wrapper {
  width: 100%;
  position:relative;
  height:180px;
}
.svg-container {
  max-width: 535px;
  margin: 0 auto;
}
.accent-colour-1 {
  fill: red;
}
.accent-colour-2 {
  fill: green;
}
.svg-size{
/*   transform: scale(0.2);
  -ms-transform: scale(0.2);
  -webkit-transform: scale(0.2); */
/*   transform-origin: 270px 5px; */
}
.slider-controls, .slider-scale-title-text {
  max-width: 540px;
}
.slider, input {
  margin: 10px 0 10px 10px;
}
button {
  margin: 5px 0 0 0;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div class="svg-container">
<div class="svg-wrapper">
<svg data-logoid="123" width="535" height="180" viewBox="0 0 535 180" class="svg-custom-logo-wrapper" xmlns:xlink="http://www.w3.org/1999/xlink"> <rect width="535" height="180" style="fill:none;stroke-width:1;stroke:rgb(0,0,0)"/>
<g class="svg-size" transform="translate (165,-50) scale(0.4)"><path class="accent-colour-1 path-1" d="m 255,108 -97,97 -27,-27 -73,72 19,21 54,-54 106,107 -25,25 66,-26 105,-106 46,46 29,-11 -75,-74 -29,29 z m 0,40 79,79 -77,77 -79,-79 z m -16,63 h 14 v 14 h -14 z m 22,-22 h 14 v 14 h -14 z m 109,80 h 11 v 11 h -11 z m 18,-18 h 11 v 11 h -11 z m -278,43 h 16 v 15 h -16 z m 25,-25 h 16 v 15 h -16 z"></path></g>
<g class="svg-size" transform="translate (165,-50)  scale(0.4)"><path class="accent-colour-2 path-2" d="M 164,381 48,259 27,281 160,404 485,254 Z M 388,269 h 11 v 11 h -11 z m -18,-18 h 11 v 11 h -11 z m -235,43 h 16 v 15 h -16 z m -25,-25 h 16 v 15 h -16 z m 151,-58 h 14 v 14 h -14 z m -22,-22 h 14 v 14 h -14 z"></path></g>
<text text-anchor="middle" class="title-text svg-business-title" transform="scale(1)" x="250" y="150" font-size="40">Title Text Here</text>
</svg>
</div>
</div>
<div class="slider-controls">
  <p>LOGO</p>
  X<div class="slider slider-x"></div>
  Y<div class="slider slider-y"></div>
  Scale<div class="slider slider-scale"></div>
</div>
<div class="slider-controls">
  <p>Text</p>
  X<div class="slider slider-text-x"></div>
  Y<div class="slider slider-text-y"></div>
  Scale<div class="slider slider-scale-title-text">
</div>
</div>


Solution

  • You were using one transform attribute for three independent operations, in fact overwriting values that you would need to maintain. In addition, transform="scale()" always scales around the origin of the local coordinate system. So to scale the paths in place, they must be centered at x = y = 0.

    The steps to take to rectify the situations in a maintainable way:

    1. Distribute translating and scaling the paths to two enclosing groups, the outer one being the translation (.svg-position), the inner one the scale (.svg-size).

    2. Determine the center of the paths:

      document.querySelector('.svg-size').getBBox()
      // returns { x: 27, y: 108, width: 458, height: 296 }
      // => center at 256, 256
      
    3. introduce a inner group that moves the paths to the origin: translate(-256, -256)

    4. Add 256 * 0.4 = 102 (rounded) to the starting translation to get the paths back to their initial position.

    5. adjust the scale values accordingly.

    $( ".slider-x" ).slider({
          min: -430,
          max: 630,
          value: 267,
          slide: function(){
            var x = $( ".slider-x" ).slider( "value" ),
                y = $( ".slider-y" ).slider( "value" );
            $('.svg-position').attr("transform","translate("+x+","+y+")");
          }
    });
    $( ".slider-y" ).slider({
          min: -150,
          max: 350,
          value: 52,
          slide: function(){
            var x = $( ".slider-x" ).slider( "value" ),
                y = $( ".slider-y" ).slider( "value" );
            $('.svg-position').attr("transform","translate("+x+","+y+")");
          }
    });
    $( ".slider-scale" ).slider({
          min: 1,
          max: 100,
          value: 40,
          slide: function(){
            var x = $( ".slider-x" ).slider( "value" ),
                y = $( ".slider-y" ).slider( "value" );
            var scale = $( ".slider-scale" ).slider( "value" );
            $('.svg-size ').attr("transform","scale(" + scale/100 + ")");
          }
    });
    $( ".slider-scale-title-text" ).slider({
          min: 1,
          max: 100,
          value: 40,
          slide: function(){
            var scale = $( ".slider-scale-title-text" ).slider( "value" );
            $('.svg-business-title').css("font-size", scale+ "px");
          }
    });
    $( ".slider-text-x" ).slider({
          min: -100,
          max: 635,
          value: 250,
          slide: function(){
            var x = $( ".slider-text-x" ).slider( "value" ),
                y = $( ".slider-text-y" ).slider( "value" );
            $('.title-text').attr("x",x);
          }
      
    });
    $( ".slider-text-y" ).slider({
          min: 0,
          max: 210,
          value: 150,
          slide: function(){
            var x = $( ".slider-text-x" ).slider( "value" ),
                y = $( ".slider-text-y" ).slider( "value" );
            $('.title-text').attr("y",y);
          }
          
    });
    h3 {
      font-family: verdana;
    }
    .svg-business-title {
      fill: brown;
      stroke-width: 4;
    }
    .svg-strap-line {
      fill: red;
      stroke-width: 4;
    }
    .svg-custom-logo-wrapper {
      fill: #94d31b;
      position:absolute;
      left:0; top:0; width:100%; height:100%;
    }
    .svg-wrapper {
      width: 100%;
      position:relative;
      height:180px;
    }
    .svg-container {
      max-width: 535px;
      margin: 0 auto;
    }
    .accent-colour-1 {
      fill: red;
    }
    .accent-colour-2 {
      fill: green;
    }
    .svg-size{
    /*   transform: scale(0.2);
      -ms-transform: scale(0.2);
      -webkit-transform: scale(0.2); */
    /*   transform-origin: 270px 5px; */
    }
    .slider-controls, .slider-scale-title-text {
      max-width: 540px;
    }
    .slider, input {
      margin: 10px 0 10px 10px;
    }
    button {
      margin: 5px 0 0 0;
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css" rel="stylesheet"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    <div class="svg-container">
    <div class="svg-wrapper">
    <svg data-logoid="123" width="535" height="180" viewBox="0 0 535 180" class="svg-custom-logo-wrapper" xmlns:xlink="http://www.w3.org/1999/xlink"> <rect width="535" height="180" style="fill:none;stroke-width:1;stroke:rgb(0,0,0)"/>
    <g class="svg-position" transform="translate(267,52)">
      <g class="svg-size" transform="scale(0.4)">
        <g transform="translate(-256 -256)">
          <path class="accent-colour-1 path-1" d="m 255,108 -97,97 -27,-27 -73,72 19,21 54,-54 106,107 -25,25 66,-26 105,-106 46,46 29,-11 -75,-74 -29,29 z m 0,40 79,79 -77,77 -79,-79 z m -16,63 h 14 v 14 h -14 z m 22,-22 h 14 v 14 h -14 z m 109,80 h 11 v 11 h -11 z m 18,-18 h 11 v 11 h -11 z m -278,43 h 16 v 15 h -16 z m 25,-25 h 16 v 15 h -16 z"></path>
          <path class="accent-colour-2 path-2" d="M 164,381 48,259 27,281 160,404 485,254 Z M 388,269 h 11 v 11 h -11 z m -18,-18 h 11 v 11 h -11 z m -235,43 h 16 v 15 h -16 z m -25,-25 h 16 v 15 h -16 z m 151,-58 h 14 v 14 h -14 z m -22,-22 h 14 v 14 h -14 z"></path>
        </g>
      </g>
    </g>
    <text text-anchor="middle" class="title-text svg-business-title" transform="scale(1)" x="250" y="150" font-size="40">Title Text Here</text>
    </svg>
    </div>
    </div>
    <div class="slider-controls">
      <p>LOGO</p>
      X<div class="slider slider-x"></div>
      Y<div class="slider slider-y"></div>
      Scale<div class="slider slider-scale"></div>
    </div>
    <div class="slider-controls">
      <p>Text</p>
      X<div class="slider slider-text-x"></div>
      Y<div class="slider slider-text-y"></div>
      Scale<div class="slider slider-scale-title-text">
    </div>
    </div>