前言

为了方便管理, 我们会定义 CSS Variables, 类似于全局变量. 有时候做特效的时候还需要 JavaScript 配合,

这时就会希望 JavaScript 可以获取到 CSC Variables, 虽然 JS 可以通过 getComputedStyle 单独获取某个 CSS Variable 但是, 若想获取所有的 CSS Variables 就没那么容易了.

它需要通过 Document.styleSheets 的方式去获取, 这篇就是介绍这个的.

参考

stackoverflow – Get all css root variables in array using javascript and change the values

CSS-Tricks – How to Get All Custom Properties on a Page in JavaScript

介绍

Document.styleSheets 可以获取到页面里所有的 Style CSS information. 类似于 C# 的反射, 让你可以遍历所以的 Style.

这篇我们就要通过这个功能, 获取到想要的 CSS Variables. 当然不管你想获取什么 Style 都是可以通过这个方式的, 不限制在 CSS variables, 自己遍历, 自己过滤就可以了.

案子

我这次是想实现一个 JS 的 breakpoint media query. 通过 Scss/CSS variables 来做管理. 过程是 Scss 定义 variables 然后写入到 CSS variables,

JS 通过 Document.styleSheets 遍历出 breakpoint CSS variables. 然后配合 Window.matchMedia 做 media query.

Scss Breakpoint

先看看 Scss 怎样弄, JS 也会实现一摸一样的方式.

file 结构

我一般上用 2 个 scss file 做管理.

第一个是 _core.scss, 里面封装功能

第二个是 _base.scss, 里面写当前项目的逻辑

剩下的就是一个页面一个 .scss

home.scss 调用

@use '../base' as *;

@include core-media-breakpoint-only('xl') {
:root {
--breakpoint-special: 123px;
}
}

_base.scss re-export 了 _core 所以调用方法时 core-media...

_base.scss 定义

$breakpoint-collection: (
xs: 0,
sm: 640px,
md: 768px,
lg: 1024px,
xl: 1280px,
'2xl': 1536px,
); @forward './core' as core-* with (
$breakpoint-collection: $breakpoint-collection
); @use './core'; :root {
@include core.root-breakpoint($breakpoint-collection);
}

CSS media query 不支持 variable, :root 的 variables 只是 for JS 用而已.

_core.scss 核心代码

@use 'sass:list';

@function map-get-next($map, $key) {
$keys: map-keys($map);
$values: map-values($map);
$index: list.index($keys, $key);
$count: length($keys);
$next-index: $index + 1;
@if ($next-index > $count) {
@return null;
}
@return list.nth($values, $next-index);
} $breakpoint-collection: null !default;
@function breakpoint($size) {
@return map-get($breakpoint-collection, $size);
}
@function breakpoint-next($size) {
@return map-get-next($breakpoint-collection, $size);
}
@mixin media-breakpoint-up($breakpoint) {
@media (min-width: breakpoint($breakpoint)) {
@content;
}
}
@mixin media-breakpoint-down($breakpoint) {
@media (max-width: breakpoint($breakpoint) - 0.02px) {
@content;
}
}
@mixin media-breakpoint-only($breakpoint) {
$current: breakpoint($breakpoint);
$next: breakpoint-next($breakpoint);
@if ($next == null) {
@media (min-width: $current) {
@content;
}
} @else {
@media (min-width: $current) and (max-width: $next - 0.02px) {
@content;
}
}
}
@mixin media-breakpoint-between($from-breakpoint, $to-breakpoint) {
@media (min-width: breakpoint($from-breakpoint)) and (max-width: breakpoint($to-breakpoint) - 0.02px) {
@content;
}
} @mixin root-breakpoint($breakpoint-collection) {
@each $breakpoint-key-value in $breakpoint-collection {
// note 解忧: + '' 是为了 clear sass warning
--breakpoint-#{'' + list.nth($breakpoint-key-value, 1)}: #{list.nth($breakpoint-key-value, 2)};
}
}

第二部分是主角, up, down, only, between 这个是效仿 Bootstrap 的做法.

document.styleSheets

StyleSheetList

console.log('document.styleSheets', document.styleSheets);

document.styleSheets 是一个 List 对象.

页面所有的 CSS Style 都会在里面, 不管是 <link> 或者 <style>

CSSStyleSheet

CSSStyleSheet 最重要的属性是 cssRules 和 href

cssRules 就是所有具体的 style 内容, 下面会详细讲.

href = null 代表它是 <style>, href=url 表示是 <link>

通过 window.location 可以判断 CSS 是否是 thrid party

console.log('location', window.location.origin);
console.log('document.styleSheets', document.styleSheets[1].href);
console.log('same domain', document.styleSheets[1].href!.startsWith(window.location.origin + '/'));

效果

CSSRuleList > CSSRule (CSSStyleRule / CSSMediaRule)

CSSStyleSheet.cssRules 是一个 CSSRuleList

CSSRuleList 里面包含了 CSSStyleRule 和 CSSMediaRule (注意: CSSRule 是 CSSStyleRule 和 CSSMediaRule 的抽象)

每一个 rule 表示一个 selector 还有它的 style

比如下图有 3 个 CSSRule

第一个是 CSSStyleRule, selectorText 是 'body'

第二个是 CSSStyleRule, selectorText 是 'h1'

第三个是 CSSMediaRule, selectorText 是 ':root'

只要是在 media query 内声明的 selector 都属于 CSSMediaRule

CSSStyleRule

最重要的属性是 selectorText 还有 style.

它们长这样

style 的 interface 是 CSSStyleDeclaration, 和 window.getComputedStyle 的返回值相同的 interface.

它是一个 iterable 对象, 通过 for...of 可以获取所有的 keys, 想获取 value 就调用 style.getPropertyValue 方法

console.log('rule.style.keys', [...rule.style]);
for (const key of rule.style) {
const value = rule.style.getPropertyValue(key);
console.log([key, value]);
}

效果

value 前面有 space 是正常的, 因为 prettier formatting 为了整齐好看都会添加空格, 取值后最好是 trim() 一下.

CSSMediaRule

每一个 media query 都会产生一个 CSSMediaRule, 哪怕 media query 是一样的

@media (min-width: 1280px) and (max-width: 1535.98px) {
:root {
--breakpoint-special: 123px;
}
}
@media (min-width: 1280px) and (max-width: 1535.98px) {
body {
--breakpoint-xx: 123px;
}
}

效果

CSSMediaRule 最重要的属性是 conditionText 和 cssRules

conditionText 就是 (min-width: 1280px) and (max-width: 1535.98px) 这些 media query

cssRules 就是 CSSRuleList 和上面提过的是一样的.

JavaScript Breakpoint

万事俱备, 有了 document.styleSheets 配上 Window.matchMedia, 我们就可以实现和 Scss 一摸一样的 break point media query 了.

调用

console.log('matches', mediaBreakpointUp('xl').matches);
mediaBreakpointBetween('sm', 'lg').addEventListener('change', e => {
console.log('matches', e.matches);
});

返回的是 MediaQueryList

四大函数

function getBreakpointCollectionFromStyleSheet(): Map<string, string> {
const breakpointCollection = new Map<string, string>();
const cssRules = Array.from(document.styleSheets).flatMap(sheet => Array.from(sheet.cssRules));
for (const cssRule of cssRules) {
if (cssRule instanceof CSSStyleRule && cssRule.selectorText === ':root') {
const keyStartsWith = '--breakpoint-';
for (const key of cssRule.style) {
if (!key.startsWith(keyStartsWith)) {
continue;
}
const value = cssRule.style.getPropertyValue(key).trim();
breakpointCollection.set(key.replace('--breakpoint-', ''), value);
}
}
}
return breakpointCollection;
} function mediaBreakpointUp(breakpoint: string): MediaQueryList {
const breakpointCollection = getBreakpointCollectionFromStyleSheet();
const breakpointValue = breakpointCollection.get(breakpoint)!;
return window.matchMedia(`(min-width: ${breakpointValue})`);
} function mediaBreakpointDown(breakpoint: string): MediaQueryList {
const breakpointCollection = getBreakpointCollectionFromStyleSheet();
const breakpointValue = breakpointCollection.get(breakpoint)!;
return window.matchMedia(`(max-width: ${parseFloat(breakpointValue) - 0.02}px)`);
} function mediaBreakpointOnly(breakpoint: string): MediaQueryList {
const breakpointCollection = getBreakpointCollectionFromStyleSheet();
const currentBreakpointValue = breakpointCollection.get(breakpoint)!;
const nextBreakpointValue = (() => {
const keys = Array.from(breakpointCollection.keys());
const currentIndex = keys.indexOf(breakpoint);
const hasNext = currentIndex < keys.length - 1;
if (!hasNext) {
return null;
} else {
const nextKey = keys[currentIndex + 1];
return breakpointCollection.get(nextKey)!;
}
})();
const mediaQuery =
nextBreakpointValue === null
? `(min-width: ${currentBreakpointValue})`
: `(min-width: ${currentBreakpointValue}) and (max-width: ${
parseFloat(nextBreakpointValue) - 0.02
}px)`;
return window.matchMedia(mediaQuery);
} function mediaBreakpointBetween(fromBreakpoint: string, toBreakpoint: string): MediaQueryList {
const breakpointCollection = getBreakpointCollectionFromStyleSheet();
const fromBreakpointValue = breakpointCollection.get(fromBreakpoint)!;
const toBreakpointValue = breakpointCollection.get(toBreakpoint)!;
return window.matchMedia(
`(min-width: ${fromBreakpointValue}) and (max-width: ${parseFloat(toBreakpointValue) - 0.02}px)`
);
}

没什么特别的, 就是 follow Scss 的写法改成 JS 而已

getBreakpointCollectionFromStyleSheet 函数

这个函数负责从 document.styleSheet 获取到 CSS variables

function getBreakpointCollectionFromStyleSheet(): Map<string, string> {
const breakpointCollection = new Map<string, string>();
const cssRules = Array.from(document.styleSheets).flatMap(sheet => Array.from(sheet.cssRules));
for (const cssRule of cssRules) {
if (cssRule instanceof CSSStyleRule && cssRule.selectorText === ':root') {
const keyStartsWith = '--breakpoint-';
for (const key of cssRule.style) {
if (!key.startsWith(keyStartsWith)) {
continue;
}
const value = cssRule.style.getPropertyValue(key).trim();
breakpointCollection.set(key.replace('--breakpoint-', ''), value);
}
}
}
return breakpointCollection;
}

注意, 这里用了许多潜规则, 也有一些隐患

1. 没有过滤 third party CSS (因为我用 Webpack 都会打包一块, 而且有时直接放 CDN 的 origin)

2. 没有考虑 CSSMediaRule, 因为 breakpoint 不可能会在 media query 里面修改

3. ‘--breakpoint-’ 是 Magic string

4. getBreakpointCollectionFromStyleSheet 每次都会遍历, 性能不太好, 应该缓存起来.

5. 没有监听 variable 的改变. 但 breakpoint 不太可能会 change 啦.

总结

如果想在 JS 和 CSS 间管理好 breakpoint 就可以采用以上的方案.

通过 Scss 定义 breakpoint, 然后放入 CSS Variables share 给 JS 用.

DOM – Work with Document.styleSheets and JS/Scss Breakpoint Media Query的更多相关文章

  1. document.styleSheets

    伪元素是不能选中的,如果非要改他的样式,两个方法. 静态方法: addClass的时候,新add的class带有新的伪元素. 动态方法: 如果知道它在document.styleSheets对象中的位 ...

  2. DOM对象本身也是一个js对象,所以严格来说,并不是操作这个对象慢,而是说操作了这个对象后,会触发一些浏览器行为(转)

    一直都听说DOM很慢,要尽量少的去操作DOM,于是就想进一步去探究下为什么大家都会这样说,在网上学习了一些资料,这边整理出来. 首先,DOM对象本身也是一个js对象,所以严格来说,并不是操作这个对象慢 ...

  3. 从原型链看DOM--Document类型

    JavaScript通过Document类型表示文档,原型链的继承关系为:document.__proto__->HTMLDocument.prototype->Document.prot ...

  4. JS函数动作分层结构详解及Document.getElementById 释义 js及cs数据类型区别 事件 函数 变量 script标签 var function

    html +css 静态页面 js     动态 交互   原理: js就是修改样式, 比如弹出一个对话框. 弹出的过程就是这个框由disable 变成display:enable. 又或者当鼠标指向 ...

  5. 第10章 文档对象模型DOM 10.2 Document类型

    Document 类型 JavaScript 通过 Document 类型表示文档.在浏览器中, document 对象是 HTMLDocument (继承自 Document 类型)的一个实例,表示 ...

  6. how to delete the virtual dom that created in memory using js

    how to delete the virtual dom that created in memory using js const virtualDomConvert = (filename = ...

  7. document.styleSheets[0]是个啥

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  8. Adding DOM elements to document

    1.JavaScript 添加DOM Element 执行效率比较: 抄自:http://wildbit.com/blog/2006/11/21/javascript-optimization-add ...

  9. 样式声明对象:document.styleSheets[0].rules[4].style;

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. DOM window的事件和方法; Rails查询语法query(查询结构继承); turbolinks的局限;

    window.innerHeight 是浏览器窗口可用的高度. window.outerHeight 是浏览器窗口最大的高度. 打开chrome-inspector,上下移动inspector,看到s ...

随机推荐

  1. C#-WPF初学

    1.新建一个WPF的应用: 2.拖拽控件并布局好: [小技巧]选中控件,点击"回形针"即可让该控件跟随窗口自动调整大小: 3.编写代码: 主程序代码如下: namespace Wp ...

  2. 基于 Impala 的高性能数仓建设实践之虚拟数仓

    导读:本文主要介绍网易数帆 NDH 在 Impala 上实现的虚拟数仓特性,包括资源分组.水平扩展.混合分组和分时复用等功能,可以灵活配置集群资源.均衡节点负载.提高查询并发,并充分利用节点资源. 接 ...

  3. CSS+JS 实现动态曲线进度条

    由于系统UI风格升级,产品童鞋和UI童鞋总是想要搞点儿事情出来,项目页面上的进度条从直线变成了曲线,哈哈,好吧,那就迎难而上 实现效果: 1.简单搞一搞 CSS , 此处代码有折叠 .process ...

  4. [oeasy]python0135_命名惯用法_name_convention

    命名惯用法 回忆上次内容 上次 了解了isidentifier的细节 关于 关键字 关于 下划线   如何查询 变量所指向的地址? id   如何查询 已有的各种变量? locals   如果 用一个 ...

  5. oeasy教您玩转vim - 12 - # 词头词尾

    词头词尾 回忆上节课内容 我们这次学了向前一个单词 w 意思是 word 还学习了向后一个单词 b 意思是 backward 这俩命令都落在单词的第一个字母 还有什么好玩的命令吗? 动手练习 我们可以 ...

  6. 智能家居如何把老款定频空调变成智能“变频”空调#米家#智能家居#HA

    背景 最近长沙的天气暴热,室内达到了34-35度,天气预报最高温度上了40度,这么酷热的天气,离开了空调,基本上就是一身汗,全身湿透,特别难受,然后不得不开启家里的一台将近10年的老式定频空调,输入功 ...

  7. hbuilder打包的应用上架appstore屏幕快照的生成方法

    当我们使用hbuiderX或apicloud这些打包工具进行打包的时候,我们需要将打包好的ipa文件进行上架,但是你会发现,我们上架的时候需要上传5.5寸的iphone.6.5寸的iphone X和两 ...

  8. mysql报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)

    mysql报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql ...

  9. ubuntu禁止内核自动更新

    ubuntu禁止内核自动更新 查看已安装内核dpkg --get-selections |grep linux-image 查看正在使用的内核uname -a 禁止内核更新sudo apt-mark ...

  10. Jmeter函数助手31-changeCase

    changeCase函数用于字符转换大小写. 字符串修改:填入需要转换的字符 更改案例模式UPPER(默认),LOWER,CAPITALIZE:不填默认UPPER,UPPER全部转换为大写,LOWER ...