🎨 CSS 写到手抽筋?Stylus 说:‘让我来!’

前言

还在手动重复写 margin: 0; padding: 0;?还在为兼容性疯狂加 -webkit- 前缀?大厂前端早已不用原始 CSS 硬刚了!Stylus 作为一款现代化 CSS 预处理器,让你写样式像写 JavaScript 一样爽快。

Stylus:高效的CSS预处理器

基本特性

Stylus是一种CSS预处理器,提供了许多CSS不具备的高级功能:

stylus 复制代码
// 定义变量
$background_color = rgba(255, 255, 255, 0.95)

.wrapper
  background $background_color
  box-shadow 0 0 0 10px rgba(0, 0, 0, 0.1)

优势与使用场景

  1. 变量支持:避免重复值,便于主题切换
  2. 嵌套规则:更清晰的DOM结构表示
  3. 混合(Mixins) :复用样式块
  4. 函数与运算:动态计算样式值
  5. 简洁语法:可选的花括号、分号和冒号

编译与使用

安装Stylus后,可以通过命令行编译.styl文件:

bash 复制代码
npm install -g stylus
stylus -w common.styl -o common.css
  1. 第一个语句是用来安装stylus的直接运行就好
  2. 第二个语句是你编译common.styl文件时使用的,也就是你写CSS代码时使用的,因为浏览器并不能直接编译.styl文件,所以你要先将.styl文件编译成.css文件,也就是用上面给的那个命令,注意要自己切换成自己的.styl文件名,后面的css名可以随便取一个自己想要的

插件的使用

我们要想使用stylus,除了要全局安装之外还要下载一下下面的这个插件。

我们要先进入插件市场,然后搜索stylus,点击我选择的那个插件点击安装即可

案例实战

先看效果,再上代码,最后在分析考点易错点

效果

下面是我们实现的一个简单的效果界面图

代码

styl 复制代码
$background_color = rgba(255, 255, 255, 0.95)

html 
    box-sizing border-box
    min-height 100vh
    display flex
    flex-direction column
    justify-content center
    align-items center
    text-align center
    background url('http://wes.io/hx9M/oh-la-la.jpg') center no-repeat
    background-size cover

* 
    box-sizing border-box

.wrapper
    padding 20px
    min-width 350px   
    background $background_color
    box-shadow 0 0 0 10px rgba(0, 0, 0, 0.1)
    h2 
        text-align center
        margin 0
        font-weight 200


body 
    color pink

.plates
    margin 0
    padding 0
    text-align left
    list-style: none
    li      
        border-bottom 1px solid rgba(0, 0, 0, 0.2)
        padding 10px 0px 
        display flex
    label 
        flex 1
        cursor pointer
    input
        display none


.add-items
    margin-top 20px
    input 
        padding 10px 
        outline 0
        border 1px solid rgba(0, 0, 0, 0.1)
        

我们可以看到.styl文件不用去写:{}了,而且可以直接层叠样式

当我们运行stylus -w common.styl -o common.css命令时,它会实时的将common.styl文件编译成common.css,你可以根据自己的需求来编写,让我们看看它帮我写好的common.css文件吧

css 复制代码
html {
  box-sizing: border-box;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: url("http://wes.io/hx9M/oh-la-la.jpg") center no-repeat;
  background-size: cover;
}
* {
  box-sizing: border-box;
}
.wrapper {
  padding: 20px;
  min-width: 350px;
  background: rgba(255,255,255,0.95);
  box-shadow: 0 0 0 10px rgba(0,0,0,0.1);
}
.wrapper h2 {
  text-align: center;
  margin: 0;
  font-weight: 200;
}
body {
  color: #ffc0cb;
}
.plates {
  margin: 0;
  padding: 0;
  text-align: left;
  list-style: none;
}
.plates li {
  border-bottom: 1px solid rgba(0,0,0,0.2);
  padding: 10px 0px;
  display: flex;
}
.plates label {
  flex: 1;
  cursor: pointer;
}
.plates input {
  display: none;
}
.add-items {
  margin-top: 20px;
}
.add-items input {
  padding: 10px;
  outline: 0;
  border: 1px solid rgba(0,0,0,0.1);
}
javascript 复制代码
// 获取DOM元素
// 获取添加项目的表单元素
const addItems = document.querySelector('.add-items');
// 获取显示项目列表的元素
const itemsList = document.querySelector('.plates');
// 从本地存储获取项目数据,如果没有则初始化为空数组
let items = JSON.parse(localStorage.getItem('tapasItems')) || [];

// 添加新项目函数
function addItem(e) {
  // 阻止表单默认提交行为
  e.preventDefault();
  // 获取输入框中的文本值
  const text = this.querySelector('[name=item]').value;
  // 创建新项目对象
  const item = {
    text,        // 项目文本
    done: false  // 完成状态初始为false
  };
  // 将新项目添加到数组中
  items.push(item);
  // 更新列表显示
  populateList(items, itemsList);
  // 将更新后的数组保存到本地存储
  localStorage.setItem('tapasItems', JSON.stringify(items));
  // 重置表单
  this.reset();
}

// 渲染项目列表函数
function populateList(plates = [], platesList) {
  // 使用map方法将数组转换为HTML字符串
  platesList.innerHTML = plates.map((plate, i) => {
    return `
      <li>
        <!-- 复选框,绑定data-index属性用于标识项目索引 -->
        <input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? 'checked' : ''}>
        <!-- 项目文本标签 -->
        <label for="item${i}">${plate.text}</label>
      </li>
    `;
  }).join(''); // 将数组转换为字符串
}

// 切换项目完成状态函数
function toggleDone(e) {
  // 如果点击的不是input元素则直接返回
  if (!e.target.matches('input')) return;
  // 获取被点击元素的data-index属性值
  const el = e.target;
  const index = el.dataset.index;
  // 切换项目的完成状态
  items[index].done = !items[index].done;
  // 更新本地存储
  localStorage.setItem('tapasItems', JSON.stringify(items));
  // 重新渲染列表
  populateList(items, itemsList);
}

// 添加事件监听器
// 表单提交事件 - 添加新项目
addItems.addEventListener('submit', addItem);
// 列表点击事件 - 切换项目完成状态
itemsList.addEventListener('click', toggleDone);

// 初始化加载 - 页面加载时渲染已有项目
populateList(items, itemsList);
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, viewport-fit=cover">
    <title>Document</title>
    <link rel="stylesheet" href="./common.css">
</head>
<body>
    <div class="wrapper">
        <h2>Local TAPAS</h2>
        <p>请添加您的TAPAS</p>
        <ul class="plates">
            <li>Loading Tapas ...</li>
        </ul>
        <form action="" class="add-items">
            <input 
                type="text"
                placeholder="Item Name"
                required  <!-- 让输入框变成必须填写 -->
                name="item"
            >
            <input type="submit" value="+ Add Item">
        </form>
    </div>

    <script src="./common.js">

    </script>
</body>
</html>

分析考点易错点

1. Stylus 变量 $background_color

考点

  • Stylus 中变量的定义和使用
  • RGBA 颜色值的表示方法

答案

  • $background_color = rgba(255, 255, 255, 0.95) 定义了一个半透明白色背景变量
  • 在 Stylus 中,变量名可以包含 $ 符号,但不是必须的
  • 可以直接在样式中引用变量,如 background $background_color

易错点

  • 忘记变量名前加 $(虽然 Stylus 允许不加,但加了更清晰)
  • RGBA 值写错格式,如漏掉 alpha 通道或使用错误范围值
  • 变量作用域问题(Stylus 变量有作用域概念)

2. 背景图片设置

考点

  • CSS 背景属性的简写方式
  • background-size: cover 的作用
  • 多背景属性的正确顺序

答案

styl 复制代码
background url('http://wes.io/hx9M/oh-la-la.jpg') center no-repeat
background-size cover

等价于 CSS:

css 复制代码
background-image: url('http://wes.io/hx9M/oh-la-la.jpg');
background-position: center;
background-repeat: no-repeat;
background-size: cover;

易错点

  • 混淆 covercontain 的区别:

    • cover:完全覆盖容器,可能裁剪图片
    • contain:完整显示图片,可能留白
  • 背景图片 URL 未加引号导致错误

  • 多个背景属性顺序错误(简写时有特定顺序要求)

  • 忘记设置 no-repeat 导致图片平铺

3. localStorage 使用

考点

  • localStorage 的 API 使用
  • JSON 序列化与反序列化
  • 数据持久化策略

答案

javascript 复制代码
// 存储数据
localStorage.setItem('tapasItems', JSON.stringify(items));

// 读取数据
let items = JSON.parse(localStorage.getItem('tapasItems')) || [];

易错点

  • 忘记使用 JSON.stringify 直接存储对象,导致存储为 [object Object]
  • 读取时忘记使用 JSON.parse,导致得到的是字符串而非对象
  • 未处理 getItem 返回 null 的情况(代码中使用 || [] 做了默认值处理)
  • 存储大量数据超出 localStorage 容量限制(通常 5MB)
  • 不考虑隐私模式下 localStorage 可能不可用的情况

4. Viewport Meta 标签

考点

  • 响应式设计基础
  • 移动端视口控制
  • 各属性的含义

答案

html 复制代码
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, viewport-fit=cover">

各属性含义:

  • width=device-width:视口宽度等于设备宽度
  • initial-scale=1:初始缩放比例为1
  • user-scalable=no:禁止用户缩放
  • viewport-fit=cover:覆盖整个屏幕(针对刘海屏设备)

易错点

  • 拼写错误如 user-scalable 写成 user-scalabe
  • 错误理解 initial-scale 的作用
  • 在需要用户缩放功能的场景错误地设置 user-scalable=no
  • 忽略 viewport-fit=cover 导致刘海屏设备显示问题
  • 多个属性间缺少逗号分隔(viewport 内容是用逗号分隔的)
相关推荐
chao_78942 分钟前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼1 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原1 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序
popoxf1 小时前
在新版本的微信开发者工具中使用npm包
前端·npm·node.js
爱编程的喵2 小时前
React Router Dom 初步:从传统路由到现代前端导航
前端·react.js
每天吃饭的羊2 小时前
react中为啥使用剪头函数
前端·javascript·react.js
Nicholas683 小时前
Flutter帧定义与60-120FPS机制
前端
多啦C梦a3 小时前
【适合小白篇】什么是 SPA?前端路由到底在路由个啥?我来给你聊透!
前端·javascript·架构
薛定谔的算法3 小时前
《长安的荔枝·事件流版》——一颗荔枝引发的“冒泡惨案”
前端·javascript·编程语言
中微子3 小时前
CSS 的 position 你真的理解了吗?
前端·css