CSS 漫谈,从一个 UserStyle 说起

最近用两天时间写了一个 UserStyle,感觉很适合用来作为一个引子谈一谈我对 CSS 的理解。你可以通过这个链接获得这份 UserStyle 文件,如果你的浏览器安装了 stylus 插件那么你会同时看到样式安装提示。

面向萌新的名词解释
CSS 指层叠样式表(Cascading Style Sheets),是前端三大件之一,目前绝大部分网页的外观都是通过 CSS 定义的
UserStyle 即在客户端加载的 CSS 文件,与更加有名的 UserScript (国内称油猴脚本)配合可以实现对第三方网页的高度自定义

UserStyle 涉及的法律问题
由于前端技术的特点所致,修改第三方页面的门槛相比于二进制可执行文件要简单得多,但这本质上与反编译修改二进制计算机软件没有太大区别,所以同样有触犯法律的风险。更详细的内容可以自行搜索广告屏蔽插件是否违法

进入正题

上文提到的 UserScript 目前的名称为 BiliBili Frosted,主要功能就是修改 bilibili.com 的页面头部外观和增加部分区域的毛玻璃效果

实现毛玻璃效果

[selector] {
    border-radius: 10px !important;
    backdrop-filter: blur(10px) saturate(1.8);
    background-color: rgba(255,255,255,.75) !important;
    box-shadow:rgba(0, 0, 0, 0.03) 0px 10px 20px 0px, rgba(0, 0, 0, 0.02) 0px 5px 10px 0px !important;
}

css真正实现毛玻璃效果依靠的是backdrop-filter这个属性,在此之前都只能使用辅助元素模拟毛玻璃效果,而在编写本文时这个样式仅有70%的浏览器覆盖率。

这里主要介绍一下前端开发经常会使用的网站 Can I Use,这是一个由社区维护的专门用来查询浏览器支持情况的网站,当你想使用一个比较新的功能时就可以在这里查询浏览器兼容性。通过上图可以看到 Chrome、Safari 和修改过选项的 Firefox 都可以支持这个样式,考虑到 Stylus 插件的浏览器支持情况,这个样式目前已经可以在 UserStyle 中使用了,但是对于前端项目来说还太早。

!important

brefore this topic { remember: "do not use" !important; /* in your project */ }

一般的 css 选择器会遵循一套较为直观的优先级规则,这时出现的!important规则可以说是一个十足的破坏者,!important规则将原有的优先级规则分为两层,拥有!important标识的属性之间根据选择器优先级规则生效,然后是没有!important标识的属性生效。考虑到 css 中一般不使用!important,这时出现的少量!important属性如果没有被合理安排的话会导致 css 属性优先级混乱难以维护。
在本文的代码中大量使用了!important标识,那是因为 UserStyle 作为一个非官方产物本身就具有较弱的可维护性,所以使用!important标识可以减少很多工作。

iframe

bilibili 顶栏有部分弹出菜单是使用 iframe 实现的,iframe 是一种在网页中嵌入网页的技术,或许你点击下面几个链接之后就能直观地理解了(注意用pc端):消息窗口动态窗口直播窗口游戏窗口小说窗口,如果你不知道这几个链接打开后是什么意思,那就点击这个链接然后把鼠标悬停在顶栏那几个按钮上。

@-moz-document domain("bilibili.com") {
    .van-popover.van-popper.popover-manga{
        background-color: unset !important;
    }
}
@-moz-document url("https://manga.bilibili.com/eden/bilibili-nav-panel.html") {/* iframe */
    body{
        background-color:transparent;
    }
    .app-layout{
        border-radius: 10px !important;
        backdrop-filter: blur(10px) saturate(1.8);
        background-color: rgba(255,255,255,.75) !important;
        box-shadow:rgba(0, 0, 0, 0.03) 0px 10px 20px 0px, rgba(0, 0, 0, 0.02) 0px 5px 10px 0px !important;
    }
}

这里你可能会有一个问题:如何让 iframe 透明?如果你在搜索引擎搜索的话会发现大部分结果都是几年前的方法,因为现在的 iframe 本身已经是透明的了,只是在 bilibili 的页面上为了避免不需要的透明设置了部分元素的背景色。
接下来的问题是,毛玻璃效果要在 iframe 内部实现还是外部实现?考虑到在游戏和漫画窗口有利用了透明背景的元素,所以我决定统一在 iframe 内部实现,从上面的代码可以看到,利用@-moz-document属性可以让插件根据页面 url 插入对应的样式,iframe 也不例外。其他的样式是用来保证从设置了毛玻璃的元素到设置了图片的元素之间的元素都透明。
由于插件的特性可以让我们将不同页面的样式文件写在同一个文件里,但如果在项目开发中使用 iframe,就需要真正当作两个页面来编写代码。

伪类和伪元素

伪类和伪元素是 css 选择器的一部分,伪类用于选择处于特定状态的元素,例如鼠标聚焦:hover、最后一个子元素:last-child,伪元素用于创建新的html元素而不改变html文件本身,例如::before,伪元素标准写法是两个冒号,但是一个也可以正常识别。

    .international-header:hover > div:last-child{
        transform: translateY(30px);
        transition: 0.3s ease 1s;
    }
    .flow-loader > div:first-child{
        width: calc(100% - 20px);
        padding: 76px 10px 10px 10px;
    }
    .search-loupe::before{
        content:"\E72C"
    }
    body::before{
        content: "";
        position: absolute;
        width: 100%;
        height: 300px;
        background-size: cover;
        background-repeat: no-repeat;
        background-position: center;
        background-image: url("example.jpg");
    }

在上面的例子中,第一个选择器使用:hover实现了当鼠标悬浮在这个区域时移动一个子元素,子元素使用:last-child和第二个选择器使用first-child则是因为元素没有标志性的类用于选择,但该元素的位置是固定的。
对于伪元素来说,可以想见设计之初的目的是第三个选择器的用法,但是现在最常见的反而是第四个选择器的用法。用 css 添加背景图或图形既不会在html中加入不必要的元素又可以减少图片的使用。关于伪类的用法还可以看一下这个网站使用css实现整个对话框的外观,试想如果使用图片实现会多耗费多少资源,还会出现放大模糊的问题。(与此相关的是,复杂图形的代码绘制可以使用svg)如果你想直观地感受的话就打开 BiliBili Frosted 的源文件然后滑动到结尾看一看那张被转换为 base64 文本的图片用了多少字符。
伪类和伪元素算不上什么深奥的知识,常用的也并不多,但是合理使用它们不仅会使 css 更加简洁,还会减少 html 和 js 的代码量。

评论区