Skip to content

Add onclone size optimization for inline styles #92

Open
@zm-cttae

Description

@zm-cttae

Use case: description, code

In an ideal world we want to optimize SVG output by removing styles that can be inherited.

This mainly has an impact at >>1K nodes where the output is in the 1MB scale of magnitude.

There's not much of a story here at the moment, but the library did previously lean on unset until the #90 regression:

function copyUserComputedStyle(sourceElement, sourceComputedStyles, targetElement, root) {
const targetStyle = targetElement.style;
const inlineStyles = sourceElement.style;
for (let style of sourceComputedStyles) {
const value = sourceComputedStyles.getPropertyValue(style);
const inlineValue = inlineStyles.getPropertyValue(style);
inlineStyles.setProperty(style, root ? 'initial' : 'unset');
const initialValue = sourceComputedStyles.getPropertyValue(style);
if (initialValue !== value) {
targetStyle.setProperty(style, value);
} else {
targetStyle.removeProperty(style);
}
inlineStyles.setProperty(style, inlineValue);
}
}

There's two strategies I tried to research:

  • The curveball method: reimplement CSSOM in the "cloning" stage using document.styleSheets
    • Multiple PITAs and footguns involved (specificity, source order and the size of this library)
    • Inheritance is done with a while loop on .parentNode, and a hardcoded list of inheritable CSS props
  • The easier, "just as good" way: a post-process op in toSvg
    • Append the output to our iframe sandbox
    • use a TreeWalker to iterate over the nodes
    • see if removing an inline value changes the computed style

Some starter code, but this code snippet:

  1. doesn't restore CSS props in place, making it indeterministic and liable to break styling
  2. doesn't go in ascending order up the DOM tree, thus removing inherited properties that are necessary
const treeWalker = document.createTreeWalker(document.querySelector('foreignObject > *'), NodeFilter.SHOW_ELEMENT)
const elementList = [];
let currentNode = treeWalker.currentNode;

while (currentNode) {
    elementList.push(currentNode);
    currentNode = treeWalker.nextNode();
}

elementList.forEach(function(element) {
    const inlineStyles = element.style;
    const computedStyles = getComputedStyle(element);
    util.asArray(inlineStyles).forEach(function(name) {
        if (inlineStyles.cssText.includes(name + ': ' + value + ';')) {
            const value = inlineStyles.getPropertyValue(name);
            inlineStyles.removeProperty(name);
            if (value !== computedStyles.getPropertyValue(name)) {
                inlineStyles[name] = value;
            }
        }
    });
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions