18

I want to create a donut chart using an SVG circle element by setting stroke-dasharray and varying stroke-dashoffset. The SVG element needs to be rotated by 270 (or -90) degrees in order for the chart "bar" to start at the top. Here is the code:

http://jsfiddle.net/q3wb6gkq/

The rotation angle is specified using the first number in transform="rotate(270, 80, 80)".

The problem is: when viewed in Safari on iOS 10 this rotation is not applied. In fact, setting 90, 180 or 270 degree rotation has no effect. The same angles but negative (for example -90) are also not applied.

Here is a screenshot of the above fiddle in Safari on iOS 10.0.1:

iOS 10 screenshot

And here is the same fiddle in Safari on iOS 9.3.5:

iOS 9 screenshot

As a workaround, I have found that using something like 270.1 degrees solves the problem, however I would like to know why 270 is not working and if there is a better way of dealing with it.

ilokhov
  • 645
  • 1
  • 4
  • 11
  • Unable to reproduce this issue, but I suspect it's happening because a circle has no specific start or end point. You might be able to fix it by moving the `transform` attribute to an enclosing `` element. Or if that doesn't work, create a circular path using [elliptical arc path segments](https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands). – r3mainer Nov 01 '16 at 16:42
  • Thanks - I already tried setting the same transformation on an enclosing element and unfortunately that doesn't help. I have not tried using as I would like to continue using and it's stroke-dasharray and stroke-dashoffset attributes. When you say that you are unable to reproduce the issue - do you mean that you opened the fiddle on an iOS 10 device and the transformation was applied correctly? – ilokhov Nov 01 '16 at 17:21
  • 2
    @squeamishossifrage a circle does have a specific start/end point in SVG. It's at [3 o'clock](https://www.w3.org/TR/SVG/shapes.html#CircleElement). – Robert Longson Nov 01 '16 at 18:43
  • @RobertLongson Thanks, I didn't know that. ilokhov: I just don't have the same environment, that's all. – r3mainer Nov 01 '16 at 20:13
  • im having my circle draw in from the bottom, thus needed rotate 90deg. since i cover the starting point with a node(dot) thats significantly larger than the width of the circle i am using 91deg for now..... but this makes me unhappy. I need to try on ios9.3. this could just be a safari ios 10 bug. not sure at this point. – Erik Nov 08 '16 at 17:57

3 Answers3

6

I've experienced this painfully on iOS 10.1 and Safari 10.0.1. The bug is definitely triggered by any rotate value which computes to a value divisible by 90 degrees.

But it gets weirder: the bug's presence is affected by the current zoom level.

See this demo/series of minimal test cases I put together (jsFiddle version here). Best to run the snippet then expand to full page:

svg {
  height: 80px;
  width: 80px;
}

circle {
  fill: none;
  stroke-dasharray: 150;
  stroke-width: 4px;
  stroke: #6fdb6f;
  transform-origin: center center;
}

.degrot {
  transform: rotate(-90deg);
}

.degrot-offset {
  transform: rotate(-90.1deg);
}

.degrot-offset-more {
  transform: rotate(-92deg);
}

.turnrot {
  transform: rotate(-0.25turn);
}

.turnrot-offset {
  transform: rotate(-0.251turn);
}


svg[viewBox] circle {
  stroke-dasharray: 300;
  stroke-width: 8px;
}

svg[viewBox].scaledown circle {
  stroke-dasharray: 300;
  stroke-width: 8px;
}

svg[viewBox].noscale circle {
  stroke-dasharray: 150;
  stroke-width: 4px;
}

svg[viewBox].scaleup circle {
  stroke-dasharray: 75;
  stroke-width: 2px;
}

.wc {
  will-change: transform;
}


/* Demo prettification */

p:last-child {
  margin-bottom: 0;
}

td {
  padding: 10px;
}

tr td:first-of-type {
  width: 80px;
  min-height: 80px;
}

tr + tr td {
  border-top: 1px solid #dcdcdc;
}
<table>
  <tr><td colspan="2">In Safari 10.0.1 and iOS 10.1, strange behavior can be observed on SVG shapes with <code>rotate</code> values not divisible by 90 degrees, when <code>transform-origin: center center;</code></td></tr>
  <tr>
    <td>

      <svg xmlns="http://www.w3.org/2000/svg">
        <circle class="degrot" r="35" cy="40" cx="40" />
      </svg>

    </td>
    <td>
      <code>transform: rotate(-90deg);</code>
      <p>The stroke improperly begins <a href="https://www.w3.org/TR/SVG11/shapes.html#CircleElement">at 3:00</a>, as if the <code>transform</code> rule hadn't been applied.</p>
    </td>
  </tr>

  <tr>
    <td>

      <svg xmlns="http://www.w3.org/2000/svg">
        <circle class="degrot-offset" r="35" cy="40" cx="40" />
      </svg>

    </td>
    <td>
      <code>transform: rotate(-90.1deg);</code>
      <p>The stroke begins at (twelve seconds before) 12:00, as expected.</p>
    </td>
  </tr>

  <tr>
    <td>

      <svg xmlns="http://www.w3.org/2000/svg">
        <circle class="turnrot" r="35" cy="40" cx="40" />
      </svg>

    </td>
    <td>
      <code>transform: rotate(-0.25turn);</code>
      <p>The same bug applies to any <code>rotate</code> value which computes to a multiple of 90 degrees.</p>
    </td>
  </tr>

  <tr>
    <td>

      <svg xmlns="http://www.w3.org/2000/svg">
        <circle class="turnrot-offset" r="35" cy="40" cx="40" />
      </svg>

    </td>
    <td>
      <code>transform: rotate(-0.251turn);</code>
      <p>43 seconds before noon.</p>
    </td>
  </tr>


  <tr><td colspan="2">But when the SVG element specifies a <code>viewBox</code> which is being scaled down, things can get weird:</td></tr>
  
  <tr>
    <td>

      <svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg">
        <circle class="degrot" r="70" cy="80" cx="80" />
      </svg>

    </td>
    <td>
      <code>transform: rotate(-90deg);</code>
      <p>So far, so the same.</p>
    </td>
  </tr>

  <tr>
    <td>

      <svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg">
        <circle class="degrot-offset" r="70" cy="80" cx="80" />
      </svg>

    </td>
    <td>
      <code>transform: rotate(-90.1deg);</code>
      <p>But now, offsetting by a little bit doesn't work, <em>unless</em> you zoom in the page in past a certain zoom threshold (either via pinching, or <code>View > Zoom</code> and/or keyboard shortcut). Try it; it's unsetting!</p>
      <p>This is probably because of some rounding of that the zooming engine performs, because...</p>
    </td>
  </tr>

  <tr>
    <td>

      <svg viewBox="0 0 160 160" class="scaledown" xmlns="http://www.w3.org/2000/svg">
        <circle class="degrot-offset-more" r="70" cy="80" cx="80" />
      </svg>

    </td>
    <td>
      <code>transform: rotate(-92deg);</code>
      <p>offsetting by a larger amount restores expected behavior.</p>
    </td>
  </tr>

  <tr><td colspan="2">If the SVG element is not being scaled <em>down</em>, behavior identical to the first section resumes. Zooming has no effect:</td></tr>
  
  <tr>
    <td>
      
      <svg viewBox="0 0 80 80" class="noscale" xmlns="http://www.w3.org/2000/svg">
        <circle class="degrot" r="35" cy="40" cx="40" />
      </svg>

      <svg viewBox="0 0 40 40" class="scaleup" xmlns="http://www.w3.org/2000/svg">
        <circle class="degrot" r="17.5" cy="20" cx="20" />
      </svg>

    </td>
    <td>
      <code>transform: rotate(-90deg);</code>
      <p>
      Top: No scaling (viewBox dimensions match parent element's)<br><br>
      Bottom: Scaling up (viewBox dimensions half of parent element's)
      </p>
    </td>
  </tr>

  <tr>
    <td>
      
      <svg viewBox="0 0 80 80" class="noscale" xmlns="http://www.w3.org/2000/svg">
        <circle class="degrot-offset" r="35" cy="40" cx="40" />
      </svg>

      <svg viewBox="0 0 40 40" class="scaleup" xmlns="http://www.w3.org/2000/svg">
        <circle class="degrot-offset" r="17.5" cy="20" cx="20" />
      </svg>

    </td>
    <td>
      <code>transform: rotate(-90.1deg);</code>
      <p>
        Top: No scaling (viewBox dimensions match parent element's)<br><br>
        Bottom: Scaling up (viewBox dimensions half of parent element's)
      </p>
    </td>
  </tr>

  <tr><td colspan="2">But there is one exception:</td></tr>

  <tr>
    <td>
      
      <svg class="degrot wc" xmlns="http://www.w3.org/2000/svg">
        <circle r="35" cy="40" cx="40" />
      </svg>

    </td>
    <td>
      <p>On the parent <code>svg</code> element:</p>
      <code>transform: rotate(-90deg);<br>will-change: transform;</code>
      <p>Iff the the the rotation is applied to a <em>parent</em> of the SVG shape (including the SVG element itself) along with the rule <code>will-change: transform</code>, all rotation values work as expected.</p>
    </td>
  </tr>

  <tr><td colspan="2">All these behaviors have been observed in Safari 10.0.1 and iOS 10.1. They appear to be fixed as of iOS 10.2 Beta 2.</td></tr>
</table>

As stated in the demo, it appears to be fixed in iOS 10.2, at least in the public beta version I just downloaded. Presumably, a Safari fix will also be arriving in due time.

iOS 10.1

iOS 10.1: bug present

iOS 10.2 (Public Beta 2)

iOS 10.2: bug not present

Jacob Ford
  • 4,553
  • 5
  • 27
  • 42
  • Thanks for putting together the test cases. I currently don't have access to iOS devices to check the behaviour - I will have a look in the next couple of days and get back to you though. The fact that it appears to be fixed in iOS 10.2 beta is encouraging though, hopefully it will stay like that when released to the public. – ilokhov Nov 11 '16 at 23:11
  • 1
    Just checked the test cases on an iOS 10 device and can confirm your findings. Your last example which includes setting "will-change: transform" (this attribute was previously unknown to me) and the rotation on the parent element seems to take care of the issue. I have updated my original fiddle here to demonstrate: https://jsfiddle.net/urfbc0gv/ Since this provides a solution to my original problem I am marking this answer as accepted. Also thanks for checking the behaviour on iOS 10.2 beta - hopefully this behaviour will be fixed soon. – ilokhov Nov 17 '16 at 16:12
  • 1
    Thanks @ilokhov! The `will-change` CSS property is typically used for manually priming the GPU—if enabled—for an `animation` or `transition` that it might not detect automatically, such as one which will be applied by JS after the page loads. It can help prevent stuttering that might occur when the element suddenly triggers hardware acceleration. So while it may work, note that it is also unnecessarily requesting GPU acceleration from the browser. Not pretty but a workaround nonetheless. – Jacob Ford Nov 21 '16 at 19:15
  • Also threw together my demo as a small GitHub repo if anyone wants to contribute or view: https://github.com/unitof/90-degree-safari – Jacob Ford Nov 21 '16 at 19:17
2

Indeed, set the rotate transformation to something like 90.1deg solves the issue...

I have test many things, reported here: https://codepen.io/KevinNTH/pen/ZBgKdG

<!-- workaround ios -->
<svg class="wka-ios">
  <g transform="rotate(-90.1 30 30)">
    <circle cx="25" cy="25" r="15"/>
  </g>
</svg>
KNTH
  • 23
  • 5
  • Additionally, I was not providing the x and y coords for rotation `transform="rotate(-90.1 55 55)"` fixed it for me (where 55 is the width of the SVG viewbox/2) – Prnth Jan 29 '20 at 16:05
0

This is happening to me as well, I settled on using a rotation just shy of being divisible by 90 degrees to get around this in the interim.

shirajg
  • 103
  • 2
  • 9