关于javascript:????️-Web-站点暗色模式探索

本文存在一些DEMO,适宜在 PC 端阅览

最近公布了本人的新博客 https://xlbd.me , 博客站点设计了暗色模式格调,然而过后只是基于媒体查问 perfers-color-schema 实现的追随零碎偏好设置切换主题格调,本次带来了可用户自定义的浅色/暗色主题格调切换性能,同时兼容追随零碎偏好设置切换主题格调。

追随零碎偏好设置切换

macOS Mojave 10.14+ 开始提供了外观设置选项,反对设置 浅色 / 深色 外观

macOS Catalina 10.15+ 开始能够设置 浅色 / 深色 / 主动 外观

用户随时都能够设定本人的零碎外观,或者让零碎在一天中从白天到早晨主动调整外观。随之而来的,perfers-color-schema CSS 媒体查问个性用于检测用户是否有将零碎的主题色设置为浅色或者暗色。其值为 light / dark ,看一个例子:

代码很简略,申明两个媒体查问,编写相应媒体查问下色值即可。

/* 示意用户设定了浅色主题时,页面背景色为红色 */
@media (prefers-color-scheme: light) {
  body { 
    background: white;
  
}

/* 示意用户设定了暗色主题时,页面背景色为彩色 */
@media (prefers-color-scheme: dark) {
  body { 
    background: black;
  }
}

https://codepen.io/xiaoluoboding/pen/wvMJZGG

⚠️ 关上 CODEPEN 查看 demo

自定义主题切换

CSS 媒体查问 perfers-color-schema 曾经帮咱们实现了零碎主动切换主题格调,尽管咱们比拟提倡在夜晚浏览网页或者应用APP时,用户看到的是暗色格调的UI界面,这样比拟护眼。然而如果设计一个可让用户可选的主题开关,交互上会更好,用户也能够自在掌控到底想以什么主题浏览页面。

基于 CSS Variables 的实现计划

一、定义 CSS 变量

将 CSS 变量编写在根伪类 :root 里,它的作用域是整个 HTML文档,任何中央都能拜访定义好的变量。上面咱们别离定义浅色 / 暗色两套 CSS 变量:

:root {
  --color-light: rgba(0, 0, 0, .75);
  --color-dark: rgba(255, 255, 255, .75);
  --background-light: #f0f2f4;
  --background-dark: #242424;
  --border-light: rgba(0, 0, 0, .33);
  --border-dark: rgba(255, 255, 255, .33);
}

二、增加开关逻辑

开关逻辑外围性能其实就是标示以后用户的抉择,咱们须要一个状态来记录用户行为,应用 localStorage 保留状态;在 body 标签上设置一个属性 data-user-color-schema ,动静扭转它的值。将这个属性作为选择器来应用。

const APP_THEME = 'user-color-scheme'

// set init theme mode as dark
localStorage.setItem(APP_THEME, 'dark')

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

/**
 * if user select the theme, then use the given theme
 */
const applyTheme = givenTheme => {
  let currentTheme = givenTheme || localStorage.getItem(APP_THEME)
  
  if(currentTheme) {
    document.body.setAttribute('data-user-color-scheme', currentTheme)
  }
}

toggleButton.addEventListener('click', e => {
  e.preventDefault()
  
  applyTheme(toggleTheme())
})

三、动静 CSS 变量实现模式切换

不同选择器能够笼罩 CSS 变量的值,利用这个个性,能够取得动静的 CSS 变量。

:root {
  --color-light: rgba(0, 0, 0, .75);
  --color-dark: rgba(255, 255, 255, .75);
  --background-light: #f0f2f4;
  --background-dark: #242424;
  --border-light: rgba(0, 0, 0, .33);
  --border-dark: rgba(255, 255, 255, .33);
}

[data-user-color-scheme='light'] {
  --color-mode: 'light';
  --text-color: var(--color-light);
  --background-color: var(--background-light);
  --border-color: var(--border-light);
}

[data-user-color-scheme='dark'] {
  --color-mode: 'dark';
  --text-color: var(--color-dark);
  --background-color: var(--background-dark);
  --border-color: var(--border-dark);
}

body {
  padding: 2rem 1rem;
  color: var(--text-color);
  background: var(--background-color);
}

到这里,咱们就实现了用户自定义的主题切换性能,看上面例子中,点击切换按钮,body 中的色彩属性曾经在动静扭转了。

如果设置了自定义主题 user-color-schema 切换,那么 perfers-color-schema 的优先级就要升高了。因为用户的选择权要高于零碎的偏好设置。

初始化主题模式

下面提到了 user-color-schema 优先级要高于 perfers-color-schema ,那么是不是阐明 perfers-color-schema 媒体查问就没什么用了,并且也享受不到追随零碎偏好展现页面的性能了呢,其实咱们有一个解决的办法,利用 window.matchMedia() API 来甄别以后用户的零碎外观偏好设置。

// 如果匹配到 perfers-color-schema: dark, 代表以后零碎外观偏好设置为暗色
if (window.matchMedia) {
  const colorSchema  = window.matchMedia('(prefers-color-scheme: dark)')
    console.log(colorSchema.matches) // Boolean: true/false
}

通过这个办法咱们就能够实现判断用户的零碎是否设置了暗色外观,利用这一点咱们能够帮忙用户默认抉择页面渲染模式。

https://codepen.io/xiaoluoboding/pen/eYJWyeM

⚠️ 关上 CODEPEN 查看 demo

“鱼”和”熊掌”两者兼顾

尽管实现了追随零碎初始化主题模式,然而因为 use-color-schema 依然优先级比 perfers-color-schema ,这时当你切换零碎外观偏好的时候,页面是没有追随扭转的。

这时咱们须要晓得零碎偏好何时发生变化,也就是咱们冀望晓得 perfers-color-schema 的值何时发生变化,同样是基于 window.matchMedia() API,看上面例子:

const initTheme = () => {
  if (window.matchMedia) {
    const colorSchema  = window.matchMedia('(prefers-color-scheme: dark)')
    
        // 为媒体查问增加监听器
    colorSchema.addListener(e => {
      console.log(e.matches) // Boolean: true/false
      const currentTheme = e.matches ? 'dark' : 'light'
      localStorage.setItem(APP_THEME, currentTheme)
      toggleButtonMode.innerHTML = e.matches ? 'light' : 'dark'
      applyTheme(currentTheme)
    })
  }
}

https://codepen.io/xiaoluoboding/pen/ExPmJyQ?editors=1010

⚠️ 关上 CODEPEN 查看 demo

默认暗色模式

要实现站点默认是暗色模式,其实很简略,色彩反着写就OK了

body {
  background-color: black;
  color: white;
}

@media (prefers-color-scheme: light) {
  body {
    background-color: white;
    color: black;
  }
}

Vue 3 中应用暗色模式

Vue3 的 composition API (组合式 API)提供了更好的逻辑复用和代码组织。

以下例子,咱们能够将暗色模式变为响应式,一旦零碎偏好扭转了外观扭转,咱们能够立刻获取扭转的值。

抽离逻辑

定义 usePerfered

将公共逻辑媒体查问 window.matchMedia.matches 变为响应式进行形象

// /src/helper/usePerfered.js
import { ref } from 'vue'
import { tryOnMounted, tryOnUnmounted } from './utils'

export function usePerferred (query) {
  let mediaQuery = null

  if (typeof window !== 'undefined') {
    mediaQuery = window.matchMedia(query)
  }

  const matches = ref(mediaQuery ? mediaQuery.matches : false)

  function handler(event) {
    matches.value = event.matches
  }

  tryOnMounted(() => {
    if (!mediaQuery) {
      mediaQuery = window.matchMedia(query)
    }
    handler(mediaQuery)
    mediaQuery.addListener(handler)
  })

  tryOnUnmounted(() => {
    mediaQuery.removeListener(handler)
  })

  return matches
}

组件中应用

咱们失去了响应式的数据 isDarkmode ,它的变动会引起页面上的视图更新,可见 composition api 对业务逻辑的形象是很有帮忙的。

// /components/Darkmode.vue
import { reactive, watch, watchEffect, onMounted, toRefs } from 'vue'
import { usePerferred } from '../helper/usePerferred'

export default {
  setup () {
    const state = reactive({
      isDarkmode: usePerferred('(prefers-color-scheme: dark)')
    })
    ...
    return {
      ...toRefs(state)
    }
  }
}

色彩管制

HSLa 介绍

HSL 色相-饱和度-亮度(Hue-saturation-lightness)模式,HSL 相比 RGB 的长处是更加直观:你能够估算你想要的色彩,而后微调。它也更易于创立相称的色彩汇合。

比方下图形容了:hsl(200, 100%, 50%) 不同透明度的色彩体现

应用 CSS Variables 定制 HSLa

首先,咱们将可管制的(Hue-saturation-lightness)拆分为独立变量,这样做的益处就是我能够独自扭转某一个值去管制色彩的变动

:root {
  --text-color-h: 200;
  --text-color-s: 100%;
  --text-color-l: 50%;
}

将拆分的变量组合为 HSL 模式

:root {
  --text-color-h: 200;
  --text-color-s: 100%;
  --text-color-l: 50%;
  --text-color-hsl: var(--text-color-h), var(--text-color-s), var(--text-color-l);
}

基于定义好的 HSL,咱们能够扩大色彩的不同透明度的色阶

:root {
  --text-color-h: 200;
  --text-color-s: 100%;
  --text-color-l: 50%;
  --text-color-hsl: var(--text-color-h), var(--text-color-s), var(--text-color-l);
  --text-color: hsla(var(--text-color-hsl), 1);
  --text-color-5: hsla(var(--text-color-hsl), .05);
  --text-color-10: hsla(var(--text-color-hsl), .1);
  --text-color-20: hsla(var(--text-color-hsl), .2);
  --text-color-30: hsla(var(--text-color-hsl), .3);
  --text-color-40: hsla(var(--text-color-hsl), .4);
  --text-color-50: hsla(var(--text-color-hsl), .5);
  --text-color-60: hsla(var(--text-color-hsl), .6);
  --text-color-70: hsla(var(--text-color-hsl), .7);
  --text-color-80: hsla(var(--text-color-hsl), .8);
  --text-color-90: hsla(var(--text-color-hsl), .9);
  --text-color-light: hsl(var(--text-color-h), var(--text-color-s), calc(var(--text-color-l) / .8));
  --text-color-dark: hsl(var(--text-color-h), var(--text-color-s), calc(var(--text-color-l) * .8));
}

应用 HSLa 设定暗色主题

https://codepen.io/xiaoluoboding/pen/wvGxvwB?editors=0100

⚠️ 关上 CODEPEN 查看 demo

参考

  • https://web.dev/prefers-color-scheme/
  • https://codepen.io/chriscoyier/pen/YvPLRg
  • https://tympanus.net/codrops/css_reference/hsla/

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理