使用香草JavaScript围绕其中心旋转SVG矩形

rotating SVG rect around its center using vanilla JavaScript

本文关键字:旋转 SVG 矩形 香草 JavaScript      更新时间:2023-09-26

这不是另一个问题的重复。我发现这是在谈论使用XML的中心旋转,并试图使用类似rotate(45, 60, 60)的普通JavaScript实现同样的功能,但对我来说不起作用

我使用的方法是下面片段中的方法,但发现矩形并没有完全围绕其中心旋转,而且它有点移动,矩形应该在第一次点击时开始旋转,并且应该在第二次点击时停止,这对我来说很好。

任何想法,为什么项目移动,以及我如何修复它。

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);
}

更新

我发现问题是使用self.getBoundingClientRect()计算rect的中心点,总是有4px extra in each side,这意味着宽度增加了8px,高度增加了8pt,并且x和y都偏移了4 px,我发现这是一样的,但设置self.setAttribute("display", "block");self.style.display = "block";都不适用。

所以,现在我有两种选择之一:

  • 找到每侧额外4px的解决方案(即x和y的4px偏移,宽度和高度总共额外8px)
  • 或使用计算中点

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

第二个选项(计算中点的另一种方法对我来说很好,因为它是矩形的,但如果使用其他形状,那就不一样了,我会寻找通用的方法来找到中点,无论对象是什么,也就是寻找第一个选项,这就是解决self.getBoundingClientRect()问题。

开始…

小提琴

这里的一些文档代码:

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,

可以通过以下任一方式解决捕捉SVG中心的问题:

  • 使用.getBoundingClientRect()和考虑4px的尺寸调整在每侧都是额外的,因此需要调整的结果为:

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

    或通过:

  • .(x/y).baseVal.value捕获为:

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

下面是完整的运行代码:

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);
}