顯示具有 v-deep 標籤的文章。 顯示所有文章
顯示具有 v-deep 標籤的文章。 顯示所有文章

2024年3月15日 星期五

要改變component的樣式為什麼不是用v-deep

前言

在與前同事隨意尬聊的狀況下,前團是提出要我幫他看code的需求,他說他一直無法改變component的樣式,我想說不過就是v-deep的麽簡單的事情,所以想也不想就答應了,殊不知最終我搞了兩個小時的故事....

問題起源於他使用了套件vue-advanced-chat來製作聊天視窗,正確來說是前同事使用,所以他也只是背鍋而已。然後公司需求很多,然後這套件很多功能其實都無法達成需求。這故事告訴我們,選元件要慎選,如果客製化需求太多,自己造輪子不一定是不好,至少彈性足夠。

接下來要說說這次遇到什麼問題,就是audio的樣式在某種解析度會跑版,需要用css去修正,但無論用v-deep或是global寫css,死活都是吃不到,正一籌莫展之際,我看到了幾個關鍵字。

首先第一個為什麼這個套件的tag還可以保持原樣<vue-advanced-chat>,然後在tag下面出現#shadow-root(open)的字樣,下面可以展開,上次我看到類似的景樣是在iframe裡面,如果這狀況跟iframe真的是類似,那確實css selector無法吃到裡面也很正常。我上網查了關鍵字發現確實是類似的情況shadow dom,簡單說被web component包裝成一個原生tag了。

我原本想說有了關鍵字剩下就簡單了,所以去查要怎麼樣才能讓shadow dom吃到css,查了半天都說有:host之類的選擇器可以用,但每個都是過了一樣沒有效果,但看到這網站說明後才大致明瞭這東西必須在寫web component當時,也就是內部撰寫,所以也還是沒辦法達成我要的效果,且裡面也明確表示css是無法從外層直接影響內層的樣式。

正當我想放棄時我想到,既然css無法做到,能否使用javascript去強制修改呢?結果還真讓我找到解法,要分成兩個步驟來說,首先你要可以選擇到shadow dom,做法也不難,就是你要在選擇到web component之後,直接.shadowRoot就可以選到dom,之後再創造一個style並把內容寫好寫滿,之後appendChild進去就好,我們直接來看code

// 寫一個可以選擇到shadow dome的selector
const querySelectorAll = (node,selector) => {
const nodes = [...node.querySelectorAll(selector)],
nodeIterator = document.createNodeIterator(node, Node.ELEMENT_NODE);
let currentNode;
while (currentNode = nodeIterator.nextNode()) {
if(currentNode.shadowRoot) {
nodes.push(...querySelectorAll(currentNode.shadowRoot,selector));
}
}
return nodes;
}

// inject css
const style = createElement("style")
style.innerHTML = `
.class { ...... }
......
`

document.querySelector("custom-web-component").shadowRoot.appendChild(style)

這樣就大功告成拉。

後記

我就說好好的vue component為什麼要包成web component,應該是考量到要讓各種框架使用,然後你包沒有關係,為什麼要包成shadow dom,然後你包成shadow dom沒有關係,為什麼還要有bug!!好在大爺我幾十歲了,不怕你!雖然已經一點多了,下班

2021年1月7日 星期四

改變Vue sub component樣式

相信元件帶給大家的好處不言而喻,但使用上總是有一些不方便之處,例如三方的元件樣式我能否調成自己喜歡的?我自己的元件在不同情境下樣式能否不一樣?難道最終都難逃考比爬斯特,重新寫一個的命運嗎?

小編的任務就是盡可能幫各位提早下班,第一個問題,我們可以寫個全域css去改變他

先假設我們的子元件長這樣

// hello.vue
<div class="component-hello">
  hello
</div>
<style scoped>
  .component-hello {
    color: red
  }
</style>

父元件長這樣

// App.vue
<div class="app">
  <hello />
</div>

<style>
  .app .component-hello {
    color: blue
  }
</style>

在先不考慮css分數的情況下,這樣寫原則上就可以覆蓋component裡面的樣式。

雖然一般三方class寫都得不錯,不太會有重複的情況,但css汙染在我心目中跟watch一樣危險,有很多不可控的因素,所謂的不可控,所謂的黑魔法,就是當下可能會覺得很爽,一個禮拜之後就會變成導致你加班的原因,且一個不小心就會演變成泥沼戰(就是比誰宣告比較兇),所以能不要互相汙染、互相傷害就不要去做這種事,能寫sooped就給他用力寫,為這社會帶來一絲和諧吧!

然而你會發現當你加上了scoped,你原本吃的到css一夕之間的突然都吃不到了,原因是因為scoped會為該component的所有tag注入一個唯一的屬性(亂數產生),但卻不會為子component注入該屬性,所以導致子component吃不到上層scoped的css。那這問題怎麼要解呢?

這就是今天寫這篇主題的目的:那就是使用v-deep,用法很簡單如下。

// App.vue
<div class="app">
  <hello />
</div>

<style scoped>
  .app /deep/ .component-hello {
    color: blue
  }
</style>

這樣就可以控制在App.vue下的component-hello顏色才會是藍色,這樣也同時解決了我們上述的第二個問題,同一個元件在不同class下可以有不同樣式就是這樣來的。

大部分語言也可以簡寫成如下,我以stylus為例

<style lang="stylus" scoped>
  .app >>> .component-hello 
    color blue
</style>

scss或sass語言也可以簡寫成如下

<style lang="stylus" scoped>
  .app ::deep .component-hello 
    color blue
</style>

打完收工下班!