Rotation of the SVG coordinate system distorts angles

My comprehension of SVG, particularly the viewport, viewBox, and the user coordinate system, seemed fairly solid.

In the initial example provided below, we utilized a viewBox with a matching aspect ratio as the viewport. As expected, there was no distortion in angles due to the rotation of the user coordinate system.

In the second example, we set the viewbox to a different aspect ratio compared to the viewport. This resulted in a mismatch of shapes' aspect ratios when mapping from viewBox to viewport. Despite this scaling, the bottom-right angle remained undistorted, which is logical considering the origin of the coordinate system at (0,0).

However, in example two, when we rotated the user coordinate system, the bottom right angle became distorted. This contrasted with example one where such distortion did not occur.

Edit 1: To clarify, the concern pertains to the bottom right angle in the final example. Prior to rotation but after stretching with viewBox, the angle measured 90 degrees. After rotation, it deviated from this measure.

Why does a non-uniformly scaled triangle lose its angles upon rotating?

Example One (uniform scale)

body {
  height: 500px;
}

svg {
  width: 400px;
  height: 400px;
  border: 1px solid red;
}
<svg id="s1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 200 200" preserveAspectRatio="none">
    <style>
      polygon {
        transform: translate(100px, 0px);
        animation: 2s ease-in 1s 1 normal forwards rotate-down;
        fill: green;
      }
      
      @keyframes rotate-down {
        0% {
          transform: translate(100px, 0px) rotate(0deg);
        }
        100% {
          transform: translate(100px, 0px) rotate(45deg);
        }
      }
    </style>
    <polygon points="100,100 100,0 0,100" />
  </svg>

Example Two (non-uniform scale)

body {
  height: 500px;
}

svg {
  width: 600px;
  height: 400px;
  border: 1px solid red;
}
<svg id="s1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 200 400" preserveAspectRatio="none">
    <style>
      polygon {
        transform: translate(100px, 0px);
        animation: 2s ease-in 1s 1 normal forwards rotate-down;
        fill: green;
      }
      
      @keyframes rotate-down {
        0% {
          transform: translate(100px, 0px) rotate(0deg);
        }
        100% {
          transform: translate(100px, 0px) rotate(45deg);
        }
      }
    </style>
    <polygon points="100,100 100,0 0,100" />
  </svg>


EDIT 2 (images for clarification):

Displayed below is the triangle after adding the viewBox (thus scaling and translating), yet before any rotation. The bottom right angle measures 90 degrees.

https://i.stack.imgur.com/WlR17.png

Here is the triangle after incorporating the viewBox (scaling and translating), and after rotation. Note that the bottom right angle is no longer 90 degrees.

https://i.stack.imgur.com/diTXt.png


EDIT 3:

I have finally uncovered the root cause behind this issue.

Refer below for an explanation detailing the specifics and providing links to relevant resources.

Answer №1

With this demonstration, you can clearly see the effects in action.

Simply hover over the SVG element to understand how the angle changes due to the stretching.

body {
  height: 500px;
}

svg {
  width: 200px;
  height: 400px;
  border: 1px solid red;
  transition: 1s width;
}

svg:hover {
  width: 600px;
}
<svg id="s1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 200 400" preserveAspectRatio="none">
    <style>
      polygon {
        transform: translate(100px, 0px) rotate(45deg);
        fill: green;
      }
    </style>
    <polygon points="100,100 100,0 0,100" />
  </svg>

Answer №2

After much investigation, I finally reached a conclusion.

The question I asked following my discovery sheds light on why coordinate transformations have the behavior they do:

  • SVG rotate after scale: Order of transforms

In response to that question, @TemaniAfif explains how the final transformation matrix is calculated and applied to map graphic element coordinates from the viewport coordinate system to the user coordinate system.

To sum it up, when applying transformations, we essentially duplicate the current user coordinate system and then translate it in relation to the duplicated system. In SVG, the initial user coordinate system (prior to viewBox or any transforms) matches the initial viewport coordinate system.

The chained/nested transforms are executed on the coordinate system from left to right/outside-in order to establish a final coordinate system for mapping graphic elements. It's important to note that nesting transforms have the same impact as chaining transforms on a single element.

How does this process function? Each transformation possesses a pre-defined affine transformation matrix unrelated to CSS/SVG. Various Wikipedia articles illustrate these matrices, such as:

To map an element's coordinates to the ultimate user coordinate system, we multiply the matrices sequentially from left to right (in the order written in the source code) to obtain the final transformation matrix.

It's important to remember that multiplication order of transform matrices matters due to the non-commutative nature of matrix multiplication. Therefore, the sequence in which transformations are listed in the source code is significant.

Ultimately, we apply this final transformation matrix to the x and y coordinates of the element to observe how each coordinate transitions from the viewport coordinate system to the user coordinate system.

For those who find it helpful, envisioning the chained/nested transforms being applied directly to the element itself (instead of user coordinate systems) from right-to-left/inside-out could simplify understanding (i.e., reverse the order of application compared to the coordinate systems).

Whether you mentally visualize transforming the coordinate systems left-to-right before mapping the graphic element, or you modify the element directly by applying transforms right-to-left, the outcome remains consistent.

Key Specifications

Please Note

Whether applying transforms to SVG or HTML elements, the transformation principles remain consistent regardless of the type of element.

Answer №3

There is a common misconception that the viewBox attribute in SVG works as a transformation method like others, but this is not accurate. In reality, the transformation is applied to the entire SVG element itself. The browser needs to compute the SVG object and all in-SVG transformations are already applied.

This concept can be likened to scaling raster images:

polygon {
  fill: transparent;
  stroke-width: 4px;
  stroke: black;
}
Base raster image:<br>
<img src="https://i.stack.imgur.com/ZA16O.png">

Stretched raster image:<br>
<img style="height: 150px; width: 300px" src="https://i.stack.imgur.com/ZA16O.png">

Base SVG:<br>
<svg viewBox="0,0,160,160" style="height: 120px" preserveAspectRatio="none">
<polygon points="10,10 150,10 80,150"/>
</svg>

Stretched SVG:<br>
<svg viewBox="0,0,160,160" preserveAspectRatio="none" style="height: 120px; width: 300px;">
<polygon points="10,10 150,10 80,150"/>
</svg>

SVG elements are drawn only after transformation has been applied, ensuring that they do not lose quality.


The SVG specification actually states (here) that all transforms applied to SVG elements function in this manner.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I am having trouble scrolling through the main content when the side-drawer is open. How can I fix this issue?

When the sidebar is opened, I am facing issues with the main content scroll and certain fields such as select options and search bar not functioning properly. I have included the main content in the routes from which it is being loaded. However, the scroll ...

Is the JavaScript Base64 image data damaged or compromised?

My current task involves selecting an image through an HTML Input and then retrieving the image using Javascript to obtain its Base64/Image Data string. However, the code I have implemented is only returning a partially corrupt or invalid representation o ...

Aligning a block of text containing two varying font sizes in the center

I'm attempting to center a paragraph element with a span inside that has a different font size, causing the alignment to be slightly off. Below is the HTML code: <p class="priceWrap"><span class="moneySign">$</span>60000.50</p> ...

jQuery animation for expanding accordion headers

I want to create a cool animation for my accordion headers that mimics a ribbon being dragged onto the wrapper when hovered over, and then dragged out when the hover ends. Everything was working fine in this initial jsFiddle, but as soon as I tried to ani ...

What is the best way to directly serve a static html file from a server using React and Express?

I recently downloaded a visually appealing HTML page with links to necessary CSS and JS files from a website builder. My intention is to use this page as the landing page for my React application. However, I'm unsure of the best approach to achieve th ...

Controlling Material-UI using a custom .css stylesheet

Are there alternative methods for customizing Material-UI components using CSS stylesheets? I'm aware of the {makeStyles} method and JSS overrides, but I find it messy in the code and confusing when trying to modularize it in other files. Is there a ...

Overlaying Div Concealed by Background Video Filter

Whenever we add a filter to our background video, the div overlay ends up moving behind the video and becoming hidden. To see an example of this issue, check out this fiddle showcasing the background video with the text overlay: https://jsfiddle.net/dyrepk ...

What is the best way to split an array into smaller chunks?

My JavaScript program fetches this array via ajax every second, but the response time for each request is around 3 to 4 seconds. To address this delay, I attempted to split the array into chunks, however, encountered difficulties in completing the task: { ...

Replacing a div with another div can be achieved in a single swap

My goal is to swap one div with another div upon clicking using the provided code. It functions properly for a single instance. However, the issue arises when the class appears multiple times, causing all instances to change due to sharing the same class n ...

What causes a header to appear transparent when it has a background color set in HTML?

I'm attempting to create a webpage that resembles Gmail. I have a header with a background color, but when I scroll, the content in the body becomes visible. Here is the view without scrolling: https://i.stack.imgur.com/UQ2sO.png However, while scr ...

Parcel JS: The function tree.render is missing or not defined

Every time I attempt to execute the production build command npm run build or npx parcel build index.html, this error occurs. My project consists of simple HTML and CSS, without any React or third-party libraries. What could be causing this issue? I have t ...

Are the links drifting to the left?

Some of the links on my website seem to be misbehaving and not following the CSS rules, floating to the left unexpectedly. Strangely, it's only the links that are affected; the rest of the text appears fine. For example, you can observe this issue at ...

Adding a CSS style to specific sections of a string that is quoted in a Razor ASP.NET file

Is it possible to format a specific part of a string? I'm trying to only stylize the word within quotation marks. This is my cshtml code: { <div class="h-44 overflow-auto text-left mx-4 mb-4"> <p ...

Create a full-screen layout with a Bootstrap 5 column grid and a sleek navbar

Can you help me modify this design using Bootstrap rules while keeping the original CSS files intact? I need to make these 5 cards (columns) full width with a navbar. Issue 1: I always see a vertical scroll bar on desktop. Issue 2: The Navbar doesn' ...

Accessing Facebook through Login with only a button visible

I need help with checking the user's login status on Facebook. I have implemented the code provided by Facebook, but all I see is the login button. How can I determine if the user is already logged in or not? function testAPI() { console.log(&apo ...

Is it possible to drag and drop without using jQuery UI?

Currently, I'm developing a jquery plugin that incorporates drag and drop functionalities using HTML5 drag attributes. The functionality is working well, except for one issue - linking the function names to their designated targets. Although the func ...

Showing error messages on HTML forms using PHP

Is there a way to show error messages in an HTML form using PHP variables that are populated with values from 'register-form_v2.php'? I am facing an issue where the variables I want to echo out in the form do not contain any values. Upon submitt ...

Troubleshooting problems with encoding in Python Selenium's get_attribute method

Currently, I am utilizing Selenium with Python to crawl the drop-down menu of this particular page. By employing the find_elements_by_css_selector function, I have successfully obtained all the data from the second drop-down menu. However, when attempting ...

The issue of MUI components overlapping arises during window resizing

I am currently working on a chat component that includes both the chat display and message input fields. function Chat() { const chatBoxStyles = { bgcolor: "red", height: "70vh", mt: "1rem" }; const messageIn ...

HTML and CSS: Centering elements inside div containers

Is it possible to align elements within a </div> using the align attribute? For example, as shown in the image below: e1:align="top" e2:align="right" e3:align="bottom" e4:align="left" EDIT: This query is purely for self-learning purposes in HTML ...