超能郭工
前端坤坤的小破站,IKUN永不过时 🐔

用 UNOCSS 重构网站使用感受

17 小时前 技术分享

为什么想换成unocss,除了是我爱学习这一方面因素外,tailwind-css 火蛮久了,但始终是更方便后端开发写前端的方式,不考虑维护的时候,确实写起来快

为什么不直接 tailwind-css,看了文档,当下感觉有些太贯彻 inline style 的写法了,部分论坛看的使用感受也是对它的一长串的,难以团队协作+维护;其次感觉tailwind-css的自定义思路和我不是我想法中的方法,刚好在看的过程中了解到了unocss,反正 unocss 也是来源于tailwind-css 的灵感,但自定义的方式相对符合我的想法

同样,又用博客做为学习并重构样式的机会。我遇到了下面的纠结

问题1:原子化CSS 与 CSS Module

先看常见的用法

tailwind-css 的示例 有种 CSS 里面找结构的感觉

html
<div class="mx-auto max-w-md overflow-hidden rounded-xl bg-white shadow-md md:max-w-2xl">
  <div class="md:flex">
    <div class="md:shrink-0">
      <img
        class="h-48 w-full object-cover md:h-full md:w-48"
        src="/img/building.jpg"
        alt="Modern building architecture"
      />
    </div>
    <div class="p-8">
      <div class="text-sm font-semibold tracking-wide text-indigo-500 uppercase">Company retreats</div>
      <a href="#" class="mt-1 block text-lg leading-tight font-medium text-black hover:underline">
        Incredible accommodation for your team
      </a>
      <p class="mt-2 text-gray-500">
        Looking to take your team away on a retreat to enjoy awesome food and take in some sunshine? We have a list of
        places to do just that.
      </p>
    </div>
  </div>
</div>

原子化CSS在对一个元素既有暗黑模式、响应式、多种交互情况下,写在一堆简直眼晕到爆炸,有种写完就最好别反工

不然你会有种我在哪?这™️还是国内吗?的感觉。但如果用 CSS Module 后,unocss 还有什么意义,除了一些在交互性不强的地方写写?

解决:指令转换器

我最后决定用 unocss 的指令转换器,让项目整体以原子化CSS样式,贯彻只用一套标准,复杂且需要交互改变多的地方就用 CSS Module 写到样式文件中

html
<template>
  <div class="panel relative z-3">
    <div class="panel-title p-m2 justify-between">
      <div class="flex items-center gap-s1">
        <Icon name="friend" size="18px"></Icon>
        大佬们
      </div>
      <NuxtLink to="/friend_link" class="text-bs auto-text-regular">
        更多大佬
      </NuxtLink>
    </div>
    <div class="grid grid-cols-3 place-items-center gap-m2 p-m2">
      <template v-if="loading">
        <div
          v-for="item in 6"
          :key="item"
          class="skeleton rounded-full size-[60px]"
        ></div>
      </template>
      <NuxtLink
        v-for="item in friendLinks"
        :key="item.id"
        target="_blank"
        :href="item.attributes.url"
        :class="$style.friend"
      >
        <div :class="$style.avatar">
          <img
            :src="mediaUrl(item.attributes.avatarMedia.data.attributes.url, 90)"
            alt="avatar"
            loading="lazy"
          />
        </div>
        <div :class="$style.name">
          {{ item.attributes.name }}
        </div>
      </NuxtLink>
    </div>
  </div>
</template>
<script setup lang="ts">
import type { Strapi4ResponseData } from "@nuxtjs/strapi";
import type { FriendLink } from "~/types";

const friendLinks = ref<Strapi4ResponseData<FriendLink>[]>([]);
const loading = ref(true);
const getFriendLinks = async () => {
  try {
    loading.value = true;
    friendLinks.value = await useFriendLinks();
    friendLinks.value.sort(() => (Math.round(Math.random()) >= 1 ? 1 : -1));
    friendLinks.value = friendLinks.value.slice(0, 6);
  } finally {
    loading.value = false;
  }
};

onMounted(() => {
  getFriendLinks();
});
</script>
<style lang="stylus" module>
.friend{
  @apply: "flex items-end mb-m4 relative"
  &:hover{
    @apply: "z-9"
    .avatar{
      @apply: "scale-110"
    }
    .name{
      @apply: "max-w-[160px] pr-m3 pl-[38px] rotate-0"
    }
  }
  .avatar{
    @apply: "aspect-1 rounded-full size-[60px] outline outline-2 outline-brand dark:outline-brand-DARK auto-bg-bg shadow-md overflow-hidden relative z-2 transition-transform duration-500"
    img{
      @apply: "object-cover relative "
    }
  }
  .name{
    @apply: "absolute-lb-30px_0 z-1 h-[40px] max-w-0 auto-bg-brand text-white text-hs rounded-r-full flex items-center truncate transition-all origin-left-bottom rotate--30 duration-500"
  }
}
</style>

问题2:原子化CSS写的也太散了

有些样式真的是一个样式名也真的一个属性在里面,那我直接写原来的 CSS 不就好了?

解决:自定义常用组合样式与制定theme

以前开源过 stylus-shortcut 的一种方式,利用特性将常见的样式属性设置,像

  • 设置 position 属性后 一般也会定义 left 与 top 属性
  • 设置 flex 属性后 一般也会定义 justify-content 与 align-items 属性

在实现优秀交互设计规范的设计稿时,通常设计师也会设计 字符规范、颜色规范 等 并贯彻整体的设计。

所以通过自定义配置后,可以进一步简化与规范

uno.config.js javascript
import {
  defineConfig,
  presetUno,
  presetTypography,
  transformerDirectives,
  type CSSObject,
} from "unocss";

// 字体规范
let spacing: Record<string, string> = {};
for (let i = 1; i <= 6; i++) {
  spacing[`m${i}`] = `${i * 8}px`;
  spacing[`l${i}`] = `${i * 16}px`;
}

// 字体规范
let fontSize: Record<string, [string, CSSObject]> = {
  dl: ["64px", { "line-height": "78px", "font-weight": 600 }],
  dm: ["48px", { "line-height": "58px", "font-weight": 600 }],
  ds: ["36px", { "line-height": "48px", "font-weight": 600 }],
  hl: ["32px", { "line-height": "40px", "font-weight": 600 }],
  hm: ["24px", { "line-height": "32px", "font-weight": 600 }],
  hs: ["18px", { "line-height": "28px", "font-weight": 600 }],
  bl: ["16px", { "line-height": "24px", "font-weight": 400 }],
  bm: ["14px", { "line-height": "20px", "font-weight": 400 }],
  bs: ["12px", { "line-height": "18px", "font-weight": 400 }],
};

export default defineConfig({
  presets: [
	  presetUno(), // 官方 tailwind 与 windcss的合集预设配置
	  presetTypography() // 官方文章样式预设配置
	 ],
  transformers: [transformerDirectives()], // 指令转换器
  rules: [
    [
	    //定位组合规则
      /^(absolute|relative|fixed|sticky)-(lt|rt|lb|rb)_(.*)-(.*)/,
      ([, posMod, dir, x, y]) => {
        let dirEnum: Record<string, Record<string, string>> = {
          lt: { left: x, top: y },
          rt: { right: x, top: y },
          lb: { left: x, bottom: y },
          rb: { right: x, bottom: y },
        };
        return {
          position: posMod,
          ...dirEnum[dir],
        };
      },
    ],
    [
	    // flex 组合规则
      /^flex-(start|end|center|between|around|evenly)-(start|end|center|stretch)/,
      ([, justify, items]) => {
        let justifyMap: Record<string, string> = {
          start: "flex-start",
          end: "flex-end",
          center: "center",
          between: "space-between",
          around: "space-around",
          evenly: "space-evenly",
        };
        let alignMap: Record<string, string> = {
          start: "flex-start",
          end: "flex-end",
          center: "center",
          stretch: "stretch",
        };
        return {
          display: "flex",
          justifyContent: justifyMap[justify],
          alignItems: alignMap[items],
        };
      },
    ],
  ],
  //#9ecbe8
  theme: {
    spacing,
    fontSize,
    container: {
      center: true,
      padding: "16px",
    },
    animation: {
	    // 自定义动效配置
      keyframes: {
        "fade-in-up":
          "{from{opacity:0;transform:translate3d(0,30px,0)}to{opacity:1;transform:translate3d(0,0,0)}}",
        rollup: "{from{transform:translateX(0)}to{transform:translateX(-50%)}}",
      },
      durations: {
        "fade-in-up": "400ms",
      },
      counts: {
        rollup: "infinite",
      },
    },
    colors: {
      sky: {
        DEFAULT: "#fff",
        DARK: "#000",
      },
      bg: {
        DEFAULT: "#f2f2f2",
        DARK: "#202020",
      },
      fg: {
        DEFAULT: "#fff",
        DARK: "#0b0b0b",
      },
      brand: {
        DEFAULT: "#252435",
        DARK: "#8a91db",
      },
      title: {
        DEFAULT: "#000",
        DARK: "#fff",
      },
      regular: {
        DEFAULT: "#2B394C",
        DARK: "#ccc",
      },
      line: {
        DEFAULT: "#efefef",
        DARK: "#2c2c2c",
      },
    },
  },
  // 设定改变 color mode 时自动变色的便捷样式
  shortcuts: [
    [
      /^auto-bg-(.*)$/,
      ([, c]) => `bg-${c} dark:bg-${c.replace(/([^\/]+)(.*)/, "$1-DARK$2")}`,
    ],
    [
      /^auto-border-(.*)$/,
      ([, c]) =>
        `b-solid border-${c} dark:border-${c.replace(
          /([^\/]+)(.*)/,
          "$1-DARK$2"
        )}`,
    ],
    [
      /^auto-text-(.*)$/,
      ([, c]) =>
        `color-${c} dark:color-${c.replace(/([^\/]+)(.*)/, "$1-DARK$2")}`,
    ],
    {
      "skeleton-wrap": "pointer-events-none",
      skeleton: "animate-pulse bg-gray-200 h-[24px] dark:bg-gray-700 rounded",
    },
  ],
});

presetUno的一个小BUG

  • theme中 spacing 不要用s开头
javascript
let spacing: Record<string, string> = {};
for (let i = 1; i <= 6; i++) {
  spacing[`s${i}`] = `${i * 4}px`;
}

// p-s1 你想要 padding: 4px
// 它会识别成 padding-inline-start: 4px;

最后

UnoCSS 实际使用起来真的还蛮符合我的需求的,社区生态看着也还不错,但配置的文档真的是一言难尽了,很多时候真的是直接看 presetUno 的源码才知道他还可以这样配置。

还好 CSS方案 也不用深入配置到特别的细节。在解决了这些纠结后,确实在开发的过程的,效率高了许多

标签: CSS
用 UNOCSS 重构网站使用感受
本站除注明转载外均为原创文章,采用 CC BY-NC-ND 4.0 协议。转载请注明出处,不得用于商业用途
评论