简体   繁体   中英

How to generate CSS variable values to a new stylesheet

I'm working on a project in which a user can select colors from a color input and create their own theme dynamically using CSS variables. I'd like the user to be able to download the entire CSS file with the values they selected.

My issue : The CSS file downloaded doesn't display the actual color values, but shows the variable name.

NOT WANTED

pre[class*="language-"] {
  background: var(--block-background);
}

instead of

WANTED OUTPUT

pre[class*="language-"] {
  background: #0D2831;
}

I know I can get CSS property values by doing the following.

const styles = getComputedStyle(document.documentElement)
const value = String(styles.getPropertyValue('--block-background')).trim()

I figured that I would create a function that loops through all my CSS variables and grabs the corresponding property values and then adds them to a new stylesheet for the user to download, but I got lost along the way. I currently have two CSS files, a main.css and a prism.css . The main.css file holds the page styling and all CSS variables within the root. The prism.css file contains the theme in which I want the user to be able to download.

I'm trying to find a way to create a new stylesheet that contains everything within the prism.css file but has the actual color hex code instead of the CSS variable name as a value to the given CSS property.

Index.js

import { colors } from './colorHelper'

const inputs = [].slice.call(document.querySelectorAll('input[type="color"]'));

const handleThemeUpdate = (colors) => {
  const root = document.querySelector(':root');
  const keys = Object.keys(colors);
  keys.forEach(key => {
    root.style.setProperty(key, colors[key]);
  });
}

inputs.forEach((input) => {
  input.addEventListener('change', (e) => {
    e.preventDefault()
    const cssPropName = `--${e.target.id}`;
    document.styleSheets[2].cssRules[3].style.setProperty(cssPropName, e.target.value)
    handleThemeUpdate({
      [cssPropName]: e.target.value
    });
    console.log(`${cssPropName} is now ${e.target.value}`)
  });
});


const cssRules = document.styleSheets[2].cssRules;
for (var i = 0; i < cssRules.length; i++) {
  // Finds css variable names
  const regexp = /(?:var\(--)[a-zA-z\-]*(?:\))/

  let cssVariables = cssRules[i].cssText.matchAll(regexp)
  cssVariables = Array.from(cssVariables).join()

  console.log(cssVariables)
}

colorHelper.js

const colorSelect = {
  'Line Highlights': {
    'highlight-background': '#F7EBC6',
    'highlight-accent': '#F7D87C'
  },
  'Inline Code': {
    'inline-code-color': '#DB4C69',
    'inline-code-background': '#F9F2F4'
  },
  'Code Blocks': {
    'block-background': '#0D2831',
    'base-color': '#5C6E74',
    'selected-color': '#b3d4fc'
  },
  'Tokens': {
    'comment-color': '#93A1A1',
    'punctuation-color': '#999999',
    'property-color': '#990055',
    'selector-color': '#669900',
    'operator-color': '#a67f59',
    'operator-background': '#FFFFFF',
    'variable-color': '#ee9900',
    'function-color': '#DD4A68',
    'keyword-color': '#0077aa'
  }
}

const colorNames = []
const colors = {}

Object.keys(colorSelect).map(key => {
  const group = colorSelect[key]
  Object.keys(group).map(color => {
    colorNames.push(color)
    colors[color] = group[color]
  })
})

export { colorSelect, colorNames, colors }

prism.css

pre[class*="language-"],
code[class*="language-"] {
  color: var(--base-color);
  font-size: 13px;
  text-shadow: none;
  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
  direction: ltr;
  text-align: left;
  white-space: pre;
  word-spacing: normal;
  word-break: normal;
  line-height: 1.5;
  -moz-tab-size: 4;
  -o-tab-size: 4;
  tab-size: 4;
  -webkit-hyphens: none;
  -moz-hyphens: none;
  -ms-hyphens: none;
  hyphens: none;
}
pre[class*="language-"]::selection,
code[class*="language-"]::selection,
pre[class*="language-"]::mozselection,
code[class*="language-"]::mozselection {
  text-shadow: none;
  background: var(--selected-color);
}

@media print {
  pre[class*="language-"],
  code[class*="language-"] {
    text-shadow: none;
  }
}

pre[class*="language-"] {
  padding: 1em;
  margin: .5em 0;
  overflow: auto;
  background: var(--block-background);
}
:not(pre) > code[class*="language-"] {
  padding: .1em .3em;
  border-radius: .3em;
  color: var(--inline-code-color);
  background: var(--inline-code-background);
}

/* Tokens */

.namespace {
  opacity: .7;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
  color: var(--comment-color);
}
.token.punctuation {
  color: var(--punctuation-color);
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
  color: var(--property-color);
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
  color: var(--selector-color);
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
  color: var(--operator-color);
  background: var(--operator-background);
}
.token.atrule,
.token.attr-value,
.token.keyword {
  color: var(--keyword-color);
}
.token.function {
  color: var(--function-color);
}
.token.regex,
.token.important,
.token.variable {
  color: var(--variable-color);
}
.token.important,
.token.bold {
  font-weight: bold;
}
.token.italic {
  font-style: italic;
}
.token.entity {
  cursor: help;
}

/* Line highlighting */

pre[data-line] {
  position: relative;
}
pre[class*="language-"] > code[class*="language-"] {
  position: relative;
  z-index: 1;
}
.line-highlight {
  position: absolute;
  left: 0;
  right: 0;
  padding: inherit 0;
  margin-top: 1em;
  background: var(--highlight-background);
  box-shadow: inset 5px 0 0 var(--highlight-accent);
  z-index: 0;
  pointer-events: none;
  line-height: inherit;
  white-space: pre;
}

I have three stylesheets.

style.css holds the CSS variables in the root

normalize.css

prism.css contains the styles for syntax highlighting. This is the stylesheet I would like the user to download, but I would like to provide them with the actual hex values for each variable and not the variable name for the CSS property.

Stylesheet order in my HTML

 <link rel="stylesheet" type="text/css" href="./style.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css"
    integrity="sha256-WAgYcAck1C1/zEl5sBl5cfyhxtLgKGdpI3oKyJffVRI=" crossorigin="anonymous" />
  <link href="./themes/prism.css" rel="stylesheet" />

EDIT

I attempted to loop through the stylesheet and grab the CSS variable names, but some of them returned as an empty string.

This is what I did

const cssRules = document.styleSheets[2].cssRules;
for (var i = 0; i < cssRules.length; i++) {
  const regexp = /(?:var\(--)[a-zA-z\-]*(?:\))/

  let cssVariables = cssRules[i].cssText.matchAll(regexp)
  cssVariables = Array.from(cssVariables)

  console.log(cssVariables)
} 

This was the result in the console

var(--base-color) 
var(--selected-color) 
<empty string>
var(--block-background)
var(--inline-code-color)
<empty string>
var(--comment-color)
var(--punctuation-color)
var(--property-color) 
var(--selector-color)
var(--operator-color)
var(--keyword-color) 
var(--function-color)
var(--variable-color) 
<empty string> 
var(--highlight-background)

I then attempted to chain .replace() after the trim() but that didn't seem to work either.

You can download the file as text then find and replace the variables.

For example:

 var s = `pre[class*="language-"] { background: var(--block-background); }` const variables = {"block-background":"#0D2831"}; Object.keys(variables).forEach(key => { s = s.replace("var(--"+key+")", variables[key]); }); console.log(s);

  1. You are getting empty strings from css rules that do not have var(--something) in them. Like

    @media print { pre[class*="language-"], code[class*="language-"] { text-shadow: none; } }

    which gives you the first empty string.

  2. You are missing var(--operator-background) because matchAll() actually doesn't do what you expect. It does

    returns an iterator of all results matching a string against a regular expression

    but the regular expression you have yields only one result. So you need to add g flag to it

    /(?:var\\(--)[a-zA-z\\-]*(?:\\))/g

  3. mozselection ... Hmm... Not sure, but shouldn't it be -moz-selection ?

  4. The full loop for replacements can look like this:

     const updated_rules = []; for (var i = 0; i < cssRules.length; i++) { const regexp = /(?:var\\(--)[a-zA-z\\-]*(?:\\))/g; let updated_rule = cssRules[i].cssText; let cssVariables = updated_rule.matchAll(regexp); cssVariables = Array.from(cssVariables).flat(); for (const v of cssVariables) { updated_rule = updated_rule.replace(v, colors[v.slice(6, -1)]); } updated_rules.push(updated_rule); } console.log(updated_rules);

    It's an ugly code, and should be refactored, but...

  5. Why would you access css through document.styleSheets anyway? It's harder than just replacing strings in a css-file and for one thing, I'm not sure if you whould be able to access ::-moz-selection rule on Chrome, and in turn ::-webkit-selection on Firefox

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM