Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rotating SVG rect around its center using vanilla JavaScript

Tags:

javascript

svg

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.

like image 411
Hasan A Yousef Avatar asked Jul 19 '16 07:07

Hasan A Yousef


1 Answers

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);
    }
}
like image 67
philipp Avatar answered Oct 31 '22 04:10

philipp