运行时快速且脏的 Bootstrap 覆盖

Avatar of Meredith Matthews
Meredith Matthews

DigitalOcean 为您旅程的每个阶段提供云产品。从 $200 免费信用额度开始!

Bootstrap 5.2 附带了针对本文中介绍的方法的解决方案。因此,如果您使用的是 Bootstrap 5.2 或更高版本,您应该能够开箱即用地使用 CSS 变量进行自定义覆盖。

哦,Bootstrap,那个古老的标准 Web 库,要么你讨厌它,要么你花所有时间为它辩护为“还不错,没有那么糟糕”。无论您站在哪一边,它都是一个强大的 UI 框架,无处不在,大多数人都知道它的基本知识,并且它可以为您提供极度可预测的结果。

无论好坏,Bootstrap 都有自己的主张。它希望您以某种方式构建 HTML,它希望您以某种方式覆盖样式,它希望以某种方式从核心文件构建,并且它希望以某种方式包含在网站中。大多数情况下,除非你有一个写 Bootstrap 代码很糟糕的同事,否则这很好,但它并不涵盖所有用例。

Bootstrap 希望在服务器端生成,并且不喜欢在运行时覆盖其样式。如果您处于需要在应用程序中使用某种视觉主题功能的情况,Bootstrap 希望您做的是为每个主题生成单独的样式表,并在需要时替换样式表。如果您有预定义的主题供用户使用,这是一种很好的方法。但是,如果您想要用户定义的主题怎么办?您可以设置您的应用程序以运行 Sass 并编译新的样式表并将它们保存到服务器,但这需要大量工作——此外,您还需要与后端人员和 DevOps 交谈,如果您只是想这样,这是一个很大的麻烦例如,只交换主色和辅助色。

所以这就是我的位置。

我正在使用 Django 和 Vue 构建一个多用户 SaaS 应用程序,它具有固定布局,但也需要能够更改每个用户帐户的品牌颜色,并使用自动默认颜色主题。还有一个要求是我们不会在每次添加新用户时重新部署应用程序。最后,每个后端和 DevOps 开发人员目前都忙于其他项目,因此我必须自己解决这个问题。

由于我确实不想在运行时编译 Sass,我只能创建样式表并将它们注入页面,但这是一个糟糕的解决方案,因为我们专注于颜色。编译后的 Bootstrap 样式表将颜色值呈现为显式十六进制值,并且(我刚检查过)我的样式表中有 23 个不同的主蓝色实例。我需要覆盖每个实例只是为了主色,然后再次对辅助色、警告色、危险色以及我们想要更改的所有其他约定和颜色标准化进行操作。这很复杂,而且工作量很大。我不想那样做。

幸运的是,这个新的应用程序没有要求支持 Internet Explorer 11,这意味着我可以使用 CSS 变量。它们也很好,并且可以在加载样式表后定义,朝各个方向流动并更改我想要的所有颜色,对吧?Bootstrap 在 :root 元素中生成该大型变量列表,因此这应该很简单。

这时我了解到 Bootstrap 只将它的一些值在样式表中呈现为变量,并且该变量列表完全是为了最终用户使用而设计的。该列表中的大多数变量都没有在样式表的其余部分中引用,因此重新定义它们没有任何作用。(但是,值得注意的是,将来可能会提供更好的运行时变量支持 可能会在将来出现。)

所以,我想要的是我的 Bootstrap 样式表以 CSS 变量呈现,我可以通过服务器端而不是静态颜色值来操作这些变量,严格来说,这是不可能的。如果您将颜色变量设置为 CSS 变量,Sass 将无法编译。有几个聪明的技巧可以让 Sass 做到这一点(这里有一个,还有 另一个),但它们需要分支 Bootstrap,并且远离升级路径会为我的应用程序带来一些我不想添加的脆弱性。老实说,我未实现这些解决方案的真正原因是我不知道如何让它们中的任何一个在我的 Sass 编译器中工作。但是您可能运气更好。

我认为在这里值得解释一下我喜欢的流程。我更喜欢在我的开发机器上本地运行 Sass 来构建样式表并将编译后的样式表提交到存储库。最佳实践建议应该在部署期间编译样式表,这是正确的,但我为一家不断发展、人手不足的初创公司工作。我使用 Sass 是因为我喜欢它,但很明显,在我的工作中这是一个主题,我没有时间、权力或精神力量将我的 Sass 构建集成到我们的各种部署管道中。

这也是一种合法的邪恶的自我防卫:我不想让我们的全栈开发人员接触到我精心制作的样式,并开始写他们想写的任何内容;而且我发现,出于某种原因,他们在笔记本电脑上安装 Node 时遇到了很大的麻烦。唉!他们只能求我做,而这正是我想要的。

所有这一切都意味着:如果我无法让样式表以其中的变量呈现,那么没有什么能阻止我在样式表编译后将其注入样式表。

看看查找和替换的强大功能!

我们要做的是进入 Bootstrap 并找到我们想要替换的颜色,这些颜色很方便地位于编译后的样式表的开头 :root 样式中

:root {
  --bs-blue: #002E6D;
  --bs-indigo: #6610F2;
  --bs-purple: #6F42C1;
  --bs-pink: #E83E8C;
  --bs-red: #DC3545;
  --bs-orange: #F2581C;
  --bs-yellow: #FFC107;
  --bs-green: #28A745;
  --bs-teal: #0C717A;
  --bs-cyan: #007DBC;
  --bs-white: #fff;
  --bs-gray: #6c757d;
  --bs-gray-dark: #343a40;
  --bs-gray-100: #f8f9fa;
  --bs-gray-200: #e9ecef;
  --bs-gray-300: #dee2e6;
  --bs-gray-400: #ced4da;
  --bs-gray-500: #adb5bd;
  --bs-gray-600: #6c757d;
  --bs-gray-700: #495057;
  --bs-gray-800: #343a40;
  --bs-gray-900: #212529;
  --bs-primary: #002E6D;
  --bs-brand: #DC3545;
  --bs-secondary: #495057;
  --bs-success: #28A745;
  --bs-danger: #DC3545;
  --bs-warning: #FFC107;
  --bs-info: #007DBC;
  --bs-light: #fff;
  --bs-dark: #212529;
  --bs-background-color: #e9ecef;
  --bs-bg-light: #f8f9fa;
  --bs-primary-rgb: 13, 110, 253;
  --bs-secondary-rgb: 108, 117, 125;
  --bs-success-rgb: 25, 135, 84;
  --bs-info-rgb: 13, 202, 240;
  --bs-warning-rgb: 255, 193, 7;
  --bs-danger-rgb: 220, 53, 69;
  --bs-light-rgb: 248, 249, 250;
  --bs-dark-rgb: 33, 37, 41;
  --bs-white-rgb: 255, 255, 255;
  --bs-black-rgb: 0, 0, 0;
  --bs-body-rgb: 33, 37, 41;
  --bs-font-sans-serif: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, Liberation Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
  --bs-body-font-family: Source Sans Pro;
  --bs-body-font-size: 1rem;
  --bs-body-font-weight: 400;
  --bs-body-line-height: 1.5;
  --bs-body-color: #212529;
  --bs-body-bg: #e9ecef;
}

获取 --bs-primary 的值,也就是老式的 Bootstrap 蓝色。我使用 Gulp 来编译我的样式表,因此让我们看看 gulpfile.js 中的 Sass 任务函数

var gulp = require('gulp');
var sass = require('gulp-sass')(require('sass'));
var sourcemaps = require('gulp-sourcemaps');

function sassCompile() {
  return gulp.src('static/sass/project.scss')
  .pipe(sourcemaps.init())
  .pipe(sass({outputStyle: 'expanded'}))
  .pipe(sourcemaps.write('.'))
  .pipe(gulp.dest('/static/css/'));
}
exports.sass = sassCompile;

我想在我的整个样式表中使用 CSS 变量复制并替换这种颜色,因此我安装了 gulp-replace 来做到这一点。我们希望我们的查找和替换在流程结束时发生,在样式表编译后但保存之前。这意味着我们应该在序列的末尾添加管道,如下所示

var gulp = require('gulp');
var sass = require('gulp-sass')(require('sass'));
var sourcemaps = require('gulp-sourcemaps');
var gulpreplace = require('gulp-replace');
function sassCompile() {
  return gulp.src('static/sass/project.scss')
    .pipe(sourcemaps.init())
    .pipe(sass({outputStyle: 'expanded'}))
    .pipe(sourcemaps.write('.'))
    .pipe(gulpreplace(/#002E6D/ig, 'var(--ct-primary)'))
    .pipe(gulp.dest('static/css/'));
}
exports.sass = sassCompile; 

编译样式表,然后查看。

:root {
  --bs-blue: var(--ct-primary);
  --bs-indigo: #6610F2;
  --bs-purple: #6F42C1;
  --bs-pink: #E83E8C;
  --bs-red: #DC3545;
  --bs-orange: #F2581C;
  --bs-yellow: #FFC107;
  --bs-green: #28A745;
  --bs-teal: #0C717A;
  --bs-cyan: #007DBC;
  --bs-white: #fff;
  --bs-gray: #6c757d;
  --bs-gray-dark: #343a40;
  --bs-gray-100: #f8f9fa;
  --bs-gray-200: #e9ecef;
  --bs-gray-300: #dee2e6;
  --bs-gray-400: #ced4da;
  --bs-gray-500: #adb5bd;
  --bs-gray-600: #6c757d;
  --bs-gray-700: #495057;
  --bs-gray-800: #343a40;
  --bs-gray-900: #212529;
  --bs-primary: var(--ct-primary);
  --bs-brand: #DC3545;
  --bs-secondary: #495057;
  --bs-success: #28A745;
  --bs-danger: #DC3545;
  --bs-warning: #FFC107;
  --bs-info: #007DBC;
  --bs-light: #fff;
  --bs-dark: #212529;
  --bs-background-color: #e9ecef;
  --bs-bg-light: #f8f9fa;
  --bs-primary-rgb: 13, 110, 253;
  --bs-secondary-rgb: 108, 117, 125;
  --bs-success-rgb: 25, 135, 84;
  --bs-info-rgb: 13, 202, 240;
  --bs-warning-rgb: 255, 193, 7;
  --bs-danger-rgb: 220, 53, 69;
  --bs-light-rgb: 248, 249, 250;
  --bs-dark-rgb: 33, 37, 41;
  --bs-white-rgb: 255, 255, 255;
  --bs-black-rgb: 0, 0, 0;
  --bs-body-rgb: 33, 37, 41;
  --bs-font-sans-serif: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, Liberation Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
  --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
  --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
  --bs-body-font-family: Source Sans Pro;
  --bs-body-font-size: 1rem;
  --bs-body-font-weight: 400;
  --bs-body-line-height: 1.5;
  --bs-body-color: #212529;
  --bs-body-bg: #e9ecef;
}

酷,好吧,现在我们有一个完整的样式表,它需要一个变量值来表示蓝色。请注意,它更改了主色和“蓝色”颜色。这不是一种微妙的技术。我之所以称之为快速且脏,是有原因的,但如果您需要更细粒度的颜色替换控制,则很容易实现。例如,如果您想将“蓝色”和“主色”保留为单独的值,请进入您的 Sass 并将 $blue$primary Sass 变量重新定义为不同的值,然后您可以根据需要单独查找和替换它们。

接下来,我们需要在应用程序中定义新的默认变量值。在 HTML 标题中这样做很简单

<link href="/static/css/project.css" rel="stylesheet">
<style>
  :root {
    --ct-primary: #002E6D;
  }
</style>

运行它,所有内容都会显示出来。所有需要为蓝色的东西都是蓝色的。重复此过程几次,您突然对 Bootstrap 样式表中的颜色有了很大的控制权。这些是我选择提供给用户的变量,以及它们的默认颜色值

--ct-primary: #002E6D;
--ct-primary-hover: #00275d;
--ct-secondary: #495057;
--ct-secondary-hover: #3e444a;
--ct-success: #28A745;
--ct-success-hover: #48b461;
--ct-danger: #DC3545;
--ct-danger-hover: #bb2d3b;
--ct-warning: #FFC107;
--ct-warning-hover: #ffca2c;
--ct-info: #007DBC;
--ct-info-hover: #006aa0;
--ct-dark: #212529;
--ct-background-color: #e9ecef;
--ct-bg-light: #f8f9fa;
--bs-primary-rgb: 0, 46, 109;
--bs-secondary-rgb: 73, 80, 87;
--bs-success-rgb: 40, 167, 69;
--bs-info-rgb: 0, 125, 188;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-rgb: 33, 37, 41;

现在乐趣开始了!从这里开始,您可以根据需要直接操作这些默认值,或者在默认值下方添加第二个 :root 样式以仅覆盖您想要的颜色。或者像我一样,在用户配置文件中放置一个文本字段,该字段将 :root 样式输出到您的标题中,覆盖您需要的任何内容。,您现在可以在不重新编译样式表或发疯的情况下在运行时覆盖 Bootstrap。

当然,这不是一个优雅的解决方案,但它解决了开发人员多年来一直在努力解决的一个非常具体的用例。在 Bootstrap 决定让我们在运行时轻松覆盖变量之前,这对我来说一直是一个非常有效的解决方案。