ScreenshotNeo

BlogEngineering

Why Safari Renders SVG Transformations Differently from Blink and Gecko

Safari SVG mismatches usually come from transform origins, reference boxes, or attribute/CSS interactions. Learn how to isolate and fix each case.

By the ScreenshotNeo team1 October 20267 min read

Safari does not apply one universal “different transform order” rule. Most SVG mismatches come from three questions: are you using the SVG transform attribute or CSS transform, what is the transform origin, and which reference box supplies that origin?

For most SVG elements, the default transform-origin is 0 0, and the default transform-box is view-box. A percentage or center can therefore refer to the SVG viewBox instead of the shape’s own bounds. Root <svg> elements and SVG elements directly inside <foreignObject> are exceptions whose default origin is 50% 50%. Make the intended coordinate system explicit before comparing browsers.

1. Build a minimal reproduction first

Reduce the page to one SVG, one shape, and one transform. Record the exact Safari version, operating system, and whether the transform is an attribute, a CSS property, or both.

<!doctype html>
<meta charset="utf-8">
<style>
  svg { width: 320px; border: 1px solid #ccc; }
  .shape { fill: tomato; transform: rotate(30deg); }
</style>
<svg viewBox="0 0 320 200">
  <rect class="shape" x="110" y="50" width="100" height="60" />
</svg>

Capture this same file in Safari, Chrome, and Firefox. Then change one variable at a time. Do not infer stable-release behavior from Safari Technology Preview, Chrome Canary, or Firefox Nightly builds.

2. Understand the two transform systems

SVG transform attribute

The SVG attribute uses SVG user coordinates. rotate(a) rotates around the current user-coordinate-system origin. rotate(a, x, y) supplies an explicit pivot in user coordinates. This avoids relying on CSS reference-box rules.

<svg viewBox="0 0 320 200">
  <rect x="110" y="50" width="100" height="60"
        transform="rotate(30 160 80)" />
</svg>

Here, the rectangle rotates around the point (160, 80). Calculate that point from the shape’s geometry when the pivot must remain stable across engines.

CSS transform property

CSS transforms use transform-origin and transform-box. For SVG, the default reference is generally the viewBox. To rotate a shape around its own bounding-box center, set both values explicitly:

.shape {
  transform-box: fill-box;
  transform-origin: center;
  transform: rotate(30deg);
}

MDN’s transform-box documentation demonstrates the difference: fill-box uses the element’s bounding box, while the default can use the SVG canvas/viewBox. The SVG transform-origin reference documents the SVG defaults and exceptions.

3. Fix the common origin and reference-box mismatch

This complete example intentionally makes the pivot explicit:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
  svg { width: 320px; height: 200px; background: #f5f5f5; }
  .shape {
    transform-box: fill-box;
    transform-origin: center;
    transform: rotate(30deg);
    fill: #e45757;
  }
</style>
</head>
<body>
<svg viewBox="0 0 320 200" aria-label="Rotated rectangle">
  <rect class="shape" x="110" y="50" width="100" height="60" />
</svg>
</body>
</html>

If the desired pivot is not the bounding-box center, use a length-based origin such as transform-origin: 20px 10px with transform-box: fill-box, or use an SVG attribute with explicit coordinates.

4. Check viewBox coordinates, including unusual origins

A viewBox does not have to start at 0 0. Negative or non-zero origins change where percentage and keyword origins land.

<svg viewBox="-100 -50 320 200">
  <rect class="shape" x="0" y="0" width="100" height="60" />
</svg>

When debugging, write down the four viewBox values, the shape’s x, y, width, and height, and the intended pivot in user coordinates. A visually centered shape can still have a pivot far from the viewBox center.

5. Separate attribute and CSS interactions

Keep the transform in one system while isolating a bug. If both are present, test these four cases separately:

  1. No transform.
  2. SVG attribute only.
  3. CSS transform only.
  4. Both attribute and CSS transform.

WebKit Bug 305181 records a presentation-hint precedence difference in Safari Technology Preview 234 compared with Chrome Canary 145 and Firefox Nightly 148; the reviewed report remained marked NEW and later prerelease attachments still showed the case. It is evidence about a specific test, not proof that Safari always orders transforms differently.

WebKit Bug 217286 documents another case where transform: none did not cancel a scale supplied by an SVG transform attribute. Retest this exact pattern on the Safari version you support.

<style>
  .icon { transform: none; }
</style>
<svg>
  <rect class="icon" transform="scale(2)" width="20" height="20" />
</svg>

If a CSS reset must cancel an attribute, remove or change the attribute in markup or script rather than assuming transform: none will override every implementation path.

6. Compare computed style with visible pixels

Inspect both the DOM and the rendered result. A computed value can look identical while the visible pivot differs, and historical WebKit Bug 174285 shows why computed-style and rendering reports should be treated separately; a 2022 comment reported agreement for that particular testcase after configuration changed.

const node = document.querySelector('.shape');
console.log('computed transform:', getComputedStyle(node).transform);
console.log('computed origin:', getComputedStyle(node).transformOrigin);
console.log('computed box:', getComputedStyle(node).transformBox);
console.log('client rect:', node.getBoundingClientRect());
console.log('svg bbox:', node.getBBox());

Use getBBox() for the SVG geometry and getBoundingClientRect() for the final CSS-pixel rectangle. They answer different questions.

7. A repeatable cross-browser test matrix

Axis Cases What it reveals
Transform source Attribute, CSS, both Precedence and code-path differences
Reference box view-box, fill-box Whether the pivot follows the canvas or shape
Origin 0 0, center, explicit lengths, percentages Coordinate interpretation
Geometry Non-zero/negative viewBox, nested groups User-coordinate conversions
Output Pixels and computed styles Rendering versus style serialization
Build Stable Safari, Safari Technology Preview, Blink, Gecko Version-specific regressions

Save the reduced HTML, screenshots, computed-style output, browser build, operating system, and device scale factor. A browser bug report is much easier to reproduce when all of these are recorded.

8. Common symptoms and fixes

Symptom Likely cause Fix
Shape rotates around the canvas Default transform-box: view-box Set transform-box: fill-box and transform-origin: center, or use rotate(a,x,y).
Percentage origin differs between browsers Different reference assumptions or nested SVG geometry Use fill-box or explicit user-coordinate lengths.
CSS reset does not remove scaling Scale remains on SVG attribute Remove the attribute or set an explicit replacement transform; test WebKit Bug 217286’s pattern.
Computed style matches but pixels differ Rendering/reference-box issue Compare getBBox(), getBoundingClientRect(), and screenshots separately.
Only a preview browser fails Unfixed prerelease regression Record the exact build and avoid claiming a stable-version defect.
Nested group behaves unexpectedly Transforms accumulate through parent coordinate systems Move the transform to one element, then reintroduce parent transforms one at a time.

9. Performance and reliability considerations

  • Prefer one clear transform system per element. Fewer nested transforms make layout and bug reports easier to reason about.
  • Use explicit pivots for generated SVG when geometry is known. This avoids repeated style calculations and ambiguity around percentages.
  • Keep reduced testcases free of animations, filters, and unrelated layout rules until the transform is correct.
  • For screenshot regression tests, fix viewport size, device scale factor, fonts, animation state, and browser build. A one-pixel difference can otherwise hide the actual transform issue.
  • Do not treat a screenshot as proof of computed-style behavior, or computed style as proof of identical pixels.

10. Capture reproducible screenshots

Once the testcase is deterministic, capture it in each target browser and compare the same crop. If the page contains consent dialogs, popups, or chat widgets, remove them before capture so they do not obscure the SVG.

11. Or skip the browser setup

ScreenshotNeo provides a GET-based website screenshot API and an MCP server for AI agents. Use one request to capture the reduced testcase or any public URL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for request options. Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server includes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

12. FAQ

Does Safari always apply SVG transforms in a different order?

No. The cause depends on the transform source, origin, reference box, nesting, and browser build. A cited WebKit report covers a specific prerelease presentation-hint case.

Should I use CSS transforms or SVG attributes?

Either works when the coordinate system is explicit. Use CSS with transform-box and transform-origin when styling; use rotate(angle,x,y) when an SVG user-coordinate pivot is the clearest contract.

Why does center not mean the shape’s center?

With the default view-box reference, it can mean the SVG viewBox center. Set transform-box: fill-box to make it follow the shape’s bounding box.

Can I diagnose this without DevTools?

Yes. Log computed styles and geometry, save a minimal HTML file, and compare deterministic screenshots at the same viewport and scale.

What should I include in a browser bug report?

Include the reduced testcase, expected and actual output, browser build, operating system, device scale factor, computed styles, and screenshots. Mention whether the transform is an attribute, CSS property, or both.