使用原生JavaScript围绕SVG矩形中心旋转

8
这不是其他问题的重复。 我发现这篇文章讨论了使用XML围绕中心旋转的方法,我尝试使用原生JavaScript实现类似的方法,如rotate(45, 60, 60),但这对我来说不起作用。
我成功的方法在下面的片段中,但发现矩形没有完全围绕其中心旋转,并且有些移动。矩形应该在第一次点击后开始旋转,在第二次点击时停止旋转,这个功能我已经实现了。
你有什么想法,为什么物品会移动,如何解决这个问题。

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的中心点时,每个边上都会有额外的4像素,这意味着宽度和高度都多出8像素,x和y方向都向右下移动了4像素,我在这里找到了相关信息。但是无论是设置self.setAttribute("display", "block");还是self.style.display = "block";,都没有解决我的问题。

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

  • 找到解决边缘多出4像素的办法(即x和y方向各移动4像素,宽度和高度各多出8像素);
  • 或者使用以下方式计算中心点:

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

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


这是一个经常被问到的问题...诀窍:将对象转换为旋转/缩放中心在原点,旋转后再移回原位。所有这三个步骤可以合并为一个变换矩阵。 - philipp
1
顺便说一下...在编写ES6类时,您不需要使用Object.defineProperty(myRect.prototype ...,因为您可以直接创建类的成员方法,这就是所谓的prototype - philipp
@philipp 我参考了你提供的链接,在我的问题中,它不是重复的,并且那种方式对我没有用 :( 关于 Object.defineProperty...,我使用它是因为我更喜欢将所有声明放在主 class { } 之外。 - Hasan A Yousef
不知道,代码中有很多我认为不理想的地方,所以对我来说有点难以弄清楚...首先:你为每个步骤附加一个新的变换项到列表中,但它可以/应该始终保持单个矩阵。其次,我认为你的第一步和第三步被交换了... - philipp
@philip 如果你有一个纯JS代码可以让矩形绕其中心旋转,我可以调整我的代码来处理它。 - Hasan A Yousef
显示剩余3条评论
2个回答

1

让我们开始吧...

FIDDLE

这里有一些文档代码:

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 :) 我发现我的代码错误在于使用 self.getBoundingClientRect() 计算 rect 的中心点时,每个边都会多出4px,这意味着宽度和高度各多出8px,同时x和y都向右下移动了4px。当我按照你的方式重新计算中心点时,我的代码运行良好。我看到了这篇文章 https://dev59.com/rWoy5IYBdhLWcg3wYM2B#8600771 但是设置 self.setAttribute("display", "block"); 或者 self.style.display = "block"; 还没有解决我的问题。 - Hasan A Yousef
看不到任何变化... - philipp
@phiiipp 这真的很奇怪,这个编辑被5个人拒绝了,理由是:“这个编辑是为了回应帖子作者而进行的,作为编辑没有意义。有更好的答案吗?请写一个新的答案。”不确定你是否能看到这个链接:http://stackoverflow.com/review/suggested-edits/13059954 - Hasan A Yousef
谢谢你的留言,老实说我还是不明白有些人怎么想的。他们首先把我的问题标记为重复的,但实际上并不是重复的。然后又将我的编辑标记为不必要的,而我认为它可能会对某个人有所帮助。有些人更喜欢在答案中提供完整的示例,而不仅仅是一小段代码片段。考虑到不同的人有不同的口味,应该考虑这样的事情。无论如何,再次感谢,我已经将我的编辑作为一个单独的答案发布了出来,相信有些人会从中受益,再次感谢 :) - Hasan A Yousef
工作与否我还没有检查,但代码很漂亮。 - user4602228
显示剩余2条评论

0

感谢@Philipp,

捕获SVG中心的解决方法有以下两种:

  • 使用 .getBoundingClientRect() 并调整尺寸,考虑到每个边缘多出4像素,因此结果数字需要进行调整:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接