convertTransform` does not apply translate(x, y) to `<text>`, `<circle>`, `<rect>`, etc
Summary
The convertTransform plugin currently applies transform="translate(...)" only to certain elements like <path>, but does not apply it to other valid SVG elements such as:
<text><circle><rect><tspan><g>(nested)
This results in inconsistent transform flattening and prevents full removal of transform from <g> containers in many real-world designs, particularly when working with Sketch or Illustrator-generated SVGs.
Example (Before)
<g transform="translate(200 188)">
<rect width="100" height="80"/>
<text><tspan x="30" y="40">Hello</tspan></text>
</g>
Expected (After)
<rect x="200" y="188" width="100" height="80"/>
<text transform="translate(200 188)"><tspan x="30" y="40">Hello</tspan></text>
Problem
In current output, convertTransform partially transforms the <rect> into absolute x, y, but leaves the <text> as-is or wrapped in residual transform. This breaks downstream animation or layout systems that expect flat coordinate-based SVGs without transform wrappers.
Suggestion
-
Extend
convertTransformto support applying translations to:x/yattributes (e.g.rect,circle,text)cx/cy(e.g.circle)- Wrap
textwith its owntransformif inline transform is not possible - Possibly recursively flatten nested
<g>elements
This would significantly improve usability of SVGO for SVG animation and simplify DOM manipulation for frontend developers.
Version
SVGO v4.0.0
My Config
export default {
multipass: true,
js2svg: { indent: 2, pretty: true },
plugins: [
{
name: 'removeViewBox',
active: false
},
{
name: 'removeXMLNS',
active: false
},
{
name: 'preset-default',
},
'collapseGroups',
'convertShapeToPath',
'convertPathData',
'mergePaths',
'convertTransform',
]
}
relative:
https://github.com/stadline/svg-flatten https://kurachiweb.github.io/svg-rect-to-path/ https://lean-svg.netlify.app/
It's never meant to. convertTransform optimizes transform's representation. E.g. long sequence to a short matrix, or a matrix to even shorter variant (e.g. scale or skewX). Unfortunately, there is no plugin to apply transforms to basic shapes (only convertPathData does for paths). It's a longstanding issue, and not that hard to implement. So, PRs are welcome.
Boy do I have a PR for you!
@KTibow I make a simple version, 中国有句古话,“邪修总让人讨厌” 😂
export const applyTranslateTransform = {
name: 'wrapTransformGroup',
description: 'Wrap <g transform="translate(...)"> into outer <g> only if it has <rect> children',
type: 'visitor',
fn: () => {
return {
element: {
enter: (node, parentNode) => {
// Must be a <g> and contain transform="translate(...)"
if (
node.name !== 'g' ||
!node.attributes?.transform ||
!node.attributes.transform.startsWith('translate(')
) return;
// Check if there is at least one <rect> child element
const hasRect = (node.children || []).some(
child => child.type === 'element' && ['rect', 'circle', 'ellipse'].includes(child.name)
);
if (!hasRect) return; // If no <rect>, do nothing
// Create a new outer <g>
const outerG = {
type: 'element',
name: 'g',
attributes: {},
children: [structuredClone(node)],
};
// Move transform to the inner <g> and remove from the original node
outerG.children[0].attributes.transform = node.attributes.transform;
delete node.attributes.transform;
// Move the rest of node's attributes to outerG
for (const [key, value] of Object.entries(node.attributes)) {
outerG.attributes[key] = value;
}
// Replace node with outerG in the parent's children
const index = parentNode.children.indexOf(node);
if (index !== -1) {
parentNode.children.splice(index, 1, outerG);
}
},
},
};
},
};