Kasper Nielsen
Kasper Nielsen

Reputation: 133

Rotate svg line with JavaScript (not CSS)

I've got the following svg line:

<svg height="100%" width="100%">
    <line id="skyline" x1="50%" y1="50%" x2="50%" y2="90%" style="stroke:rgb(0,0,0);stroke-width:10" />
</svg>

Trying to rotate the line multiple times seems to add many rotate(X) to the transform attribute, not simply overriding the value every time:

var skyline = document.getElementById("skyline");

for (i = 0; i < 100; i++) {
    var rotation = skyline.getAttribute("transform") + i;
    skyline.setAttribute("transform", "rotate(" + rotation + ")");
}

How can I properly get the rotate attribute, and how can I properly override in multiple times?

Upvotes: 1

Views: 1247

Answers (4)

Paul LeBeau
Paul LeBeau

Reputation: 101938

Assuming your line doesn't already have a transform, you can just do:

var skyline = document.getElementById("skyline");

for (i = 0; i < 100; i++) {
    skyline.setAttribute("transform", "rotate(" + i + ")");
}

But that doesn't work as is because you aren't giving the browser a chance to render the updated SVG. Also it would run too fast even if it did update.

So you need to use an interval, timeout or requestAnimationFrame().

var i = 0;

function rotate() {
  skyline.setAttribute("transform", "rotate(" + (i++) + ")");
  if (i < 100)
    setTimeout(rotate, 100);
}

setTimeout(rotate, 100);

If your elements could already have a transform, then you will need to get the current transform and manipulate it. For example:

var i = 0;

function rotate() {
  var matrixList = skyline.transform.baseVal;
  if (matrixList.length === 0) {
    skyline.setAttribute("transform", "rotate(" + (i++) + ")");
  } else {
    var firstTransform = matrixList.getItem(0);
    firstTransform.setMatrix(firstTransform.matrix.rotate(1));
    i++;
  }
  if (i < 100)
    setTimeout(rotate, 100);
}

setTimeout(rotate, 100);

var skyline = document.getElementById("skyline");
var i = 0;

function rotate() {
  var matrixList = skyline.transform.baseVal;
  if (matrixList.length === 0) {
    skyline.setAttribute("transform", "rotate(" + (i++) + ")");
  } else {
    var firstTransform = matrixList.getItem(0);
    firstTransform.setMatrix(firstTransform.matrix.rotate(1));
    i++;
  }
  if (i < 100)
    setTimeout(rotate, 100);
}

setTimeout(rotate, 100);
<svg height="100%" width="100%">
    <line id="skyline" x1="50%" y1="50%" x2="50%" y2="90%" style="stroke:rgb(0,0,0);stroke-width:10" transform="rotate(-10)"/>
</svg>

Upvotes: 1

WalksAway
WalksAway

Reputation: 2829

Using: https://css-tricks.com/get-value-of-css-rotation-through-javascript/

function getRotate(el) {
  var st = window.getComputedStyle(el, null);
  var tr = st.getPropertyValue("-webkit-transform") ||
    st.getPropertyValue("-moz-transform") ||
    st.getPropertyValue("-ms-transform") ||
    st.getPropertyValue("-o-transform") ||
    st.getPropertyValue("transform") ||
    "fail...";

  var values = tr.split('(')[1];
  values = values.split(')')[0];
  values = values.split(',');
  var a = values[0];
  var b = values[1];
  var c = values[2];
  var d = values[3];

  var scale = Math.sqrt(a * a + b * b);


  // arc sin, convert from radians to degrees, round
  var sin = b / scale;
  // next line works for 30deg but not 130deg (returns 50);
  // var angle = Math.round(Math.asin(sin) * (180/Math.PI));
  var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));

  return angle
}

setInterval(rotate, 100);

function rotate() {
  var rotation = getRotate(document.getElementById("skyline")) + 5;

  console.log(rotation);

  skyline.setAttribute("style", "stroke:rgb(255,0,0);stroke-width:2;transform: rotate(" + rotation + "deg);transform-origin: center");
}
<svg height="100%" width="100%">
  <line id="skyline" x1="80" y1="20" x2="100" y2="40" style="stroke:rgb(255,0,0);stroke-width:2;transform: rotate(30deg);transform-origin: center" />
</svg>

Upvotes: 0

Yury Tarabanko
Yury Tarabanko

Reputation: 45106

Changing the same attribute in synchronous loop doesn't make sense. If you want some sort of an animation you need to introduce time delay. The most straight forward way is to use setTimeout

var skyline = document.getElementById("skyline");

var angle = getOriginalAngle(),
    finalAngle = angle + 100;

function rotate() {
   skyline.setAttribute("transform", "rotate(" + (angle++) + "deg)");
   (angle < finalAngle) && setTimeout(rotate, 12); // repeat every 12 ms
}

rotate(); // launch animation

UPD Or if you want your changes to be in sync with browser rendering loop you can use requestAnimationFrame

function rotate() {
   skyline.setAttribute("transform", "rotate(" + (angle++) + "deg)");
   (angle < finalAngle) && requestAnimationFrame(rotate); // +- 60 fps
}

Upvotes: 2

Jonathan Ben Avraham
Jonathan Ben Avraham

Reputation: 56

Greensock is a great extension for CSS and JS which allows you to manipulate SVG's based on id's or classnames of the element in question:

GreenSock

Here's an example:

TweenMax.staggerTo('.skyline', 2, {rotation: -90, repeat:-1, yoyo:true});
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.5/TweenMax.min.js"></script>

<svg height="100%" width="100%">
  <line class="skyline" x1="50%" y1="50%" x2="50%" y2="90%" style="stroke:rgb(0,0,0);stroke-width:10" />
</svg>

The best part is it will run in IE as well as being simple, easy to implement and easy to understand and well documented! It can be set to run automatically on page load, or when an event is fired such as a hover etc etc. you can choose whether it runs indefinitely or how many times you want it to run, and if you want it to have a yoyo effect or not (animation to run back and forth)

Upvotes: 1

Related Questions