cssfirefoxsvginline-svg

Why does transform-origin not work for Firefox using inline SVG and is there an alternative?


I found inconsistencies in Firefox compared to Chrome, Edge, and Opera. Each browser handles transform-origin fine when using a CSS class. However, when I place transform-origin on an SVG element as an attribute, FF ignores the effect. Demo code below. My main question is how to get around this, but I'm also curious to know if this is expected behavior.

CSS transform-origin works in FF.

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1000 1000'>	
	<style>	
		.centered{			
			transform-origin: center;
		}
	</style>
	<path fill='#500' d='M500 500 400 400 400 600 600 600 600 400z' transform='scale(2)' class='centered'/>
</svg>

Inline SVG doesn't seem to recognize transform-origin (works consistently in Chrome/edge)

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1000 1000'>	
	<path fill='#500' d='M500 500 400 400 400 600 600 600 600 400z' transform='scale(2)' transform-origin='center'/>
</svg>


Edit: Another user pointed out this question is similar to How to set transform origin in SVG, but their premise is super broad (how to) and either wrong or outdated (posted 8.5 years ago).

"I tried using the transform-origin attribute, but it does not affect anything."

Perhaps browser support has improved, but my question specifically shows transform-origin works perfect as a CSS rule in all modern browsers or as a presentational attribute in all modern browsers except FireFox. That differentiation is likely what led to one solution being unique to this thread and a second solution that is a new take on the other threads answers.

Not to mention their question is centered around JavaScript implementation and was published at a time when FireFox didn't recognize keywords like "center" or unitless numbers which detracts from the core SVG markup problem which is that FireFox interprets the presentational attribute transform-origin='' different than other browsers.


Solution

  • I've solved the problem but I am (as yet) unable to give you a comprehensive reasoning why it works.

    The first important thing to know is that you can chain SVG Transforms.

    So, wherever you write transform="scale(2)", you can add a translate(x, y) to the chain, like this:

    transform="scale(2) translate(x, y)"
    

    So far, so good... but if scale is 2, then what values should we give to x and y of translate?

    To find out, I decided to superimpose larger and subimpose smaller scaled versions of your SVG shape (one for each colour of the rainbow) and see what patterns I could find.

    Over the top of your grey shape, I positioned an identically sized green shape.

    I gave the green shape a transform of:

    transform="scale(1) translate(0, 0)"
    

    so that it would be exactly congruent with your original grey shape.

    Then I set about subimposing larger scaled versions (yellow, orange, red) and superimposing smaller scaled versions (blue, indigo, violet).

    I predicted that x and y in each case would relate to the scale factor applied to that shape and also to the overall size of the original viewBox.

    With 3 smaller versions and 3 larger versions, the pattern emerged:

    From this, we can conclude, that if you are positioning a shape centered at 50%, 50% of your viewBox and you want to display the shape in that same position with scale(2), you must also apply a translate for x of:

    50% of ((width of canvas / scale-factor) - (width of canvas))
    

    where 50% corresponds to the x position you want to centre the shape over.

    And a translate for y of:

    50% of ((height of canvas / scale-factor) - (height of canvas))
    

    where 50% corresponds to the y position you want to centre the shape over.

    This works consistently, but I haven't spent enough time staring at it yet, to properly understand why.

    Working Example:

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
    
    <!-- Grey Original -->
    <path fill="#555" d="M500 500 400 400 400 600 600 600 600 400z" />
    
    <!-- Red Transform [50% of ((1000 / 8) - 1000) is -437.5] -->
    <path fill="rgb(255, 0, 0)" d="M500 500 400 400 400 600 600 600 600 400z" transform="scale(8) translate(-437.5, -437.5)" />
    
    <!-- Orange Transform [50% of ((1000 / 4) - 1000) is -375] -->
    <path fill="rgb(255, 125, 0)" d="M500 500 400 400 400 600 600 600 600 400z" transform="scale(4) translate(-375, -375)" />
    
    <!-- Yellow Transform [50% of ((1000 / 2) - 1000) is -250] -->
    <path fill="rgb(255, 255, 0)" d="M500 500 400 400 400 600 600 600 600 400z" transform="scale(2) translate(-250, -250)" />
    
    <!-- Green Transform [50% of ((1000 / 1) - 1000) is 0] -->
    <path fill="rgb(0, 125, 0)" d="M500 500 400 400 400 600 600 600 600 400z" transform="scale(1) translate(0, 0)" />
    
    <!-- Blue Transform [50% of ((1000 / 0.5) - 1000) is 500] -->
    <path fill="rgb(0, 0, 125)" d="M500 500 400 400 400 600 600 600 600 400z" transform="scale(0.5) translate(500, 500)" />
    
    <!-- Indigo Transform [50% of ((1000 / 0.25) - 1000) is 1500] -->
    <path fill="rgb(63, 0, 255)" d="M500 500 400 400 400 600 600 600 600 400z" transform="scale(0.25) translate(1500, 1500)" />
    
    <!-- Violet Transform [50% of ((1000 / 0.125) - 1000) is 3500] -->
    <path fill="rgb(199, 125, 243)" d="M500 500 400 400 400 600 600 600 600 400z" transform="scale(0.125) translate(3500, 3500)" />
    
    </svg>