Alterando variáveis ​​CSS com escopo usando JavaScript

Jan 18 2023
Útil para temas
Pesquisei bastante esse tópico no Google e não encontrei uma solução razoável, então achei que valeria a pena um pequeno artigo. Conteúdo Caso você esteja trabalhando com vários temas, parece um caso de uso comum não colocar suas variáveis ​​CSS diretamente em :root .

Pesquisei bastante esse tópico no Google e não encontrei uma solução razoável, então achei que valeria a pena um pequeno artigo.

Contente

  1. O problema
  2. Como alterar as variáveis ​​CSS dentro de :root
  3. Como alterar variáveis ​​CSS com escopo
  4. Alterando variáveis ​​CSS com escopo de dentro de um Web Worker
  5. Criando um atalho
  6. O projeto neo.mjs
  7. Fora do assunto, mas incrível: o que o ChatGPT pensa sobre o neo?

Caso você esteja trabalhando com vários temas, parece um caso de uso comum não colocar suas variáveis ​​CSS diretamente em :root.

Em vez disso, você pode usar uma estrutura como
:root .theme-light {/*my variables*/}

Caso você queira alterar os valores dessas variáveis ​​CSS em tempo de execução usando JavaScript, isso pode ser um pouco complicado.

2. Como alterar variáveis ​​CSS dentro de :root

:root {
    --my-color: red;
}
document.documentElement.setProperty('--my-color', 'blue');

3. Como alterar variáveis ​​CSS com escopo

:root .theme-light {
    --my-color: red;
}

:root .theme-dark {
    --my-color: darkred;
}

Podemos, por exemplo, aplicar theme-light na viewport, usar um container filho com theme-dark e dentro dele botões com theme-light novamente.

Eu esperava uma maneira elegante de alterar as variáveis ​​CSS para este caso de uso também. Eu não encontrei isso. Então:

/**
 * @param {Object} data
 * @param {String} data.key
 * @param {String} [data.priority] optionally pass 'important'
 * @param {String} data.theme
 * @param {String} data.value
 */
function setCssVariable(data) {
    let key = data.key,
        rule, sheet;

    if (!key.startsWith('--')) {
        key = '--' + key;
    }

    for (sheet of document.styleSheets) {
        if (sheet.href.includes(data.theme)) {
            for (rule of sheet.cssRules) {
                if (rule.constructor.name === 'CSSStyleRule') {
                    if (rule.style.getPropertyValue(key) !== '') {
                        rule.style.setProperty(key, data.value, data.priority);
                        return true;
                    }
                }
            }
        }
    }

    return false;
}

setCssVariable({
    key  : 'my-color',
    theme: 'theme-light',
    value: 'blue'
});

Estamos iterando todas as folhas de estilo do documento. Supondo que a URL do seu arquivo CSS contenha o nome do seu tema, você pode limitar a quantidade de verificações por ele.

Caso isso não aconteça, você pode verificar o selectorTextinterior do loop interno (mais caro):

Para o caso de uso neo, cada arquivo de tema tem apenas um CSSStyleRule, então o loop interno não é tão caro quanto pode parecer à primeira vista.

Valores de propriedade não existentes retornam uma string vazia (interessante :)), para que possamos verificar para identificar qual CSSRule contém a variável que queremos alterar.

Acessar a propriedade rule.style novamente nos dá:

e terminamos.

Você pode usar essa técnica dentro do seu aplicativo Angular, React ou qualquer outra tecnologia convencional que você esteja usando. As próximas seções estão além disso.

4. Alterar variáveis ​​CSS com escopo de dentro de um Web Worker

src/main/addon/Stylesheet.mjs (tópico principal)

class Stylesheet extends Base {
    static getConfig() {return {
        className: 'Neo.main.addon.Stylesheet',

        remote: {
            app: [
                'setCssVariable'
            ]
        }
    }}

    setCssVariable(data) {
        // ...
    }
}

Se você olhar de perto o console, notará a caixa azul → o escopo é definido como um Web Worker e podemos apenas chamar o método como uma promessa dentro do mesmo namespace vazio.

5. Criando um atalho

src/worker/App.mjs (fora do thread principal)

class App extends Base {
    construct(config) {
        super.construct(config);

        // convenience shortcut
        Neo.setCssVariable = this.setCssVariable.bind(this);
    }

    setCssVariable(data) {
        let addon = Neo.main?.addon?.Stylesheet,
            theme = Neo.config.themes?.[0];

        if (!addon) {
            return Promise.reject('Neo.main.addon.Stylesheet not imported');
        } else {
            if (theme.startsWith('neo-')) {
                theme = theme.substring(4);
            }

            return addon.setCssVariable({theme, ...data});
        }
    }
}

de dentro de um Web Worker.

6. O projeto neo.mjs

“Um trabalhador de aplicativo sendo o ator principal” ou “fora do thread principal” não são mais ficção, mas funcionam de forma muito estável e extremamente rápida:

neo já atingiu a v4.8.2 com mais de 15.000 commits dentro do ecossistema. Você pode ter perdido milhares desde a minha última postagem no blog.

Caso você ainda não tenha analisado os benefícios de desempenho, escalabilidade e extensibilidade, convém acompanhar o tópico de front-ends multithreaded da próxima geração.

Como um spoiler: Max Rahder está no meio do ensino do primeiro treinamento neo profissional para 20 alunos. Uma vez terminado, devemos ter conteúdo suficiente para finalmente lançar a seção de aprendizado que falta.

Caso você esteja interessado em obter um acesso antecipado, basta nos enviar um ping no Slack:

7. Fora do assunto, mas incrível: o que o ChatGPT pensa sobre o neo?

Atenciosamente e feliz codificação,
Tobias