svg rotation, scale and translate with mouse

2019-06-27 12:03发布

Trying to apply rotation, move and resize using mouse on SVG element. Here you can test this.

Currently I worked on South control, Center control and Rotate control.

  1. Rotation works perfectly fine, I can rotate, stop and again rotate. But after I move the element by dragging center point, the rotation flickers first time and rotation starting point is different. I belive this is because center position is changed after translate. I tried recalculating center position but it did not work.

  2. And scaling is moving the element instead of increasing the size.

Please help me on this. I am missing some adjustments here.

Note: First you have draw some path with mouse to get controls on it.

var svg = document.querySelector('.container');
var svgns = 'http://www.w3.org/2000/svg';

var path = document.createElementNS(svgns, 'path');
svg.appendChild(path);

var points = [];
var Resizer_Instance = null;

var boundingBox = svg.getBoundingClientRect();

var toSVGPath = function(points) {
  var SVGPath = '';
  for (var i = 0; i < points.length; i++) {
    var prefix = (i == 0) ? 'M' : 'L';
    SVGPath += prefix + points[i].x + ' ' + points[i].y + ' ';
  }
  return SVGPath;
};

var create_mousedown = false;

var createStart = function(event) {
  create_mousedown = true;
};

var creating = function(event) {
  if (create_mousedown) {
    var point = svg.createSVGPoint();
    point.x = event.clientX - boundingBox.left;
    point.y = event.clientY - boundingBox.top;
    var t = point.matrixTransform(svg.getScreenCTM().inverse());
    points.push(t);
    path.setAttributeNS(null, 'd', toSVGPath(points));
  }
};

var createEnd = function(event) {
  create_mousedown = true;
  svg.removeEventListener('mousedown', createStart);
  svg.removeEventListener('mousemove', creating);
  svg.removeEventListener('mouseup', createEnd);
  setTimeout(function functionName() {
    Resizer_Instance = new Resizer(path, svg);
  }, 500);
};

svg.addEventListener('mousedown', createStart);
svg.addEventListener('mousemove', creating);
svg.addEventListener('mouseup', createEnd);


var Resizer = (function() {

  function Resizer(element) {
    var that = this;
    that.element = element;
    createSelector.call(that);

    document.addEventListener('mousemove', dragging);
    document.addEventListener('mouseup', dragend);

  }

  var RAD2DEG = 180 / Math.PI;

  function angleBetweenPoints(p1, p2) {
    var angle = null;
    if (p1.x == p2.x && p1.y == p2.y)
      angle = Math.PI / 2;
    else
      angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
    return (angle * RAD2DEG) + -90;
  }

  function controlPositions(el) {
    var pt = svg.createSVGPoint();
    var bbox = el.getBoundingClientRect();
    var matrix = el.getScreenCTM().inverse();
    var halfWidth = bbox.width / 2;
    var halfHeight = bbox.height / 2;
    var placements = {};

    pt.x = bbox.left;
    pt.y = bbox.top;

    placements['nw'] = pt.matrixTransform(matrix);
    pt.x += halfWidth;
    placements['n'] = pt.matrixTransform(matrix);
    pt.x += halfWidth;
    placements['ne'] = pt.matrixTransform(matrix);
    pt.y += halfHeight;
    placements['e'] = pt.matrixTransform(matrix);
    pt.y += halfHeight;
    placements['se'] = pt.matrixTransform(svg.getScreenCTM().inverse());
    pt.x -= halfWidth;
    placements['s'] = pt.matrixTransform(matrix);
    pt.x -= halfWidth;
    placements['sw'] = pt.matrixTransform(matrix);
    pt.y -= halfHeight;
    placements['w'] = pt.matrixTransform(matrix);
    pt.x += halfWidth;
    placements['center'] = pt.matrixTransform(matrix);
    pt.y -= (halfHeight + 30);
    placements['rot'] = pt.matrixTransform(matrix);

    return placements;
  }

  var dragging_element = null;

  var dragstart = function(event) {
    var box = this;
    var context = box.context;
    var rootContext = context.rootContext;
    rootContext.current_handle_inaction = context.direction;
    dragging_element = box;
  };

  var dragging = function(event) {
    if (!dragging_element) return;
    var box = dragging_element;
    var context = box.context;
    var rootContext = context.rootContext;
    var currentHandle = rootContext.current_handle_inaction;
    var control_points = rootContext.control_points;

    if (currentHandle === context.direction) {
      var point = svg.createSVGPoint();
      point.x = event.clientX;
      point.y = event.clientY;
      var element = rootContext.element;
      var transformed = point.matrixTransform(svg.getScreenCTM().inverse());

      var centerPosition = context.center;

      rootContext.angle = rootContext.angle || 0;
      rootContext.hMove = rootContext.hMove || 0;
      rootContext.vMove = rootContext.vMove || 0;
      rootContext.scaleX = rootContext.scaleX || 1;
      rootContext.scaleY = rootContext.scaleY || 1;

      switch (currentHandle) {
        case "rot":
          rootContext.angle = angleBetweenPoints(transformed, centerPosition);
          break;
        case "center":
          rootContext.hMove = transformed.x - centerPosition.x;
          rootContext.vMove = transformed.y - centerPosition.y;
          break;
        case "s":
          var startPos = control_points[currentHandle];
          var vMove = transformed.y - startPos.y;
          rootContext.scaleY += (vMove > 0 ? -1 : 1) * 0.001;
          break;
      }

      var move_transform = "translate(" + rootContext.hMove + " " + rootContext.vMove + ")";
      var rotate_transform = "rotate(" + rootContext.angle + ", " + centerPosition.x + ", " + centerPosition.y + ")";
      var scale_transform = "scale(" + rootContext.scaleX + ", " + rootContext.scaleY + ")";

      var transform = [move_transform, rotate_transform, scale_transform].join(' ');

      rootContext.element.setAttribute('transform', transform);
      rootContext.controlGroup.setAttribute('transform', transform);
    }
  };

  var dragend = function() {
    if (!dragging_element) return;
    var box = dragging_element;
    var context = box.context;
    var rootContext = context.rootContext;
    delete rootContext.current_handle_inaction;
    // createSelector.call(rootContext);
    dragging_element = null;
  };

  var adjustPositions = function() {
    var that = this;
    var control_points = that.control_points;
    var controlGroup = that.controlGroup;
    var point = svg.createSVGPoint();
    for (var direction in control_points) {
      var dP = control_points[direction];
      point.x = dP.x;
      point.y = dP.y;
      debugger;
      control_points[direction] = point.matrixTransform(controlGroup.getScreenCTM().inverse());
    }
    return control_points;
  };

  var Deg2Rad = 0.017453292519943295;

  var createSelector = function() {
    var that = this;
    var points = that.control_points;
    if (points) {
      points = adjustPositions.call(that);
    } else {
      points = controlPositions(that.element, svg);
    }
    that.control_points = points;
    var existingBoxes = {};
    var controlGroup = that.controlGroup;

    if (!controlGroup) {
      controlGroup = document.createElementNS(svgns, 'g');
      that.controlGroup = controlGroup;
      svg.appendChild(controlGroup);
    }

    that.control_boxes = that.control_boxes || {};

    var line_name = "connecting-line",
      line_element = that.control_boxes['connecting-line'];

    var line_route = ["nw", "n", "rot", 'n', "ne", "e", "se", "s", "sw", "w", "nw"];

    if (!line_element) {
      line_element = document.createElementNS(svgns, 'path');
      line_element.style.cssText = "fill: none; stroke: #f41542; opacity: 0.5";

      that.control_boxes[line_name] = line_element;
      controlGroup.appendChild(line_element);

      var pathString = "";

      line_route.forEach(function(direction) {
        var point = points[direction];
        var command = pathString.length === 0 ? "M" : " L ";
        pathString += (command + point.x + " " + point.y);
      });

      line_element.setAttribute('d', pathString);
    }

    Object.keys(points).forEach(function(direction) {
      var point = points[direction];
      var box = that.control_boxes[direction];
      if (!box) {
        box = document.createElementNS(svgns, 'circle');
        box.style.cssText = "fill: #5AABAB";

        that.control_boxes[direction] = box;
        box.setAttributeNS(null, 'r', 3);
        box.setAttribute('handle', direction);

        box.addEventListener('mousedown', dragstart.bind(box));

        controlGroup.appendChild(box);
      }

      box.setAttributeNS(null, 'cx', point.x);
      box.setAttributeNS(null, 'cy', point.y);

      box.context = {
        point: point,
        direction: direction,
        rootContext: that,
        center: points.center
      };

    });


  };

  var prototype = {
    constructor: Resizer
  };
  Resizer.prototype = prototype;
  return Resizer;
})();
path {
  fill: none;
  stroke: #42B6DF;
}
body,
html {
  height: 100%;
  width: 100%;
  margin: 0;
}
<svg class="container" version="1.1" baseProfile="full" style="position:absolute;left:0;top:0;height:100%;width:100%;-ms-transform:scale(1,1);transform:scale(1,1);-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-o-transform:scale(1,1);transform:scale(1,1);-ms-transform-origin:0, 0;-webkit-transform-origin:0, 0;-moz-transform-origin:0, 0;-o-transform-origin:0, 0;transform-origin:0, 0"
viewBox="-220.38356461849224 6442.3347962008365 454.7376658611161 114.54981723871151"></svg>

2条回答
唯我独甜
2楼-- · 2019-06-27 13:01

You are currently calculating angle in relation to the initial center of the figure (the one when you have just drawn it). This is wrong - you need to calculate angle in relation to the center of the figure after previous move.

Fiddle

I've stripped the parts that I didn't change.

var dragging = function(event) {
    ...

    if (currentHandle === context.direction) {
        ...

        var initialCenterPosition = context.center,
            // use the coordinates saved after last move or
            // initial coordinates if there are none saved
            previousCenterPosition = rootContext.previousCenterPosition || initialCenterPosition;

        ...

        switch (currentHandle) {
            case "rot":
                rootContext.angle = angleBetweenPoints(transformed, previousCenterPosition);
                break;
            case "center":
                rootContext.hMove = transformed.x - initialCenterPosition.x;
                rootContext.vMove = transformed.y - initialCenterPosition.y;

                // remember the new center coordinates
                rootContext.previousCenterPosition = {
                    x: transformed.x,
                    y: transformed.y
                };
                break;
            case "s":
                ...
        }

        var move_transform = "translate(" + rootContext.hMove + " " + rootContext.vMove + ")";
        var rotate_transform = "rotate(" + rootContext.angle + ", " + initialCenterPosition.x + ", " + initialCenterPosition.y + ")";
        var scale_transform = "scale(" + rootContext.scaleX + ", " + rootContext.scaleY + ")";

        ...
    }
}
查看更多
不美不萌又怎样
3楼-- · 2019-06-27 13:02

You can't easily handle all three transform operations (translate, scale and rotate) with just those three transform functions alone. You actually should use four functions.

You should remember the original centre point of the element. Let's call this ocx and ocy. Then do the following:

  1. translate the original centre point to the origin
  2. perform the scale
  3. perform the rotation
  4. translate the centre back to the new (current) centre position.

So the transform string would look something like this:

transform="translate(ncx,ncy) rotate(rot) scale(sx,sy) translate(-ocx,-ocy)"

This way you can keep all the operations isolated, and you don't need to alter the others when one changes.

查看更多
登录 后发表回答