Impossible de styliser du html rendu avec v-html

L’auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour,

J'ai un petit problème avec vue.js, que je suis en train d'apprendre. J'utilise webpack et les fichiers .vue, et je travail toujours en style scoped (c'est important pour la suite). Mon but est d'afficher dans une div qui posséde la classe .render du html, fournie par une chaine de caractère et de styler ce html (par exemple changer la couleur des paragraphes).

Le problème est que mon selecteur css .render p ne sélectionne rien, le style n'est pas appliqué. Voici un exemple simplifié de code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
    <div class="render" v-html="response"></div>
</template>

<script>
    export default {
        data () {
            return {
                response = "<p>je suis un paragraphe !</p>"
            }
        }
    }
</script>

<style scoped="true">
    .render p {
        color: red /* Ne fonctionne pas oO */
    }
</style>


<style>
    .render p {
        color: red /* Fonctionne si le style est non scoped oO */
    }
</style>

Le style scoped n'est pas appliqué alors que le style non scoped lui est appliqué, ce qui est un peu embêtant. Pour l'instant je contourne le problème avec un identifiant unique et un style global mais j'aimerai bien pour garder un style interne au composant pour styler du html rendu avec la directive v-html histoire que ça soit propre (le fix du style externe n'est peut être pas possible dans tous les cas, même si dans mon projet ça ne pose pas de souçis).

Merci d'avance pour vos réponses :)

"Toute extrapolation est valide dès lors que son résultat m’est sympathique." Loi de Lomborg

+0 -0

Tu as essayé de mettre la balise <style> dans le template ?

Car l'attribut scoped fait en sorte que le contenu ne s'applique qu'à son parent et sa descendance.

Quand ton template est utilisé, son contenu est donc déplacé, tu perds donc la filiation.

Mes tutos — Développeur JS (front principalement) — Consultant qualité, ergonomie et UX

+0 -0
Auteur du sujet

Salut !

Je ne peux pas faire ça, le but des fichiers vue.js est de séparer le template, le fonctionne via <script> et le style. Si je met mon style dans le template, le linter n'est pas content du tout ^^

Le problème ne vient pas du style scoped, si j'écris le code suivant le paragraphe sera bien affiché en rouge :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<template>
    <div class="render"><p>Je suis un paragraphe en rouge !</p></div>
</template>

<script>
    export default {
        data () {
            return {
            }
        }
    }
</script>

<style scoped="true">
    .render p {
        color: red /*fonctionne oO */
    }
</style>

Le problème semble vraiment venir de la directive v-html et de la façon dans le style scoped est injecté. J'avoue que j'ai résolu le problème avec un style non scoped et une classe unique pour éviter que ça n'interfère avec le reste du code, je ne maitrise pas assez les rouages de vue.js pour creuser afin de comprendre précisément d'où vient le problème.

"Toute extrapolation est valide dès lors que son résultat m’est sympathique." Loi de Lomborg

+0 -0
Auteur du sujet

As-tu regardé le HTML généré par Vue.js, justement, pour voir où il mettait ton CSS par rapport au HTML ?

Normalement il génère des classes uniques ou des id uniques pour les éléments scoped. Je pense que le probléme vient de là : le style scoped est probablement intégré avec l'interprétation du v-html ce qui doit empêcher les selecteurs de s'appliquer.

Pour l'instant ma solution me convient bien, mais t'as question m'a fait réfléchir au probléme ce qui est toujours utile ! La bonne façon de faire serait peut être de mettre un style dynamique via un :bind-style, il faudra que je test a l’occasion.

Merci pour ton aide !

"Toute extrapolation est valide dès lors que son résultat m’est sympathique." Loi de Lomborg

+0 -0

Cette réponse a aidé l’auteur du sujet

Malgré le fait que ce sujet soit marqué comme résolu, je poste la solution officielle si cela peut aider quelqu’un. :D

Ce qu’il se passe quand le style est scopé: Vue ajoute un attribut unique au composant courant sur toutes les balises du template.

Exemple 1:

1
2
3
<template>
    <div class="render" v-html="response"></div>
</template>

Devient:

1
2
3
<template>
    <div class="render" v-html="response" data-v-abcdef123></div>
</template>

Example 2:

1
2
3
4
5
<template>
    <div class="render">
        <p v-html="response"></p>
    </div>
</template>

Devient:

1
2
3
4
5
<template>
    <div class="render" data-v-abcdef123>
        <p v-html="response" data-v-abcdef123></p>
    </div>
</template>

Au niveau du CSS, Vue fait une transformation similaire:

1
2
3
4
5
<style scoped>
    .render p {
        color: red;
    }
</style>

Devient:

1
2
3
4
5
<style>
    .render p[data-v-abcdef123] {
        color: red;
    }
</style>

De cette façon, le style n’entrera pas en conflit avec d’autres composants, vue que data-v-abcdef123 est unique à ce composant.

Revenons au problème initial:

1
2
3
<template>
    <div class="render" v-html="response"></div>
</template>

Que va donner ce template dans le DOM ? Seul les éléments initialement présents dans le template auront l’attribut:

1
2
3
<div class="render" data-v-abcdef123>
    <p>Red</p>
</div>

Donc le selecteur CSS .render p[data-v-abcdef123] ne fonctionnera pas.

Heureusement, il existe un moyen de résoudre ce problème: l’opérateur de "perçage de scope", >>> ou /deep/ (pour le Sass).

Exemple:

1
2
3
4
5
<style scoped>
    .render >>> p {
        color: red;
    }
</style>

Devient:

1
2
3
4
5
<style>
    .render[data-v-abcdef123] p {
        color: red;
    }
</style>

Le selecteur CSS est passé de .render p[data-v-abcdef123] à .render[data-v-abcdef123] p, ce qui permet d’être plus souple sur le contenu enfant dynamique. Ainsi, le paragraphe est bien ciblé et le texte coloré en rouge ! :)

Résultat final:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<template>
    <div class="render" v-html="response"></div>
</template>

<script>
    export default {
        data () {
            return {
                response = "<p>je suis un paragraphe !</p>"
            }
        }
    }
</script>

<style scoped>
    /* Scope piercing ! */
    .render >>> p {
        color: red;
    }
</style>

Documentation de vue-loader

Édité par Akryum

Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte