skip to Main Content

I have a table "css_rules" with 2 columns: "id_type" and "icone_type".
I’d like to generate CSS rules like this:

.custom-marker .[id_type] {
  background-image: url(http://example.com/[icone_type].svg);
}

What I tried is that:

async setup(props, { emit }) {
const apiUrl = import.meta.env.VITE_APP_API_URL;
const cssRules = reactive({});

const cssText = computed(() => {
  let result = '';

  for (const key in cssRules) {
    result += `.custom-marker .${key} {`;
    const rules = cssRules[key];
    for (const property in rules) {
      result += `${property}: ${rules[property]};`;
    }
    result += '}';
  }

  return result;
});

onMounted(async () => {
  await fetch(`${apiUrl}/referentiels/lister/typesressource`, {
    method: 'GET',
    credentials: 'include',
  })
    .then((r) => r.json())
    .then((r) => {
      r.forEach((type) => {
        cssRules[type.id_type] = {
          backgroundImage: `url(http://example.com/${type.icone_type}.svg) !important`,
        };
      });
    });
});
return {
  cssText,
};

},

And in the template:

<template>
  <div id="map">XXX</div>
    {{ cssText }}
    
  <style>
    {{ cssText }}
  </style>
</template>

The fact is that the "cssText" text displays perfectly, but I have an error:

[plugin:vite:vue] Tags with side effect ( and ) are ignored in client component templates.

Apparently I can’t create my own style tag inside my component. How could I solve this?

2

Answers


  1. I am not aware of a way in Vue to dynamically add or remove CSS classes. But you can do it in regular JS, by creating a style tag and adding it to the HTML head:

    const registerStyles = (id, stylesString) => {
      let styleEl = document.getElementById(id)
      if (!styleEl) {
        const el = document.createElement('style')
        el.type = 'text/css'
        el.id = id
        styleEl = el
        document.head.appendChild(styleEl)
      }
      styleEl.innerHTML = stylesString
    }
    

    Here it is in a small snippet:

    const { createApp, watch, ref } = Vue;
    
    const colors = ['purple', 'hotpink', 'darkviolet']
    const classes = ['foo', 'bar', 'baz']
    
    const buildStyles = () => classes.map(className => {
        const colorIx = Math.random() * colors.length | 0
        const color = colors[colorIx]
        return `.${className}{ background: ${color}; }`
      }).join("n")
    
    const registerStyles = (id, stylesString) => {
      let styleEl = document.getElementById(id)
      if (!styleEl) {
        const el = document.createElement('style')
        el.type = 'text/css'
        el.id = id
        styleEl = el
        document.head.appendChild(styleEl)
      }
      styleEl.innerHTML = stylesString
    }
    
      
    const App = {
      setup(){
        const rebuildStyles = () => {
          const styles = buildStyles()
          registerStyles('mystyles', styles)
        }
        rebuildStyles()
        return {
          rebuildStyles
        }
      }
    }
    const app = createApp(App)
    app.mount('#app')
    <div id="app">
      <div class="foo">foo</div>
      <div class="bar">bar</div>
      <div class="baz">baz</div>
      <button @click="rebuildStyles">rebuild styles</button>
    </div>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

    Have a look at how Vuetify does it, they use a library for document head management (@vueuse/head), and use DOM manipulation (as above) as fallback.

    Login or Signup to reply.
  2. Since you want to dynamically assign the image URL, I recommend using style binding. With this, you can create an object that contains CSS properties in camel case and assign it to the style prop. Observe:

    <script setup>
    const buildStyle = (skill) => {
      const image = skill.toLowerCase();
      
      return {
        backgroundImage: `url("https://cdn.jsdelivr.net/gh/devicons/devicon/icons/${image}/${image}-original.svg")`,
        width: '18px',
        height: '18px',
      };
    };
    
    const skills = ['VueJS', 'React'];
    </script>
    
    <template>
      <main>
        <article v-for="skill in skills" :key="skill">
          <div :style="buildStyle(skill)" />
          {{ skill }}
        </article>
      </main>
    </template>
    

    Live Demo

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search