阅读视图

发现新文章,点击刷新页面。

写 CSS 用 px?这 3 个单位能让页面自动适配屏幕

在网页开发中,CSS 单位是控制元素尺寸、间距和排版的基础。

长期以来,px(像素)因其直观、精确而被广泛使用。

然而,随着设备屏幕尺寸和用户需求的多样化,单纯依赖 px 已难以满足现代 Web 对可访问性灵活性响应式能力的要求。

什么是 px?

px 是 CSS 中的绝对长度单位,代表像素(pixel)。

在标准密度屏幕上,1px 通常对应一个物理像素点。

开发者使用 px 可以精确控制元素的大小,例如:

.container {
  width: 320px;
  font-size: 16px;
  padding: 12px;
}

这种写法简单直接,在固定尺寸的设计稿还原中非常高效。但问题也正源于它的绝对性。

px 存在哪些问题?

1. 缺乏响应能力

px 的值是固定的,不会随屏幕宽度、容器大小或用户设置而变化。

在一个 320px 宽的手机上显示良好的按钮,在 4K 显示器上可能显得微不足道,反之亦然。

2. 不利于可访问性

许多用户(尤其是视力障碍者)会调整浏览器的默认字体大小。

但使用 px 定义的字体不会随之缩放,导致内容难以阅读。

相比之下,使用相对单位(如 rem)能尊重用户的偏好设置。


更好的选择

为解决上述问题,CSS 提供了一系列更智能、更灵活的单位和功能。以下是几种核心方案:

1. 相对单位:rem 与 em

  • rem(root em):相对于根元素(<html>)的字体大小。默认情况下,1rem = 16px,但可通过设置 html { font-size: 18px } 改变基准。
  • em:相对于当前元素或其父元素的字体大小,常用于局部缩放。

示例:

html {
  font-size: 16px; /* 基准 */
}

.title {
  font-size: 1.5rem; /* 24px */
  margin-bottom: 1em; /* 相对于自身字体大小 */
}

优势:支持用户自定义缩放,便于构建比例一致的排版系统。

2. 视口单位:vw、vh、vmin、vmax

这些单位基于浏览器视口尺寸:

  • 1vw = 视口宽度的 1%
  • 1vh = 视口高度的 1%
  • vmin 取宽高中较小者,vmax 取较大者

用途:适合全屏布局、动态高度标题等场景。

示例:

.hero {
  height: 80vh; /* 占视口高度的 80% */
  font-size: 5vw; /* 字体随屏幕宽度缩放 */
}

注意:在移动端,vh 可能受浏览器地址栏影响,需谨慎使用。

3. clamp() 函数:实现流体响应

clamp() 是 CSS 的一个重要进步,允许你在一个属性中同时指定最小值、理想值和最大值:

font-size: clamp(16px, 4vw, 32px);

含义:

  • 在小屏幕上,字体不小于 16px;
  • 在中等屏幕,按 4vw 动态计算;
  • 在大屏幕上,不超过 32px。

这行代码即可替代多个 @media 查询,实现平滑、连续的响应效果。

更推荐结合相对单位使用:

font-size: clamp(1rem, 2.5vw, 2rem);

这样既保留了可访问性,又具备响应能力。

4. 容器查询(Container Queries)

过去,响应式布局只能基于整个视口(通过 @media)。

但组件常常需要根据自身容器的大小来调整样式——这就是容器查询要解决的问题。

使用步骤:

  1. 为容器声明 container-type
.card-wrapper {
  container-type: inline-size; /* 基于内联轴(通常是宽度) */
}
  1. 使用 @container 编写查询规则:
@container (min-width: 300px) {
  .card-title {
    font-size: 1.25rem;
  }
}

@container (min-width: 500px) {
  .card-title {
    font-size: 1.75rem;
  }
}

现在,只要 .card-wrapper 的宽度变化,内部元素就能自动响应,无需关心页面整体布局。这对构建可复用的 UI 组件库至关重要。

容器查询已在主流浏览器(Chrome 105+、Firefox 116+、Safari 16+)中得到支持。


建议

  • 避免在字体大小、容器宽度、内边距等关键布局属性中使用纯 px
  • 优先使用 rem 作为全局尺寸基准,em 用于局部比例。
  • 对需要随屏幕缩放的元素,使用 clamp() + vw/rem 组合。
  • 构建组件时,考虑启用容器查询,使其真正“自适应”。
  • 保留 px 仅用于不需要缩放的场景,如边框(border: 1px solid)、固定图标尺寸等。

本文首发于公众号:程序员大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期内容

写前端久了,我用 Node.js 给自己造了几个省力小工具

我也是写了很久 TypeScript,才意识到这些写法不对

ThreadLocal 在实际项目中的 6 大用法,原来可以这么简单

重构了20个SpringBoot项目后,总结出这套稳定高效的架构设计

JavaScript 如何准确判断数据类型?5 种方法深度对比

在写js的时候,很容易因为数据类型没判断好而出错,比如这样的代码:

function calculate(a, b) {
    return a + b;
}
// 我以为是 10 + 20 = 30
calculate(10, 20); // 结果 30 对的 

// 实际上用户输入的是字符串和数字
calculate("10", 20); // 结果为 "1020" 

所以为了避免这种情况的出现,我们还是要去判断好数据类型。

JavaScript中,有几种方式来判断数据类型,以下是常用的方法:

1. typeof 操作符

最常用的类型判断方法,但有一些局限性:

typeof 42;           // "number"
typeof "hello";      // "string"
typeof true;         // "boolean"
typeof undefined;    // "undefined"
typeof null;         // "object" (历史遗留问题)
typeof {};           // "object"
typeof [];           // "object"
typeof function(){}; // "function"
typeof Symbol();     // "symbol"
typeof 42n;          // "bigint"

局限性

  • 无法区分数组、对象和 null
  • 函数返回 function
  • typeof 适合判断基本类型,但遇到对象类型就力不从心了

2. instanceof 操作符

用于检测构造函数的 prototype 属性是否出现在对象的原型链中:

[] instanceof Array;           // true
{} instanceof Object;          // true
new Date() instanceof Date;    // true
function(){} instanceof Function; // true

// 继承关系,数组也是对象
[] instanceof Object;          // true

instanceof 的局限性:

// 基本类型用不了
42 instanceof Number;          // false
"hello" instanceof String;     // false

在跨 iframe 或不同 window 环境下可能失效(因为构造函数不同)


3. Object.prototype .toString.call()

这是最准确、最可靠的方法,能识别所有内置类型!

Object.prototype.toString.call(42);           // "[object Number]"
Object.prototype.toString.call("hello");      // "[object String]"
Object.prototype.toString.call(true);         // "[object Boolean]"
Object.prototype.toString.call(null);         // "[object Null]"
Object.prototype.toString.call(undefined);    // "[object Undefined]"
Object.prototype.toString.call([]);           // "[object Array]"
Object.prototype.toString.call({});           // "[object Object]"
Object.prototype.toString.call(function(){}); // "[object Function]"
Object.prototype.toString.call(Symbol());     // "[object Symbol]"
Object.prototype.toString.call(42n);          // "[object BigInt]"

我们封装一个实用的工具函数:

function getRealType(value) {
    return Object.prototype.toString.call(value)
        .slice(8, -1)          // 截取"[object "和"]"之间的内容
        .toLowerCase();         // 转为小写,更友好
}

console.log(getRealType([]));        // "array"
console.log(getRealType(null));      // "null"
console.log(getRealType({}));        // "object"
console.log(getRealType(new Date())); // "date"

4. 专用方法

对于一些特殊类型,JavaScript提供了专门的判断方法:

判断数组:Array.isArray()

Array.isArray([]);     // true
Array.isArray({});     // false
Array.isArray("123");  // false

判断NaN:Number.isNaN()

// 注意区别!
isNaN("hello");        // true  ← 字符串不是数字,但这样判断容易误解
Number.isNaN("hello"); // false ← 更准确:只有真正的NaN才返回true
Number.isNaN(NaN);     // true

判断有限数字:Number.isFinite()

Number.isFinite(42);     // true
Number.isFinite(Infinity); // false  ← 无穷大不是有限数字
Number.isFinite("42");   // false  ← 字符串不是数字

编写健壮的函数

在实际开发中的应用:

场景1:安全的数字相加

function safeAdd(a, b) {
    // 确保两个参数都是数字类型
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new Error('参数必须是数字');
    }
    return a + b;
}

safeAdd(1, 2);     // 3
safeAdd(1, "2");   // 报错:参数必须是数字

场景2:处理多种数据类型

function processData(data) {
    // getRealType方法在第3点Object.prototype.toString.call()中有写
    const type = getRealType(data); 
    
    switch(type) {
        case 'array':
            return data.map(item => item * 2);
        case 'object':
            return Object.keys(data).length;
        case 'string':
            return data.toUpperCase();
        case 'number':
            return data * 2;
        default:
            return '不支持的数据类型';
    }
}

console.log(processData([1, 2, 3]));    // [2, 4, 6]
console.log(processData("hello"));      // "HELLO"
console.log(processData({a: 1, b: 2})); // 2

总结:选择合适的方法

场景 推荐方法 示例
判断基本类型 typeof typeof "hello" === "string"
判断数组 Array.isArray() Array.isArray([])
判断自定义对象 instanceof obj instanceof MyClass
精确类型判断 Object.prototype.toString.call() 见上文工具函数
特殊值判断 专用方法 Number.isNaN(), Number.isFinite()

本文首发于公众号:程序员大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

写前端久了,我用 Node.js 给自己造了几个省力小工具

我也是写了很久 TypeScript,才意识到这些写法不对

ThreadLocal 在实际项目中的 6 大用法,原来可以这么简单

重构了20个SpringBoot项目后,总结出这套稳定高效的架构设计

JavaScript 中如何正确判断 null 和 undefined?

相信写前端开发的朋友对下面这种报错应该很熟悉:

Cannot read properties of undefined

有一次我加班处理问题,也是因为这一个bug。

后来才发现,原来是一个接口返回的数据里,某个字段有时候是null导致的,而我没有做判断就直接使用了。

一、为什么需要判断空值?

举例如下:

// 场景1:用户没填姓名
const userName = undefined;
console.log(userName.length); // 报错!Cannot read properties of undefined

// 场景2:接口返回空数据
const apiResponse = null;
console.log(apiResponse.data); // 报错!Cannot read properties of null

所以,空值判断是保证代码健壮性的重要环节。


二、先搞懂:null 和 undefined 有啥区别?

虽然它们都表示空,但含义不同:

undefined:变量被声明了,但还没赋值。

let name;
console.log(name); // undefined

null:程序员主动赋值为空,表示我明确知道这里没有值。

let user = null; // 表示“用户不存在”

所以,当我们说判断变量是否为空,通常是指:它是不是 nullundefined


判断方法

以下介绍了比较常用的几种判断方案。

方法一:显式比较(最保险)

if (variable === null || variable === undefined) {
  console.log('变量为空');
}

适用场景

  • 需要明确区分null和undefined时
  • 团队代码规范要求严格相等判断
  • 对代码可读性要求高的项目

案例

function getUserProfile(user) {
  if (user === null || user === undefined) {
    return '用户不存在';
  }
  return `欢迎,${user.name}`;
}

方法二:非严格相等(最常用)

if (variable == null) {
  console.log('变量为空');
}

这里有个重要知识点== null 实际上等价于 === null || === undefined,这是JavaScript的语言特性。

为什么推荐这个写法?

  • 代码简洁,少写很多字符
  • 性能优秀,现代JS引擎都做了优化
  • 意图明确,专业开发者一看就懂

方法三:逻辑非操作符

if (!variable) {
  console.log('变量为falsy值');
}

注意! 这个方法容易造成其它的误伤:

// 这些值都会被判断为"空"
!false      // true
!0         // true  
!""        // true
!NaN       // true

// 实际开发中的坑
const count = 0;
if (!count) {
  console.log('计数为0'); // 这里会执行,但可能不是我们想要的
}

方法四:typeof 关键字(安全的写法)

if (typeof variable === 'undefined' || variable === null) {
  console.log('变量是空的!');
}

安全在哪?

// 如果变量根本没声明,前两种方法会报错
if (notDeclaredVariable == null) { // 报错!ReferenceError
}

if (typeof notDeclaredVariable === 'undefined') { // 正常运行!
  console.log('变量未定义');
}

这种写法主要用于检查一个变量是否被声明过,但对于普通函数参数或已声明变量,没必要这么复杂。

适用于不确定变量是否声明的场景。


方法五:空值合并操作符(现代写法)

这是ES2020的新特性,用起来特别顺手:

// 传统写法
const name = username ? username : '匿名用户';

// 现代写法
const name = username ?? '匿名用户';

优势:只对 nullundefined 生效,不会误判0、false等其他值。


方法六:可选链操作符(对象属性专用)

处理嵌套对象时,这个功能简直是救命稻草:

// 以前的痛苦写法
const street = user && user.address && user.address.street;

// 现在的优雅写法
const street = user?.address?.street;

结合空值合并,写法更加安全:

const street = user?.address?.street ?? '地址未知';

实际开发中的建议

场景1:简单的空值检查

// 推荐
if (value == null) {
  // 处理空值
}

// 或者
const safeValue = value ?? defaultValue;

场景2:需要区分null和undefined

if (value === null) {
  console.log('明确设置为空');
}

if (value === undefined) {
  console.log('未定义');
}

场景3:处理对象属性

// 安全访问深层属性
const phone = order?.customer?.contact?.phone ?? '未填写';

场景4:函数参数默认值

function createUser(name, age = 18) {
  // age参数只在undefined时使用默认值
  return { name, age };
}

总结

虽然现代JavaScript引擎优化得很好,但了解原理还是有帮助的:

  1. == null 和显式比较性能相当
  2. typeof 检查稍慢,但能安全检测未声明变量
  3. 可选链操作符在现代浏览器中性能优秀
场景 推荐写法 原因
一般空值检查 value == null 简洁高效
需要明确意图 value === null value === undefined` 可读性强
设置默认值 value ?? defaultValue 安全准确
对象属性访问 obj?.prop 避免报错
函数参数 参数默认值 语言特性

没有绝对最好的方法,只有最适合当前场景的选择。根据你的具体需求和团队规范来决策吧!

本文首发于公众号:程序员大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

16 个前端冷知识:用一次就忘不掉的那种

我也是写了很久 TypeScript,才意识到这些写法不对

ThreadLocal 在实际项目中的 6 大用法,原来可以这么简单

重构了20个SpringBoot项目后,总结出这套稳定高效的架构设计

❌