在 Nuxt3 使用 shadow DOM 取代 v-html 的歷程
前言
因為遇到了後台圖文編輯器在前端無法正常的 UI 顯示 ( 比如無法顯示斜體 )以下紀錄為了解決此問題我所思考以及學習的歷程
TL;DR
最終使用 Shadow DOM 解決
前端環境
先從 API 開始
每當前後端遇到問題,我都先會用 postman 檢查 API
我首先把後端吐回來的 html 使用 codepen render 看看,我發現該顯示的 UI 畫面如預期的都沒有問題,於是我知道問題可能發生在前端
v-html
<div v-html="data">
</div>
由於發生問題在於這行 code,當我打開 Chrome dev tool 的時候,發現這段 div 的 css 竟然 inherent global css style,於是我嘗試把 global css style 被影響的地方關掉,發現可以正常顯示
那更動或者關掉 global css
這樣方法確實可行,但影響的層面非常之廣,連帶影響其他的頁面,覺得不太妥
那如果在後台圖文編輯器寫 inline style 呢
此方法也不可行,因為對上架的人不友善
那有辦法影響 v-html 裡面的 css 嗎
我看了很多文章,找到了 Deep Selectors 這關鍵字,可以影響 v-html 裡面的 style 解決我目前的問題,但圖文編輯器就是可以讓使用者隨意編輯,這麼多種情境要處理感覺就不是一個正確的做法
備註:在找個解答的過程中,又學到 reset css 跟 normalize.css
思路轉變
上面的思考一直圍繞在一個想辦法影響 v-html 裡面 style 的圈圈裡面,走到最後發現還是不行
於是我沈寂了一天之後突然想到,如果讓此部分是一個獨立的 document 呢?有這想法之後馬上蹦出 iframe 來
iframe
<iframe :srcdoc="data"></iframe>
果然這樣的思維沒問題,後台圖文編輯器的畫面可以很順利的顯示,但衍伸而來的是高度問題
iframe 高度問題
我是參考這篇,作者透過 timer 跟 postMessage 來監聽 content 的高度,然後定期的更新 iframe 的高
但我沒真的實作,因為我在查資料的過程中發現了這篇
Upcoming Change to Embedded Tweet Display on Web
Tweet 說
Embedded Tweet display 要從 source-less iframe 改為 Shadow Dom
因為 lower memory,render times faster 以及更流暢的滑動 🤔🤔🤔
那具體為什麼更快呢?
詳情可看下方,簡單來說就是東西變少惹,要 traverse、update、create…就會更快
那其他人怎麼說
twitter 要換 iframe,那有沒有誰也想換,於是我找到這篇,作者在 BBC 工作
根據原文擷取一些重點,在處理 ifrmae 的時候需要一直跟 host page 溝通來溝通去,還要依據 content 處理高度以及 rwd 寬度…等
新名詞 source-less iframe
<iframe id="myIframe"></iframe>
就是 iframe 一開始沒有 src 這 attribute,之後再透過 javascript assign,請看 ChatGPT 回答
新名詞 2 :Shadow Dom
Shadow DOM allows hidden DOM trees to be attached to elements in the regular DOM tree
其核心價值就是 encapsulation
我自己的理解是在 Shadow DOM 裡有自己的 Scoped style ,且不會跟已有的 Dom Tree 有任何互相影響,所以解決我一開始的問題
後台圖文編輯器在前端無法正常的 UI 顯示
Nuxt3 Code
沒問題那就直接上 Code 拉
<!-- File name : ShadowDom.vue -->
<script setup lang="ts">
const element = ref(null)
const props = defineProps({
sourceDoc: {
type: String,
default: null,
},
})
onMounted(() => {
const shadowHost = computed(
() =>
element.value.shadowRoot || element.value.attachShadow({ mode: 'open' })
)
shadowHost.value.innerHTML = props.sourceDoc
})
</script>
<template>
<div ref="element"></div>
</template>
使用
<ClientOnly>
<ShadowDom :source-doc="data"></ShadowDom>
</ClientOnly>
Done
Ref
https://twittercommunity.com/t/upcoming-change-to-embedded-tweet-display-on-web/66215/1
https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM
https://medium.com/bbc-product-technology/goodbye-iframes-6c84a651e137