分类: css

  • CSS 小教程:在网格型选择工具上添加渐变背景

    CSS 小教程:在网格型选择工具上添加渐变背景

    新年开始了,要努力,本博客开始 2024 年招商,欢迎各位想推广产品的老板投广告,目前定价 4800/年,亦可增加评测文章、教学文章配合,详情可与我联系。具体详情更新在 本站广告 2024 年招商


    好几个月之前,我遇到这样一个需求:

    这个东西叫作 Emotional Scale,是一大堆描述情绪的形容词,存在从积极到消极的渐进关系,老板希望我们用颜色给予它们比较明显的区分。于是我就想到这样一种表现方式:

    1. 用网格化来布局每个形容词
    2. 给它们添加从上至下的渐变背景
    3. 再让 ChatGPT 给每个形容词选一个 emoji

    最后一项不用多说,直接丢给 ChatGPT 就行。这里重点说下前面两个。

    网格化布局用 display:grid + grid-template-columns: repeat(N, minmax(0, 1fr)) 即可,其中 N 可以根据宽度调整成需要的列数。

    接下来处理渐变。这里的难点在于,每个小格子是独立的,要维持统一的渐变比较困难。我想到两个方式:

    1. 画一个大的渐变背景,然后用 clip-path
    2. 通过计算的方式给每个小格子加渐变背景

    后面我决定选择第二种方式。我总共有 38 个格子,一行 4 个的话,就是 10 行;有 6 个颜色,刚好每两行是一个过渡。于是一行就应该是 0 -> 50%,如何渐变一半呢?我尝试了一下,发现可以用 -100% 和 200% 来做。

    于是我就配合 Vue 开发了这个组件:

    <script lang="ts" setup>
    function getBgColor(index: number): string {
    const colors = ['#1B4397', '#648EF7', '#5DC264', '#FFAE43', '#F15146', '#b91c1c']; // 总共 6 个颜色
    const start = index / 8 >> 0; // 8个=2行,就是两行完成两个颜色的过渡
    const end = start + 1;
    const line2 = index % 8 >= 4;

      // 借助 CSS 变量完成过渡效果
    return `--tw-gradient-stops: ${colors[start]} ${line2 ? '-100%' : ''}, ${colors[end]} ${line2 ? '' : '200%'}`;
    }
    </script>

    <template lang="pug">
      .grid.grid-cols-4.gap-2
    label.flex.flex-col.justify-center.items-center.bg-base-100.rounded-3xl.w-full.aspect-square.cursor-pointer.bg-gradient-to-b(
    v-for="([label, face], index) in todayDoingsScale"
    class="hover:bg-base-200"
    :style="getBgColor(index)"
    :key="index"
    @click="doSelect"
    )
    input.hidden(
    type="radio"
    name="feel"
    :value="label"
    v-model="score"
    )
    </template>

    最终完成的效果就如同上面截图所示。

    CSS 作为前端开发的必备技能,搞起来别有一番乐趣。新特性不断被添加进来,就有越来越多的功能可以实现,有越来越多的功能可以更容易的实现。相信我们下一步的开发环境和产品体验会更好。

    如果各位读者对上面的 CSS 实现有什么问题,或者有其它关于 CSS 的问题,欢迎留言提问。

  • 值得关注的 CSS 新特性

    值得关注的 CSS 新特性

    CSS 作为前端的重要组成部分,虽然受瞩目的程度逊于 JS,但是也在不停地进步。CSS 可以让我们的开发环境更好,用户体验更佳,所以大家也需要保持关注。这里记录一些值得关注的新特性(评判标准由我主观决定),有些已经实装,可以取代旧样式,提供更好的效果;有些还没有普及,但是可以逐步应用到我们的产品当中,渐进式增强。

    overflow-wrap

    这个属性我还是通过 TailwindCSS 学到的。它可以用来处理文本换行,拥有 3 个可选值:

    1. overflow-wrap: normal 默认值,按照标准模式换行
    2. overflow-wrap: anywhere 可以在任意处换行
    3. overflow-wrap: break-word 尽量利用宽度,只在超宽时打破单词换行

    我们知道,中文一个字就是一个字,随时可以换行;而英文则把一个单词算作一个字,单词内部不能随意断开。但是有时候,容器宽度不够,文字就会撑破容器。以前我们只有 word-break: break-all,但是 word-break: break-all 会在单词的任意位置断开,甚至在不必要的时候断开;而 overflow-wrap: break-word 会尝试把单词放到独立一行,如果还是不行,那就再断开。所以它的效果会更好。推荐大家使用。

    Scoped CSS

    CSS 的优先级非常重要,平时常常用到,面试的时候也会常常问到。CSS 优先级分成若干层级:

    1. #id
    2. .class
    3. element
    4. [attr="value"]
    5. :pseudo-class

    这样的优先级规划在大部分场景下,都能正常工作,但是当我们需要使用公共仓库,或者开发公共仓库的时候,就会遇到困难。

    比如 Awesome Comment 项目,它是一个嵌入页面的评论框,用户可以把它嵌入到自己的网站当中,给自己的网站添加评论功能。我们使用 TailwindCSS + DaisyUI 作为样式库,直接使用一切都好,但是当我们要把它嵌入别的网页时,就可能跟目标页已有的样式产生各种冲突。于是,在 Scoped CSS 尚未普及的今天(主要是 Safari 不支持),我们就必须给所有样式加上 ac- 前缀,非常影响开发效率。还有一位同学,他想给现在使用 bootstrap 的项目里引入 TailwindCSS,也面临严重问题。

    如果有了 Scoped CSS,我们只需要这样:

    @scope (.awesome-comment) {
      .a { }
    }

    就可以非常轻松地让我们的样式只在 .awesome-coment 节点内生效,且盖过其它样式。可以大大提升我们集成别人的样式,或别人使用我们样式时的效率。相信以后这个功能会大大改善第三方框架的开发。

    更多更详细的用法,建议大家深入阅读:@scope – CSS: Cascading Style Sheets | MDN (mozilla.org)

    <textarea> 根据内容自动缩放

    这项目功能目前还在讨论之中:[css-ui] ? Allow <textarea> to be sized by contents. · Issue #7542 · w3c/csswg-drafts (github.com)。大概意思是,<textarea> 用途非常广泛,让它能够根据内容长度自动调整大小会非常有价值。不过,具体选用什么方案,目前还没有定论。

    我搜了一下,目前无论 form-sizing 还是 field-sizing 都还没有进入规范,也没有间接实现方案(比如 PostCSS),所以大家先保持关注吧。

    View Transition 视图切换动画

    View Transition 让我们的产品可以在页面切换时,复用一些页面元素,展现出漂亮的动画,让用户更加理解页面逻辑。如下面这则视频所示,当用户点击列表中的链接时,大标题、作者等信息会以动画形式,移动到页头位置;当用户回到列表页的时候,它们又会移回列表里的相应位置。

    使用 View Transition 的方法比较复杂,我暂时还没有 Demo,所以就不详细说明了,大家感兴趣的话,可以看下面两个链接:

    1. Getting started with View Transitions on multi-page apps | daverupert.com
    2. https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

    light-dark() 函数

    light-dark() 函数可以接受两个参数,分别作为 light 模式和 dark 模式下生效的属性。如以下代码所示:

    body {
      background-color: light-dark(white, black);
    }

    不过我觉得这个函数的作用不会很大,我还是喜欢集中定义和管理这些变量。

    color-mix() 函数

    相对来说,我觉得 color-mix() 函数的作用就大多了,它可以基于一个颜色,生成新的值。比如,我们先约定一批颜色,分别作为 primarysecondarysuccesserror 等,然后使用 color-mix 函数,生成一些衍生颜色,以便适用在 disabledactivehighlight 等场景下。

    使用方式也比较简单,它接受 3 个参数:混合模式,颜色1,颜色2。比如下面的代码,就是两种颜色各取一半,以 srgb 模式混合,得到新颜色。其中百分比也可以调整,比较好理解。

    /* 减淡,即混合白色 */
    color: color-mix(in srgb, var(--primary) 50%, white);
    /* 加深,即混合黑色 */
    background-color: color-mix(in srgb, var(--primary) 50%, black);

    有兴趣的同学可以深入了解:color-mix() – CSS: Cascading Style Sheets | MDN (mozilla.org)

    原生嵌套 CSS

    嵌套 CSS 相信大家在使用预处理工具的时候都会用到:

    .a {
      .b {
        .c { }
      }
    }

    因为确实有用,所以如今原生 CSS 也开始支持嵌套,可惜有些浏览器还没有支持。我建议大家先配合 TailwindCSS 使用,等到将来都支持了,再去掉 TailwindCSS 即可。

    export default {
      plugins: {
        'tailwindcss/nesting': {},
        tailwindcss: {}, // 不需要 TW 的话可以不要这行
        autoprefixer: {},
      },
    }

    详细规范请继续阅读:https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting

    CSS 容器查询

    我猜大部分同学都用过 @media query,以适配不同设备和分辨率。但我们现在已经是组件化开发的时代,很多时候,我们需要让自己的组件能够适配不同的使用场景,于是仅仅根据分辨率调整布局就不够了。万一被用在桌面显示器上,但是本身只占据一小块空间,那就无法保证用户体验。

    这个时候我们可以使用 CSS 容器查询(Container query),它的用法很简单,首先,假设我们的组件渲染出来的 HTML 是这样的:

    <div class="post">
      <div class="card">
        <h2>Card title</h2>
        <p>Card content</p>
      </div>
    </div>

    接着,我们要把目标容器标记为“容器控制上下文”(containment context)。未特意标记的容器不会产生容器查询,我猜这样可以减轻浏览器布局的计算负担。最后,使用 @container 查询,让容器在某个尺寸时,内部某些样式生效,即可:

    .post {
      container-type: size;
    }
    
    /* 默认样式 */
    .card h2 {
      font-size: 1em;
    }
    
    /* 仅在容器 >= 640px 时才生效的样式
    @container (min-width: 640px) {
      .card h2 {
        font-size: 2rem;
      }
    }

    容器查询还有一些进阶用法,大家可以深入阅读:CSS container queries – CSS: Cascading Style Sheets | MDN (mozilla.org)

    总结

    好的,先写这么多。感谢所有标准贡献者、开源软件开发者、社区参与者,我们 Web 开发者能收获现在的成就,跟所有贡献者的辛勤工作分不开。

    希望这些新特性能尽早进入实装,成为我们开发时的利器。

    如果你有什么想补充的,或者有问题想讨论一下,欢迎留下评论。

  • 2023 告别 CSS 预处理工具,彻底拥抱 TailwindCSS

    2023 告别 CSS 预处理工具,彻底拥抱 TailwindCSS

    CSS 是声明式语言,很简单,很好学,但是写起来很累,所有东西都要写出来才能生效。复用方面更是无从下手,虽然大家都在不断总结,但始终没能找到足够好用的方案,可以有效改善 CSS 开发。

    于是我们只好把视线转出 CSS 之外,转投 CSS 预处理工具,Less、SASS(SCSS)、Stylus,引入种种 CSS 不具备的功能,帮助我们改进开发体验。比如嵌套、函数、循环、条件,等等。然而如果你细心观察,实际上,这几个工具最近 5、6 年都没怎么更新(我说的是功能性),因为该有的都有了,甚至很稳定;其它来自于 CSS 的改进,几乎跟它们没什么关系,也不用更新。

    最近几年,随着 CSS 发展,一些新特性逐步引入,我觉得这些工具越来越难用,它们能带来的好处已经无法掩盖它们所造成的问题。是时候告别 CSS 预处理工具了,就像我们当年告别 jQuery 一样。

    为什么说预处理工具落后?

    我把理由分成三大类:

    预处理工具的问题

    • 对 CSS 函数兼容性不好,尤其是 rgba()hsl() 这些常用的颜色函数
    • 数值类型转换,有不符合预期的行为,比如 Stylus 实现 content:5

    CSS 的改进

    • CSS 拥有越来越多的函数,可以直接进行计算,比如前面提到的颜色;还有 calc() 来完成基础数学计算
    • CSS 变量非常好用,可以大大改进编程体验,配合各种 JS 框架,我们可以更容易的把数学逻辑和显示效果绑定在一起
    • CSS Houdini 可以实现新功能,即使不深入使用(JS 部分),也有好用的自定义属性
    • CSS 也开始从预处理工具吸收营养,比如近期的嵌套功能已经开始被整合,未来我们可以直接使用

    预处理工具无法跟进的问题

    • 很多缩写、复合属性无法处理,比如 background-imagebox-shadow 等,都支持多属性共同生效,预处理工具擅长的循环、条件、函数无法提供帮助。
    • 预处理,顾名思义,发生在生产之前。实际上,网页在实际浏览时,会有很多因素影响到渲染结果,比如分辨率、dark mode 等。预处理工具对这些需求也没有改进。

    替代方案

    我目前的替代方案基于 TailwindCSS,所以自然包含 PostCSS、AutoPrefixer 等工具。然后用 postcss-import 实现自动导入和模块化;使用 tailwindcss/nesting 实现嵌套。

    为什么选用 TailwindCSS?首先,实际开发中,不管使用什么前端框架,我们都需要大量原子化的胶水样式,比如调整间距、改变字号、给容器添加一些边框、圆角、阴影等。这些样式如果都手写,工作量并不小;学习不同的样式名也是负担;以及最重要的,CSS 优先级问题。使用 TailwindCSS 就都能很好解决。

    TailwindCSS 不仅包含一大堆原子化样式,自身也是个完整且优秀的 CSS 编译器。它包含 reset,提供一组全局通用的 CSS 变量;它可以从各种文件里把我们用到的样式提取出来,构建后生成的 CSS 里只有我们要用到样式,不会有多余的;它会分析我们对样式的使用,合理的调整样式顺序,保证样式能正确生效。使用 TailwindCSS 可以节省很多时间。

    它还自带若干插件,比如解决嵌套的 tailwindcss/nesting,支持内容类元素的的 @tailwindcss/typography 等。使用这些插件也可以帮我们节省很多时间。

    最后,TailwindCSS 的生态不断成长,我们的选择范围越来越宽:HeadlessUI、DaisyUI、付费的 Tailwind UI 等。方便我们从产品生命周期的任意阶段开始集成。

    推荐项目配置

    启动项目的时候,安装依赖。包含 PostCSS + AutoPrefixer、TailwindCSS 和 DaisyUI。前者提供 CSS 处理框架,包含自动导入 css 和嵌套功能;后两者提供可见的 UI。

    pnpm i postcss postcss-import tailwindcss autoprefixer daisyui -D

    自动初始化配置,-p 会自动生成 PostCSS 配置:

    pnpm tailwindcss init -p

    调整 postcss.config.js,启用 postcss-importtailwindcss/nesting。目前我们常用的嵌套规则和 CSS 规范略有区别,不过无所谓,规范也没确定,所以这样就足够了。

    module.exports = {
      plugins: {
        'postcss-import': {},
        'tailwindcss/nesting': {},
        tailwindcss: {},
        autoprefixer: {},
      },
    }

    然后调整 tailwind.config.js

    const DaisyUI = require('daisyui');
    // 这个插件可以帮我们处理文档类内容,我建议常用
    const Typography = require('@tailwindcss/typography');
    
    module.exports = {
      // 从以下文件查找用到的样式
      content: [
        './index.html',
        './src/**/*.{js,ts,jsx,tsx,vue}',
      ],
      theme: {
        extend: {
          // 扩充 TailwindCSS 没有包含的样式
        },
      },
      plugins: [
        DaisyUI,
        Typography,
      ],
      daisyui: {
        themes: [{
          // 只构建一个主题: luxury,并覆盖其中的两个属性
          luxury: {
            ...require('daisyui/src/colors/themes')[ '[data-theme=luxury]' ],
            primary: '#FFA028',
            '--bc': '0 0% 87.5%',
          },
        }],
      },
    }
    

    然后,创建样式入口 main.css。其它样式可以如常写在这个文件里,不过如果要 @import 其它 CSS 文件,就要进行一些调整。具体可以看官方文档。

    @tailwind base;
    @tailwind components;
    @tailwind utilities;

    然后在入口文件引用 main.css 即可:

    import './main.css';

    至此,新项目配置完成,可以照常开发了。

    下期预告

    这次我先分享了整体思路:用新的工具链替代预处理工具,保证已有的功能不缺失。那么下期分享的内容就是使用新的 CSS 特性,更好的完成开发。


    如果你对新 CSS 感兴趣,对预处理工具和新工具链有兴趣和疑问,欢迎留言讨论。如果本文对你有启发,也请帮我点赞分享,谢谢。


    本文参与了 SegmentFault 思否写作挑战赛,欢迎正在阅读的你也加入。

  • 【视频】如何正确使用 TailwindCSS

    【视频】如何正确使用 TailwindCSS

    TailwindCSS 是一个争议很大的样式库。

    他封装了大量原子化的样式,比如 w-4,表示 width: 1remtext-gray-500,表示字体颜色为灰色500。如果我们某个节点同时需要两个样式,就是 class="w-4 text-gray-500"。极端一点的例子是这样子的:

    很明显,大家的争议点在于:

    1. 这么细碎的样式,我为什么不自己写?灵活性还高一些。
    2. 这么写跟 inline style 有什么区别?
    3. 开发一时爽,维护火葬场。

    基本上,发出这些疑问的都是前端。包括我,最初也是这样的想法。但是有一天,我要维护一个老项目,大部分组件都是现成的——引用自某个组件库,或者团队已经写好,只需要调整布局,我发现 TailwindCSS 简直是神器,太方便了。

    于是,当我反复看到大家争论该不该用 TailwindCSS 后,决定做一期视频,表达一下自己的态度:

    1. 我们做技术,要避免对一项技术做非黑即白的判断,更不应该轻易否定一项技术。同时,使用技术 A 并不代表就要拒绝技术 B。
    2. 具体到 TailwindCSS 上,使用它不代表我们从头到尾就要只能用 TailwindCSS;使用其它前端框架也不要求我们绝对不能使用 TailwindCSS。
    3. 所以正确的做法是,我们应该使用 TailwindCSS。
    4. 项目启动时,使用比较完整全面的前端框架,比如 Element UI、AntDesign,或者基于 TailwindCSS 打造的 DaisyUI;然后辅以 TailwindCSS。这样就可以同时照顾开发效率与维护效率,收获最佳效果。

    除了并不会降低开发效率之外,TailwindCSS 还有以下优势:

    1. 它跟内联样式有很大的区别,它的优先级很低,意味着我们也可以很容易覆盖、调整。
    2. TailwindCSS 样子很好看,直接能画出漂亮的界面。
    3. 基于 TailwindCSS 的代码分享很容易,只要复制粘贴 html 即可,在前端工程化日趋复杂的今天,简直是一股清流。
    4. 因为文档组织得更好,后端和其它领域的开发者也很喜欢使用 TailwindCSS 替代手写样式。

    所以无论如何,我都推荐所有团队所有开发者使用 TailwindCSS。当然,用其它原子化样式框架,比如 UnoCSS 也可以。

    如果你有其它意见和建议,欢迎提出讨论。如果你有 B 站账号,恳请三连+关注+转发,感谢。

  • 纯 CSS 实现优惠券效果

    纯 CSS 实现优惠券效果

    (本文不是广告,因为没给钱。)我厂 code.fun 上线了付费购买与优惠券功能,欢迎各位新老顾客莅临。

    上面是优惠券的视觉效果,本文分享如何使用纯 CSS 实现它,希望对大家有帮助。

    0. 分析

    首先,我们来分析一下这个优惠券的实现方案。

    左边是摘要,右边是详情,这个部分用 display:flex 很容易就能搞定。中间的虚线,使用任意一个容器边框 + 少许 padding 即可实现。其它部分,也就是些字体行高,都不是很复杂。难点在于投影,尤其是左右两个挖空的半圆。

    1. 挖空

    1. 首先,我们给整个优惠券矩形加上投影
    2. 然后,我们给两边加上两个包含内投影的圆形
    3. 这个时候,两边的内投影原型会多出来一块,我们需要把它们盖住。但是,不能让矩形 overflow:hidden,因为投影也会变,如下图。
    .coupon {
      width: 15rem;
      height: 6rem;
      background: white;
      box-shadow: 1px 1px 6px rgba(0,0,0,.15);
      position: relative;
      overflow: hidden;
      
      &::before,
      &::after {
        background-color: white; 
        border-radius: 1rem;
        box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.15);
        content: '';
        width: 2rem;
        height: 2rem;
        position: absolute;
        top: 2rem;
        z-index: 1;
      }  
    
      &::before {
        left: -1rem;
      }
      &::after {
        right: -1rem;
      }
    }

    2. 增加父容器

    我的第一反应是增加父容器,让父容器 overflow:hidden 来隐藏多出来的部分。但是不行,会影响投影。

    但是转念一想,我们可以不让父容器限制显示内容,而是在父容器内部增加一些元素,遮蔽多出来的内容。比如用两个纯色圆形,把竖着的阴影遮起来。

    于是我把上面的样式更名为 .coupon-inner,然后增加一个父容器。父容器的 ::before ::after 伪元素都搞成略小一圈的纯色圆形,把竖着的投影挡住,最终效果如下图。

    3. 总结

    到这里,效果就基本让人满意了。

    最终完成的代码可以在 codepen 里看到:

    https://codepen.io/meathill/embed/oNqxOXE?default-tab=html%2Cresult&editable=true

    使用纯 CSS 的好处,在于体积小、加载快,调整起来非常灵活,能用 CSS 最好都用 CSS。

    有任何问题和建议,欢迎评论、讨论。

  • 代码分享:翻转小动画

    代码分享:翻转小动画

    朋友对我图省事用的 animate.css flipInX 效果不满意,软磨硬泡非要我改成 wordle 原版那种整个翻过来的。于是我就想办法实现了一把,比想象的稍微复杂一些,难点在于我不知道 transform-style: preserve-3d 这个属性。没有这个属性,正反两个图层就被压在一个平面里,怎么翻转都是上面那个图层显示出来。

    我用来调试的代码实现请点 这里,为了方便调试,我这次没用 codepen,用的 Vue SFC Playground,效果挺好,尤其是适合不熟悉属性时一边试一边写。不知道 codepen 是否支持这种玩法。

    实现的代码与之类似:

    <template lang="pug">
    .game-item
      .face G
      .face G
    </template>
    
    <style lang="stylus">
    .game-item
      aspect-ratio 1
      position relative
      // 这个样式很重要,没有它就没有背面一说
      transform-style: preserve-3d;
      user-select none
    
    .face
      position absolute
      top 0
      left 0
      width 100%
      height 100%
      display flex
      justify-content center
      align-items center
    
      &:first-child
        // 把第一层稍微提高一点点,不能太多,不然旋转效果不好看
        transform translateZ(0.1px)
    
      &:last-child
        // 背面提前翻转 180 度,这样转过来才是对的
        transform rotateX(180deg)
    
    @keyframes flip
      from
        transform: perspective(400px) rotate3d(1, 0, 0, 0)
    
      to
        transform: perspective(400px) rotate3d(1, 0, 0, 180deg)
    
    .flip
      animation-name: flip
    </style>

    这种效果平时还是找时间写上一两次,CSS 属性是知识性内容,没办法凭经验推出来,平时得注意积累。

  • 正确使用 height: 100% 和 flex: 1

    正确使用 height: 100% 和 flex: 1

    HTML+CSS 实现页面布局的时候,盒模型是很重要的概念。早期没有明确布局概念的时候,HTML 元素主要分两大类:行(inline)元素与块(block)元素。默认情况下,块元素会占据父元素的一整行矩形区域,宽度是 100%,高度由内容决定。如果我们希望子元素跟父元素一样高,可以设置 height:100%

    接下来我们有了弹性盒模型, display: flex。弹性盒模型是一种主动布局,即我们先决定怎么布局,浏览器则负责填充内容和渲染。所以直觉上,我以为:

    1. 父元素 height: 100%; display: flex; flex-direction: column
    2. 某个子元素 flex: 1
    3. 子元素的子元素 height: 100%,应该能自动填充剩下的高度
    4. 如果子子元素同时 overflow: auto,那么应该可以自动出滚动条。

    结果不行。

    问题一

    如上图,我厂的 Showman 产品。它的高度自适应屏幕高度,顶部通栏、导航、动作按钮栏高度固定,编辑器和日志输出窗口填满剩下的空间。我希望用户可以调节编辑器和日志输出窗口的比例,以适应开发与调试不同的场景。于是保存日志输出窗口的高度,编辑器的高度自适应(flex:1)。

    因为 Vue2 组件要求有唯一的根元素,且整个应用有多个不同的路由,所以编辑器和日志输出窗口只能作为子子元素存在,大概的结构是这样的:

    <div id="app">
      <header id="main-nav">
      <!-- <router-view> -->
      <div id="main-body">
        <header id="second-nav">....</header>
        <div id="action-bar">....</div>
        <div class="editor-output">
          <div id="editor">....</div>
          <div class="drag-splitter"></div>
          <div id="output">....</div>
        </div>
      </div>
    </div>

    CSS 大概如此:

    html, body, #app {
      height: 100%;
    }
    #app {
      display: flex;
      flex-direction: column;
    }
    #main-nav, #second-nav, #action-bar {
      height: 40px;
    }
    #main-body, .editor-output {
      flex: 1;
      display: flex;
      flex-direction: column;
    }
    #editor {
      flex: 1;
    }
    #output {
      height: 100px;
    }

    这样的结果是,执行时,大量日志输出,就把界面顶开了。而不是预期中那样,出现滚动条,多余的部分被隐藏。

    经过一番 google,发现问题在高度计算。虽然定义了 height: 100%display: flex,但是浏览器在计算高度的时候,并不会从外往里一层一层算,而是按照规范:

    百分比
    指定一个百分比的高度。这个百分比是相对于父元素的盒子的高度计算的。如果父元素没有明确指定高度,并且该元素不是绝对定位的,该值将计算为 “自动”。

    auto
    由其它属性决定。

    于是因为上图中的界面存在嵌套关系,所以在需要计算高度的时候,子元素的高度虽然应该是 100%,但是父元素并没有被明确指定,所以就变成了 auto,继而被子元素撑开。解决方案就是沿着你需要 height: 100% 的元素往上,添加明确的 height,可以是百分比,也可以是绝对数值。

    于是,我在 #main-body 上添加 height: calc(100% - 49px),问题解决。

    😓 等下,不是每级都要加么?为啥只加一个具体高度就可以了?这个问题,我还要再研究一下。目前猜测,因为这个元素是竖直方向排列的(flex-direction: column)。

    问题二

    后来,在编辑器和日志输出窗口的右侧,增加了资源缩略图侧边栏。于是又遇到第二个问题:我以为 #main-nav 的高度确定,那么作为 display:flex,默认 align-items: stretch,它的子元素的高度应该都等于它的高度。所以给子元素设置 overflow: auto 就应该可以限制高度,出现滚动条。结果又失败了。

    然后我想起来 BFC。虽然直觉上 BFC 应该跟 display:flex 应该没什么关系,不过因为测试起来比较简单,可以先试试。

    于是我就在 div.d-flex 上添加了 .overflow-hidden 样式,果然问题就解决了。因为没找到明确的文档解释,所以我只能猜测:

    1. 类似 BFC 的逻辑在 display:flex 元素上依然存在。
    2. 父元素 display:flex;flex:N,根据上下文它应该有个确定的高度
    3. 但如果子元素高度超过它的高度,默认会撑开
    4. 如果父元素 overflow: hidden,会触发某个 xFC,于是整体高度就被限制了
    5. 于是子元素的滚动条就出来了

    总结

    行文至此,其实两个问题我都没找到具体的文档或者规范,只能说是摸索着解决了,然后再自己猜测原理。希望日后能找到具体的解释和规范吧。(要不要去翻翻张鑫旭的《CSS 世界》呢,都送完了,还得再买……)


    参考阅读:

  • 重发老视频:使用 CSS 制作工序流程图

    整理之前录的视频,发现一个漏掉没有上传的:

    这个视频里,我演示了如何使用纯 HTML + CSS 制作工序流程图。涉及到的技术包括:

    1. display:flex Flex 布局
    2. 使用 order: N 调整显示顺序,以实现响应式
    3. 使用 position:XX 调整定位

    虽然项目不大,不过大部分布局相关的技术都有所涉及,很适合刚入门和初级同学学习。


    今年想继续在直播、视频方面发力,希望大家支持。如果有什么想听的想看的想学的,也欢迎点菜。

  • CSS 网页保持全屏并自动伸长

    CSS 网页保持全屏并自动伸长

    其实是个小需求,以前也搞过很多次,没想到前几天被坑了一下,记一笔。

    以前,如果想要页面在内容任意多的时候都能占满浏览器,可以简单设置:

    html,
    body {
      height: 100%;
    }

    但是这样设置,在 Safari 浏览器上,会将 <body> 固定为窗口高度,如果内容多,就会被底部挡住内容。解决方案是 body 高度用 min-height:100%

    如果是三行一列的结构,即上面是导航条,下面是脚部,中间随内容自适应,可以用:

    body {
      display: flex;
      flex-direction: column;
    }

    这个时候,不能用 flex-basis,在 Safari 上会失去弹性,也要用 min-height。所以,最终样式大概就是:

    html {
      height: 100%;
    }
    
    body {
      min-height: 100%;
      display: flex;
      flex-direction: column;
    }
    
    #nav {
      height: 4rem;
    }
    #bottom {
      height: 10rem;
    }
    #content {
      flex-grow: 1;
      min-height: 40rem;
    }

    另外,因为基本只有桌面浏览器需要这个功能,所以可以考虑加一个 @media (min-width: 576px) 做限制。

  • 诡异的 `height: intrinsic`

    诡异的 `height: intrinsic`

    我厂最新也是最重要的产品 OpenResty XRay 即将开始邀请测试,所以官网上自然要添加对应的网页。目前该网页已经部署到生产环境,大家可以访问 https://openresty.com.cn/cn/xray/ 简单了解一下。

    这个页面的最下面,“信任与合规”区块是一些标准化组织的认证,按照需求应该放几个 logo。然后我就很自然的用 display: flex 来做了。在桌面浏览器显示正常。

    但是在 iPhone Safari 上,上面的两个图标会变得瘦长,看起来是高度计算有问题。我尝试修复这个问题,却除了写明高度,只有 height: intrinsic 可以让它显示正常。去 MDN 一搜,竟然没有这个属性?!只提到 max-contentmin-content 两种“intrinsic”的属性。

    caniuse 上,可以看到 intrinsic 是个非标准化的属性,应该是以前浏览器自发实现过,后来被 max-contentmin-content 取代。但是为何 Safari 明明支持这几个属性,但是只有 height: intrinsic 能显示正常,我就不知道了,也没有查到。

    先记一下吧,将来再看。如果有同学遇到类似的,图片在 display:flex 横排时尺寸出现问题,可以试试这个。