8

This is not a duplicate of the other question. I found this talking about rotation about the center using XML, tried to implement the same using vanilla JavaScript like rotate(45, 60, 60) but did not work with me.

The approach worked with me is the one in the snippet below, but found the rect not rotating exactly around its center, and it is moving little bit, the rect should start rotating upon the first click, and should stop at the second click, which is going fine with me.

Any idea, why the item is moving, and how can I fix it.

var NS="http://www.w3.org/2000/svg";  
var SVG=function(el){
    return document.createElementNS(NS,el);
}

var svg = SVG("svg");
    svg.width='100%';
    svg.height='100%';
document.body.appendChild(svg);

class myRect {
  constructor(x,y,h,w,fill) {
   this.SVGObj= SVG('rect'); // document.createElementNS(NS,"rect");
   self = this.SVGObj;
      self.x.baseVal.value=x;
      self.y.baseVal.value=y;
      self.width.baseVal.value=w;
      self.height.baseVal.value=h;
      self.style.fill=fill;
      self.onclick="click(evt)";
      self.addEventListener("click",this,false);
  }
}

Object.defineProperty(myRect.prototype, "node", {
    get: function(){ return this.SVGObj;}
});

Object.defineProperty(myRect.prototype, "CenterPoint", {
    get: function(){ 
                   var self = this.SVGObj;
                   self.bbox = self.getBoundingClientRect();  // returned only after the item is drawn   
                   self.Pc = {
                       x: self.bbox.left + self.bbox.width/2,
                       y: self.bbox.top  + self.bbox.height/2
                 };
         return self.Pc;
         }
});

myRect.prototype.handleEvent= function(evt){
  self = evt.target;  // this returns the `rect` element
  this.cntr = this.CenterPoint;  // backup the origional center point Pc
  this.r =5;

switch (evt.type){
    case "click":   
       if (typeof self.moving == 'undefined' || self.moving == false) self.moving = true;
       else self.moving = false;
 
     if(self.moving == true){
     self.move = setInterval(()=>this.animate(),100);
     }
      else{
       clearInterval(self.move);
      }        
    break;
    default:
    break;
 } 
}  

myRect.prototype.step = function(x,y) {
   return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().translate(x,y));
}

myRect.prototype.rotate = function(r) {
   return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().rotate(r));
}

myRect.prototype.animate = function() {
       self = this.SVGObj;
          self.transform.baseVal.appendItem(this.step(this.cntr.x,this.cntr.y));
            self.transform.baseVal.appendItem(this.rotate(this.r));
            self.transform.baseVal.appendItem(this.step(-this.cntr.x,-this.cntr.y)); 
};

for (var i = 0; i < 10; i++) {
    var x = Math.random() * 100,
        y = Math.random() * 300;
    var r= new myRect(x,y,10,10,'#'+Math.round(0xffffff * Math.random()).toString(16));
    svg.appendChild(r.node);
}

UPDATE

I found the issue to be calculating the center point of the rect using the self.getBoundingClientRect() there is always 4px extra in each side, which means 8px extra in the width and 8px extra in the height, as well as both x and y are shifted by 4 px, I found this talking about the same, but neither setting self.setAttribute("display", "block"); or self.style.display = "block"; worked with me.

So, now I've one of 2 options, either:

  • Find a solution of the extra 4px in each side (i.e. 4px shifting of both x and y, and total 8px extra in both width and height),
  • or calculating the mid-point using:

    self.Pc = { x: self.x.baseVal.value + self.width.baseVal.value/2, y: self.y.baseVal.value + self.height.baseVal.value/2 };

The second option (the other way of calculating the mid-point worked fine with me, as it is rect but if other shape is used, it is not the same way, I'll look for universal way to find the mid-point whatever the object is, i.e. looking for the first option, which is solving the self.getBoundingClientRect() issue.

Community
  • 1
  • 1
Hasan A Yousef
  • 22,789
  • 24
  • 132
  • 203
  • that is an often answered thing… The Trick: Translate the Object so that the center of Rotation/Scale is at the origin, rotate, translate back. All these three steps can be combined in one transformation matrix. – philipp Jul 19 '16 at 07:27
  • Possible duplicate of [Rotate rectangle around its own center in SVG](http://stackoverflow.com/questions/15138801/rotate-rectangle-around-its-own-center-in-svg) – philipp Jul 19 '16 at 07:28
  • 1
    By the way… When writing es6 classes, you do not need to use this `Object.defineProperty(myRect.prototype …`, since you can create the member methods of the class directly, what translates to this `prototype` thing… – philipp Jul 19 '16 at 07:34
  • @philipp I refered to the linked you provided, in my question itself, it is not duplicate, and that way did not work with me :( regarding the `Object.defineProperty...` I used it because I prefer to have all my declaration outside the main `class { }` – Hasan A Yousef Jul 19 '16 at 07:57
  • @philipp the problem is in the 3rd step, i.e. when I translate back, it is not going back! – Hasan A Yousef Jul 19 '16 at 08:06
  • Dont know, there are a lot of things in the code, which I would not consider ideal, so it is a bit hard to figure that out for me… First: You append a new Transform Item to the list for each step, but it could/should stay a single matrix all the time. Second I think your first and third step are exchanged … – philipp Jul 19 '16 at 08:25
  • @philip if you have a vanilla JS code that code rotate the rect around it's center, I can tweak my code to handle it. – Hasan A Yousef Jul 19 '16 at 08:39
  • @philipp pls remove the duplication tag you assigned fit my question, it is not a duplicate of the one you pointed to, that one is XML, this one is vanilla JS – Hasan A Yousef Jul 19 '16 at 08:45

2 Answers2

1

Here we go…

FIDDLE

Some code for documentation here:

let SVG = ((root) => {
    let ns = root.getAttribute('xmlns');

    return {
        e (tag) {
            return document.createElementNS(ns, tag);
        },

        add (e) {
            return root.appendChild(e)
        },

        matrix () {
            return root.createSVGMatrix();
        },

        transform () {
            return root.createSVGTransformFromMatrix(this.matrix());
        }
    }

 })(document.querySelector('svg.stage'));



class Rectangle {
    constructor (x,y,w,h) {
        this.node = SVG.add(SVG.e('rect'));
        this.node.x.baseVal.value = x;
        this.node.y.baseVal.value = y;
        this.node.width.baseVal.value = w;
        this.node.height.baseVal.value = h;
        this.node.transform.baseVal.initialize(SVG.transform());
    }

    rotate (gamma, x, y) {
        let t  = this.node.transform.baseVal.getItem(0),
            m1 = SVG.matrix().translate(-x, -y),
            m2 = SVG.matrix().rotate(gamma),
            m3 = SVG.matrix().translate(x, y),
           mtr = t.matrix.multiply(m3).multiply(m2).multiply(m1);
        this.node.transform.baseVal.getItem(0).setMatrix(mtr);
    }
}
philipp
  • 15,947
  • 15
  • 61
  • 106
  • well cocked @philipp :) I found the mistake in my code to be calculating the center point of the `rect` using the `self.getBoundingClientRect()` there is always 4px extra in each side, which means 8px extra in the width and 8px extra in the height, as well as both x and y are shifted by 4 px, when I recalculated the center point the same way you did, my code run fine, I saw this http://stackoverflow.com/a/8600771/2441637 but setting `self.setAttribute("display", "block");` or `self.style.display = "block";` did not solve my issue yet. – Hasan A Yousef Jul 19 '16 at 16:12
  • I updated your answer to include my tweaked code based on your answer, once you approve the edit, I'll mark your answer as the correct and complete one, thanks. – Hasan A Yousef Jul 19 '16 at 21:06
  • Can't see any changes… – philipp Jul 20 '16 at 04:59
  • @phiiipp this is really strange, the edit had been rejected by 5 guys with the reason: "This edit was intended to address the author of the post and makes no sense as an edit. Have a better answer? Please write a new answer.", not sure if you can see this: http://stackoverflow.com/review/suggested-edits/13059954 – Hasan A Yousef Jul 20 '16 at 10:05
  • @HasanAYousef I had a look at your edit and I honestly understand the rejection, since therein is a lot more code, which is needed for animation and other interaction, but is not central to questions asked. I think good questions and answers focus on one/the main problem, which in this is how to rotate a svg element around an arbitrary point, period. Everything else is decoration and specific for your application, so not of central interest for readers in the future, searching for solutions to this very problem. – philipp Jul 20 '16 at 10:14
  • thanks for your note, honestly still do not understand how some people think here, first they marked my question as duplicated one, while it is not, then mark my edit as un-required, while I believe it could be helpful for some one some day, some people prefer more details in the answer with FULL example, not just a snippet, considering different people have different tastes, believe such thing should be considered, any how, thanks again, I posted my edit as a separate answer, believing some people will get a benefit from it, thanks again :) – Hasan A Yousef Jul 20 '16 at 10:37
  • working or not I haven't checked, but the code is beautiful. –  Feb 12 '18 at 13:44
0

Thanks @Philipp,

Solving catching the SVG center can be done, by either of the following ways:

  • Using .getBoundingClientRect() and adjusting the dimentions considering 4px are extra in each side, so the resulted numbers to be adjusted as:

     BoxC = self.getBoundingClientRect();        
     Pc = {
         x: (BoxC.left - 4) + (BoxC.width - 8)/2,
         y: (BoxC.top  - 4) + (BoxC.height - 8)/2
     };
    

    or by:

  • Catching the .(x/y).baseVal.value as:

    Pc = {
         x: self.x.baseVal.value + self.width.baseVal.value/2, 
         y: self.y.baseVal.value + self.height.baseVal.value/2
    };
    

Below a full running code:

let ns="http://www.w3.org/2000/svg";
var root = document.createElementNS(ns, "svg");
    root.style.width='100%';
    root.style.height='100%';
    root.style.backgroundColor = 'green';
document.body.appendChild(root);

//let SVG = function() {};  // let SVG = new Object(); //let SVG = {};
class SVG {};

SVG.matrix = (()=> { return root.createSVGMatrix(); });
SVG.transform = (()=> { return root.createSVGTransformFromMatrix(SVG.matrix()); });
SVG.translate = ((x,y)=> { return SVG.matrix().translate(x,y) });
SVG.rotate = ((r)=> { return SVG.matrix().rotate(r); });

class Rectangle {
 constructor (x,y,w,h,fill) {                            
   this.node = document.createElementNS(ns, 'rect');
    self = this.node;  
        self.x.baseVal.value = x;
        self.y.baseVal.value = y;
        self.width.baseVal.value = w;
        self.height.baseVal.value = h;
        self.style.fill=fill;
        self.transform.baseVal.initialize(SVG.transform());  // to generate transform list
    this.transform  = self.transform.baseVal.getItem(0),  // to be able to read the matrix
    this.node.addEventListener("click",this,false);
  }
}

Object.defineProperty(Rectangle.prototype, "draw", {
    get: function(){ return this.node;}
});

Object.defineProperty(Rectangle.prototype, "CenterPoint", {
    get: function(){ 
                   var self = this.node;
                   self.bbox = self.getBoundingClientRect();  // There is 4px shift in each side   
                   self.bboxC = {
                       x: (self.bbox.left - 4) + (self.bbox.width - 8)/2,
                       y: (self.bbox.top  - 4) + (self.bbox.height - 8)/2
                 };

                 // another option is:
                 self.Pc = {
                       x: self.x.baseVal.value + self.width.baseVal.value/2, 
                       y: self.y.baseVal.value + self.height.baseVal.value/2
                 };
       return self.bboxC;          
    //   return self.Pc;  // will give same output of bboxC
         }
});

Rectangle.prototype.animate = function () {
 let move01 = SVG.translate(this.CenterPoint.x,this.CenterPoint.y), 
      move02 = SVG.rotate(10), 
            move03 = SVG.translate(-this.CenterPoint.x,-this.CenterPoint.y); 
            movement = this.transform.matrix.multiply(move01).multiply(move02).multiply(move03);
      this.transform.setMatrix(movement);
}

Rectangle.prototype.handleEvent= function(evt){
  self = evt.target;             // this returns the `rect` element
switch (evt.type){
    case "click":   
       if (typeof self.moving == 'undefined' || self.moving == false) self.moving = true;
       else self.moving = false;
 
     if(self.moving == true){
     self.move = setInterval(()=>this.animate(),100);
     }
      else{
       clearInterval(self.move);
      }        
    break;
    default:
    break;
 } 
} 

for (var i = 0; i < 10; i++) {
    var x = Math.random() * 100,
        y = Math.random() * 300;
    var r= new Rectangle(x,y,10,10,'#'+Math.round(0xffffff * Math.random()).toString(16));
    root.appendChild(r.draw);
}
Hasan A Yousef
  • 22,789
  • 24
  • 132
  • 203