标签: css variables

  • 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 的问题,欢迎留言提问。

  • 小教程:React 创建支持切换效果的导航组件

    小教程:React 创建支持切换效果的导航组件

    需求

    需求不复杂,如上图所示:

    1. 导航栏,里面有若干个链接按钮
    2. 点击切换的时候,会有一个高亮标记随着选中的按钮移动
    3. 按钮尺寸不确定,高亮标记的尺寸随着选中的按钮变化

    选择技术方案

    我的第一想法是 CSS 纯原生,这样使用起来会非常简单。但是考察之后,发现不太可行,难点在于:

    1. 高亮标记如果跟着按钮走,无法处理按钮间切换
    2. 如果放在父容器里,可以在按钮间切换,但是尺寸没法跟着按钮走

    所以短暂思考之后,我只能退而求其次,选择和框架结合的开发方式:高亮标记使用绝对定位来控制位置;当用户切换导航(点击按钮)的时候,使用 JS 获取目标按钮的位置和尺寸,并且赋给高亮标记;使用 CSS 变量控制动画,以提升代码的复用性,确保 CSS 规则不要太死硬。

    这个过程并不复杂,我们可以利用事件冒泡机制,获取被点击的按钮,然后利用 getBoundingClientRect() 计算坐标。

    CSS

    CSS 部分比较简单,我们用一个伪元素作为高亮标记,然后配合 CSS 变量移动它即可。唯一需要注意的是,伪元素要跟按钮重合,所以必须用到 z-index,按钮也必须手动设置成 position: relative

    (WP 代码高亮插件挂了,大家将就看……)

    .slide-navigator {
    --highlight-x: 0; /* 标记的 x 坐标 */
    --highlight-width: 0; /* 标记的宽度 */
    position: relative;

    &::before {
    content: '';
    position: absolute;
    z-index: 0;
    bottom: 0;
    left: var(--highlight-x);
    width: var(--highlight-width);
    background: #ffd850;
    transition: all 0.2s;
    }

    > * {
    position relative;
    }
    }

    这里暂时没考虑响应式,如果要切换横纵,可以增加一个 Y 坐标。如果需要定制样式,可以将高亮标记的样式抽出来单独处理。

    React JSX 组件

    作为组件,导航里面的按钮要跟着业务逻辑走,最好在业务组件里生成,然后通过 props.children 传进来。点击事件可以直接交由父容器,也就是本组件侦听,然后利用 event.target 来判断选中的是哪个按钮。

    这里还有一个抉择,即怎么确定高亮按钮是哪个。我考虑了一下,觉得将这个逻辑一并放在业务组件里比较好,然后通过 props.currentIndex 传进来使用。这样能确保本组件的复用性。坏处就是每个用到这个组件的地方都要写一个函数来判断,略嫌麻烦。

    最终组件代码如下:

    (WP 代码高亮插件挂了,大家将就看……)

    import {
    CSSProperties,
    FC,
    MouseEventHandler,
    useEffect,
    useRef,
    useState
    } from 'react';

    interface SlideHighlightProps {
    children: React.ReactNode;
    className: string;
    currentIndex: number;
    }

    type SlideNavigatorHighlight = CSSProperties & {
    '--highlight-x'?: string;
    '--highlight-width'?: string;
    };

    const SlideHighlight: FC<SlideHighlightProps> = function (props) {
    const { className, children, currentIndex } = props;
    const root = useRef<HTMLDivElement>(null);
    const [navStyle, setNavStyle] = useState<SlideNavigatorHighlight>();

    /* 处理按钮点击事件,找到被点击的按钮,并将其位置和宽度赋给高亮标记 */
    const onClick: MouseEventHandler<HTMLDivElement> = (event) => {
    if (!root.current) return;

    const target = Array.from(root.current.children).find((v) =>
    v.contains(event.target as Node)
    ) as HTMLElement;
    const { left } = root.current.getBoundingClientRect();
    const { left: l, width } = target.getBoundingClientRect();
    setNavStyle({
    '--highlight-x': `${l - left}px`,
    '--highlight-width': `${width}px`
    });
    };

    /* 组件初始化时,或者导航切换时,改变高亮标记的位置 */
    useEffect(() => {
    if (!root.current) return;

    /* 如果导航不应该高亮,就把标记隐藏起来 */
    if (currentIndex === -1) {
    setNavStyle({
    '--highlight-width': `0px`
    });
    return;
    }

    const { left } = root.current.getBoundingClientRect();
    const target = root.current.children[currentIndex] as HTMLElement;
    const { left: l, width } = target.getBoundingClientRect();
    setNavStyle({
    '--highlight-x': `${l - left}px`,
    '--highlight-width': `${width}px`
    });
    }, [currentIndex]);

    return (
    <div ref={root} className={'slide-navigator ' + className} style={navStyle} onClick={onClick}>
    {children}
    </div>
    );
    };

    export default SlideHighlight;

    总结

    我的 React 经验不太多,不考虑项目结构倒也问题不大,但是抽象组件的经验比较少,这次也是在 ChatGPT 帮助下才写好,所以赶紧趁热发一篇笔记。

    如果你对 React 经验比较丰富,发现我上面的代码有问题,欢迎指出。如果你对 React 开发有问题,对组件开发有疑问,也欢迎留言讨论。

  • 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 思否写作挑战赛,欢迎正在阅读的你也加入。