renderizando html bruto de dentro do vuejs
https://codepen.io/AnonymousCaptain/pen/eYZZOyO
Eu fiz isso, mas preciso que meu "{{data.body}}" seja interpretado como HTML.
Eu sou um novato total e espero que alguém aí possa me ajudar.
Até agora eu bati minha cabeça contra a parede digital olhando para a diretiva chamada v-html... mas não tenho certeza se é isso que devo usar.
Eu também vi pessoas fazerem algo como:
Vue.component("aplicativo", { modelo:HTML
https://www.digitalocean.com/community/tutorials/vuejs-raw-html-bindingIsso pode ser o que eu preciso?
Por favor, me aponte para uma solução :)
new Vue({
el: '#magiccardapp',
data: {
message: 'Hello',
tabs: {
'Title 1': {
subtitle: 'some text here',
title: 'Awesome Title',
body: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ab nam alias architecto officia, dolores animi qui debitis incidunt eius temporibus nostrum nihil soluta commodi molestiae necessitatibus ducimus amet. Suscipit, saepe!',
img_1: 'https://placedog.net/320/180/?random',
img_2: 'https://placedog.net/320/180/?random'
},
'Title 2': {
subtitle: 'some cool text here',
title: 'This is great',
body: 'Lorem ipsasdfasdfasd alias architecto officia, dolores animi qui debitis incidunt eius temporibus nostrum nihil soluta commodi molestiae necessitatibus ducimus amet. Suscipit, saepe!',
img_1: 'https://placedog.net/320/180/',
img_2: 'https://placedog.net/320/180/'
},
'Title 3': {
subtitle: 'some epic text here',
title: 'Look I\'m a title!',
body: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ab nam aliasdfasdfaas architecto officia, dolores animi qui debitis incidunt eius temporibus nostrum nihil soluta commodi molestiae necessitatibus ducimus amet. Suscipit, saepe!',
img_1: 'https://cdn.mos.cms.futurecdn.net/QjuZKXnkLQgsYsL98uhL9X-320-80.jpg',
img_2: 'https://aldf.org/wp-content/uploads/2018/06/sad-dog-1846066_1920-320x180.jpg'
},
'Title 4': {
subtitle: 'some other text here',
title: 'LOREM IPSUM?',
body: 'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ab nam alias architecto officia, dolores animi qui debitis incidunt eius temporibus nostrum nihil soluta commodi molestiae necessitatibus ducimus amet. Suscipit, saepe!',
img_1: 'https://cdn.mos.cms.futurecdn.net/QjuZKXnkLQgsYsL98uhL9X-320-80.jpg',
img_2: 'https://aldf.org/wp-content/uploads/2018/06/sad-dog-1846066_1920-320x180.jpg'
},
},
activeTab: 'Title 1',
},
computed:{
tabContent() {
return this.tabs[this.activeTab];
},
},
methods: {
setTabActive(tab) {
this.activeTab = tab;
}
},
components:{
'TabContent': {
props: {
data: Object,
},
}
},
})
/*tabs*/
magiccarddeck {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: 1rem 1rem;
align-items: center;
max-width: 1000px;
padding: 1rem 1rem;
font-family: "Open Sans",
sans-serif;
text-align: center;
}
magiccard:hover {
transform: scale(1.05);
border-radius: 0.25rem;
overflow: hidden;
transition: 1s;
-webkit-transition: 1s;
}
.line {
height: 2px;
width: 100%;
margin: auto;
background-color: #c38e3d;
}
magiccard {
background: ghostwhite;
border-radius: 0.25rem;
overflow: hidden;
transition: 1s;
-webkit-transition: 1s;
cursor: pointer;
height: 100%;
color: black;
border: 0.3rem solid #c38e3d;
height: max-content;
filter: brightness(50%);
}
magiccard:hover {
filter: brightness(100%);
}
magiccard.active {
cursor: unset;
transform: scale(1.05);
filter: brightness(100%);
}
magiccard h3 {
justify-content: center;
letter-spacing: 4px;
margin: 0px;
color: black;
font-weight: 400;
font-size: 20px;
line-height: 90%;
padding-left: 0%;
padding-top: 3%;
padding-bottom: 3%;
border-radius: 20px;
}
magiccard p {
line-height: 1.6;
}
/* BOTTOM */
section {
font-family: "Open Sans",
sans-serif;
font-weight: 800;
color: ghostwhite;
font-size: 12px;
}
#workshopgrid {
padding: 1rem 1rem;
max-width: 1000px;
justify-self: center;
}
workshopbox {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
border: 0.3rem solid #c38e3d;
border-radius: 0.5rem;
background: ghostwhite;
padding: 1rem 2rem;
grid-template-areas:
'text'
'img';
}
@media (min-width: 800px) {
workshopbox {
grid-template-columns: 3fr 1fr;
grid-template-areas:
'text img';
}
workshopdescription {
border-right-style: groove;
padding-right: 2%;
}
workshopimg {
padding-left: 1rem;
}
}
@media (max-width: 799px) {
workshopbox {}
workshopdescription {
border-bottom-style: groove;
}
workshopimg {
padding-top: 1rem;
}
}
.workshoptext {
grid-area: 1 / 1 / 2 / 2;
grid-area: text;
margin: 0px;
color: black;
font-weight: 400;
font-size: 15px;
line-height: 100%;
padding-top: 3%;
padding-bottom: 2%;
}
workshopdescription {
grid-area: 1 / 1 / 2 / 2;
grid-area: text;
margin: 0px;
color: black;
font-weight: 400;
font-size: 15px;
line-height: 100%;
padding-top: 3%;
padding-bottom: 2%;
}
workshopdescription h1 {
line-height: initial;
}
workshopimg {
grid-area: 1 / 2 / 2 / 3;
grid-area: img;
display: grid;
grid-gap: 1rem;
color: black;
align-content: center;
/* border-left: 1px solid; */
justify-content: center;
}
/*transitions*/
.fade-enter-active > *,
.fade-leave-active > * {
transition-duration: 200ms;
transition-property: opacity, transform;
transition-timing-function: cubic-bezier(0.6, 0.15, 0.35, 0.8);
}
.fade-enter > *,
.fade-leave-to > * {
opacity: 0;
transform: translateY(40px);
}
.fade-enter-active > *:nth-child(2) {
transition-delay: 100ms;
}
.fade-enter-active > *:nth-child(3) {
transition-delay: 200ms;
}
.fade-leave-active > *:nth-child(1) {
transition-delay: 200ms;
}
.fade-leave-active > *:nth-child(2) {
transition-delay: 100ms;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<main id="magiccardapp">
<magiccarddeck>
<magiccard v-for="(tab, tabName) in tabs" :key="tabName" @click="setTabActive(tabName)" :class="{'active': tabName === activeTab}">
<h3 class="tab-copy">{{ tabName }}</h3>
<div class="line"></div>
{{tab.subtitle}}
</magiccard>
</magiccarddeck>
<section id="workshopgrid">
<workshopbox>
<workshopdescription>
<transition name="fade" mode="out-in" appear :duration="500">
<tab-content v-for="(tabContent, t) in tabs" :data="tabContent" :key="'content'+t" v-if="t === activeTab" inline-template>
<div class="content">
<h1>{{data.title}}</h1>
<p>{{data.body}}</p>
</div>
</tab-content>
</transition>
</workshopdescription>
<tab-content v-for="(tabContent, t) in tabs" :data="tabContent" :key="'workshopimg'+t" v-if="t === activeTab" inline-template>
<workshopimg>
<img :src="data.img_1">
<img :src="data.img_2">
</workshopimg>
</tab-content>
</workshopbox>
</section>
</main>
Respostas
Você pode usar v-html
desta forma:
<p v-html="data.body"></p>
Ele renderizará html dentro da <p>
tag.
Se você quiser usar algum texto como html em vue, você pode usarv-html
Embora haja um aviso para não usar v-html
, porque isso tornará seu aplicativo propenso a ataques XSS.
Este aviso sempre estará lá e existe por um bom motivo. Para evitar ataques XSS, você só precisa limpar sua string html que está inserindo raw. Este processo de saneamento removerá qualquer coisa que possa levar à execução de script de HTML, evitando assim ataques XSS e assim você pode injetar com segurança conjuntos completos de nós HTML em seu DOM usando v-html
.
DOMPurify , que foi revisado por vários especialistas em segurança, é uma biblioteca adequada para limpar seu html bruto.