<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Dark Mode 實(shí)踐踩坑記錄

          共 5306字,需瀏覽 11分鐘

           ·

          2022-03-08 23:16

          只能說,實(shí)現(xiàn) Dark Mode 的盡頭是手寫。

          手機(jī) QQ 最近火急火燎地整改,暗黑模式的支持就是其中的一個(gè)整改項(xiàng)。由于騰訊課堂在手機(jī) QQ 有一個(gè)常駐入口,因此我們也要按照它們的要求實(shí)現(xiàn)真正意義上的 dark mode 支持 (而不是目前手機(jī) QQ 強(qiáng)制給加的一層灰色蒙層)。

          大學(xué)時(shí)候有個(gè)項(xiàng)目也是自己設(shè)計(jì)和實(shí)現(xiàn)的 dark mode 支持,當(dāng)時(shí)是手寫的,依稀記得后面從哪些文章里看到說可以一行代碼實(shí)現(xiàn)暗黑模式云云,于是企圖在這次實(shí)踐過程中應(yīng)用下這些奇技淫巧,然而經(jīng)過一天的實(shí)踐,我發(fā)現(xiàn)這些方法有繞不過的坑,最后只得推翻重來手寫一把,下面來細(xì)說一下。

          常見實(shí)踐

          在開始寫代碼前先 Google 了一下,CSS Tricks 的?A Complete Guide to Dark Mode on the Web?比較推薦閱讀,里面寫的還挺全的。

          開啟方式

          一般來說會(huì)有兩種開啟方式,一種會(huì)在頁面 (通常右上角) 使用一個(gè) switch 開關(guān)控制頁面是 light 還是 dark,一種會(huì)根據(jù)系統(tǒng)或者應(yīng)用的 Preference 來自動(dòng)切換。

          Manually toggle

          對(duì)于手動(dòng)選擇的模式,我們要如何讓開關(guān)和樣式關(guān)聯(lián)上呢?肯定要給這個(gè)開關(guān)加個(gè)事件處理函數(shù)了,里面可以去改變頁面根元素的類名,通過類名控制樣式,如下。

          const btn = document.querySelector('.btn-toggle');

          btn.addEventListener('click', function() {
          document.body.classList.toggle('dark-theme');
          })
          // 方法 1: 通過改變類,并使用不同的樣式
          body {
          color: #222;
          background: #fff;

          a {
          color: #0033cc;
          }
          }

          body.dark-theme {
          color: #eee;
          background: #121212;

          a {
          color: #809fff;
          }
          }
          // 方法 2: 通過改變類,并使用不同的 CSS 變量
          body {
          --text-color: #222;
          --bkg-color: #fff;
          --anchor-color: #0033cc;
          }

          body.dark-theme {
          --text-color: #eee;
          --bkg-color: #121212;
          --anchor-color: #809fff;
          }

          body {
          color: var(--text-color);
          background: var(--bkg-color);

          a {
          color: var(--anchor-color);
          }
          }

          也可以去改變加載的樣式文件,通過不同的 css 文件來控制樣式。

          <html lang="en">
          <head>
          <link href="light-theme.css" rel="stylesheet" id="theme-link">
          head>
          html>

          const btn = document.querySelector(".btn-toggle");
          const theme = document.querySelector("#theme-link");

          btn.addEventListener("click", function() {
          if (theme.getAttribute("href") == "light-theme.css") {
          theme.href = "dark-theme.css";
          } else {
          theme.href = "light-theme.css";
          }
          });

          /* light-theme.css 文件寫一份樣式 */

          /* dark-theme.css 文件寫一份樣式 */

          Follow system

          對(duì)于要自動(dòng)適配系統(tǒng)的模式,我們要如何判斷系統(tǒng)的偏好并編寫樣式呢?一種方法是用 CSS 所支持的?prefers-color-scheme?這個(gè) media query 來包含樣式,另一種類似,也是通過對(duì)這個(gè) query 的匹配來判斷繼而添加類名和樣式。

          /* 通過 media query 直接寫 */
          @media (prefers-color-scheme: dark) { }
          @media (prefers-color-scheme: light) { }
          if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
          document.body.classList.add('dark-theme');
          } else {
          document.body.classList.remove('dark-theme');
          }

          用戶偏好

          這個(gè)功能其實(shí)是有真實(shí)的需求的,比如如果我們不記住用戶偏好,那么肯定只能有一種默認(rèn)值,再在加載的過程中判斷是偏好 light 或 dark。這種情況下,我們勢必會(huì)得罪一方 (eg. 默認(rèn) light 那么對(duì)于 dark 偏好的用戶來說,肯定會(huì)先閃過白色樣式再加載到正確的黑色樣式,反之亦然),這種現(xiàn)象叫做 “flash of incorrect theme” (FOIT)。

          想要記住用戶偏好,可以把這個(gè)偏好值存儲(chǔ)在 localStorage 里,不過這對(duì)于「follow system」的用戶來說不適用,總不能給 system preference 添加監(jiān)聽函數(shù),它一改我就去改這個(gè)偏好值吧,系統(tǒng)偏好不在我的管轄范圍內(nèi),在頁面設(shè)置偏好倒是可行。對(duì)于前后端不分離的類型,還可以把偏好值放到 cookie 里,讓 server 獲取到偏好從而返回對(duì)應(yīng)的 HTML。

          奇技淫巧

          有一個(gè)方法可以一行代碼搞定 dark mode,而且乍一看效果還可以。

          html {
          filter: invert(1) hue-rotate(180deg);
          }



          解釋一下這里的樣式,filter 其實(shí)是濾鏡,它本身提供了很多處理的接口,參考,比如模糊?blur()、灰度?grayscale()、對(duì)比度?contrast()?等。

          其中 invert 的作用是反轉(zhuǎn)顏色通道數(shù)值,接收的值在 0~1。可以把?invert(param)?想象成一個(gè)函數(shù)?f(value, param) = param * (255 - value) + (1 - param) * value,當(dāng) param 為 0 時(shí)這個(gè)公式退化為 f (value) = value 也就是不變色,當(dāng) param 為 1 時(shí)這個(gè)公式退化為?f(value) = 255 - value。比如一個(gè)?rgb(0, 0, 255)?在被套用 invert (1) 后會(huì)變成?rgba(255, 255, 0);一個(gè)?rgb(255, 0, 0)?在被套用 invert (0.85) 后會(huì)變成?rgb(38, 217, 217)(套公式,0.85*(255-255) + (1-0.85)* 255 = 38.25),參考

          其中 hue-rotate 的作用是轉(zhuǎn)動(dòng)色盤,接收的值在 0deg~360deg。這個(gè)其實(shí)更好理解,如下圖是色盤,比如一個(gè)純紅色在被套用?hue-rotate(90deg)?后會(huì)變成綠色,相當(dāng)于我的取色點(diǎn)針順時(shí)針轉(zhuǎn)了 90°,具體的計(jì)算和矩陣運(yùn)算相關(guān),參考。感覺這個(gè)轉(zhuǎn)換還蠻復(fù)雜的,參考?stackoverflow 的討論,還有個(gè)將 black 通過 filter 轉(zhuǎn)換成想要顏色的工具

          在二者疊加的效果下,就會(huì)有很神奇的暗色模式了。但我們可以很明顯地看到,這里的圖片也被反色了,這不是我們預(yù)期的效果,一個(gè)常見的做法是給 img 標(biāo)簽再使用這個(gè) filter 給反回去,它是生效的,如下圖。

          html {
          filter: invert(1) hue-rotate(180deg);

          // 圖片反轉(zhuǎn)再反轉(zhuǎn),就和原先一樣了
          img {
          filter: invert(1) hue-rotate(180deg);
          }
          }

          PS: 對(duì)于 invert 非 1 的,無法通過兩次 revert 來反轉(zhuǎn)到初始值,參考

          暗黑模式の坑

          根據(jù)目標(biāo)色反推源頭色

          • 問題
            如果要在實(shí)踐中使用 filter 來實(shí)現(xiàn)暗黑模式,那么我們就不需要給各種 color 設(shè)置偏黑色的,而是用原始的偏白色顏色,因?yàn)檫@樣套用 filter 自然就會(huì)變黑。想要達(dá)到目標(biāo)樣式,只需要設(shè)置一個(gè)特定的偏白色,讓這個(gè)色通過 filter 后呈現(xiàn)目標(biāo)樣式就行 (目標(biāo)顏色在設(shè)計(jì)稿里)。那么問題來了,我要怎么根據(jù)設(shè)計(jì)稿里的偏黑顏色,去反推我要設(shè)置的偏白初始值呢?

          • 解決
            聰明的我想到了一種方法,就是反其道而行之。先把目標(biāo)值設(shè)為某個(gè)元素的 color,給整個(gè)頁面加個(gè) filter,用取色器應(yīng)用 (無法用 chrome devtools 的取色器噢!) 來取當(dāng)前的顏色,這個(gè)顏色是不是就是我們需要的呢?果不其然,的確如此。不過隨著實(shí)驗(yàn)越多,我發(fā)現(xiàn)黑白這一類的可以得到正確的顏色,但是彩色的貌似不是這么容易就能推出來的。這里取色還要注意下,電腦和外接顯示屏不一樣。

          通過 Background url 設(shè)置的圖片無法反色

          • 問題
            像下面的例子,即使加了上面的樣式,還是沒法反色。


          • 原因
            首先是因?yàn)檫@種方法設(shè)置圖片的元素,無法通過 img 標(biāo)簽選擇到 (那是自然!),且有個(gè)規(guī)定,對(duì)于設(shè)置了 background 屬性的選擇器,在其中寫的 filter 屬性是完全不生效的,參考。解決辦法要么把這些都換成 img,要么用 hack 一些的加偽元素的方法,不過前者不太現(xiàn)實(shí),后者不太方便。單就這一個(gè)問題就可以否決掉 filter 的方案了。

          Filter 影響其他元素

          • 問題

            給某個(gè) H5 頁面內(nèi) react root 元素添加 filter 后,發(fā)現(xiàn)頁面上的頂部固定搜索框、底部固定 tab 欄都消失不見了,類似下圖。
            正常情況下:

            給 react root 元素添加 filter 后:

          • 原因
            搜了好多問題,終于通過一篇被搬運(yùn)的文章發(fā)現(xiàn)了問題所在 (感謝這篇文章!),原因是 filter 屬性會(huì)影響 fixed 的組件,因?yàn)樗鼤?huì)給 absolute 和 fixed 的元素添加一個(gè) containing block,除非這個(gè)被添加 filter 的元素是 document 的根元素 (也就是 html 元素),否則 fixed 和 absolute 相對(duì)的位置就不對(duì)了。fixed 的元素會(huì)相對(duì)于使用了 filter 的元素來定位,而不是相對(duì)于視口 viewport,解決辦法有兩種,要么把 filter 只設(shè)置在 html 元素上來避開,要么針對(duì)每個(gè) fixed 元素套一個(gè) container,只給這個(gè) container 使用 filter,參考

          直出頁面無法設(shè)置?Dark Mode 類名

          • 問題
            在調(diào)試的時(shí)候,發(fā)現(xiàn)某個(gè)直出頁面大體顏色都正常,但有兩個(gè)模塊顏色不對(duì)勁。

          • 解決
            排查到原因在于,這個(gè) Container 中有兩個(gè)子組件少了?dark-mode?的類名。但是這幾個(gè)組件都是同樣的判斷條件和傳遞 props 方式,為什么會(huì)有的帶上了正確的類名,令人百思不得其解。
            后面再思考下,有可能是因?yàn)?props 不行,如果我把 props 改成 state 呢?實(shí)踐后發(fā)現(xiàn)可行,也就是在 constructor 中設(shè)置一個(gè) isDarkMode 的值為?false,在 componentDidMount 的時(shí)候去設(shè)置 isDarkMode 的值為?!!this.props.isDarkMode。但如果我在 constructor 中就設(shè)置?!!this.props.isDarkMode?就會(huì)不生效,為什么呢?原因在于我們對(duì)系統(tǒng)偏好的判斷,是通過?window.matchMedia?來的,這個(gè)在直出的 server 端必然獲取不到,這里我們 server 返回的 html 字符串就是錯(cuò)誤的。當(dāng)頁面返回到 client 這邊,加載的 js 會(huì)執(zhí)行各種生命周期函數(shù),componentDidMount 這里的 setState 值和 constructor 中的初始 state 值是一樣的,就不會(huì)觸發(fā) update,就會(huì)直接使用我們錯(cuò)誤的 html 字符串對(duì)應(yīng)的頁面。因此會(huì)顯示錯(cuò)誤。


          往期推薦


          第一次拿全年年終獎(jiǎng)的前端女程序員的2021
          2022 年值得關(guān)注的 9 大用戶體驗(yàn)趨勢
          收藏!史上最全 Vue 前端代碼風(fēng)格指南

          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...

          點(diǎn)個(gè)在看支持我吧
          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男女高清无码 | 久久综合电影 | 久久人色| 亚洲AAA在线观看 | 日本高清黄页免费网站大全 |