Web特效03------像素VS顶点:图形处理的的两种基本单位
像素和顶点,是图形处理里最基础的两个单位。像素是屏幕上"点亮"的最小单位,数量固定,分辨率越高,画面就被切得越细------这也是很多适配问题的根源,比如同一份 GGB 图形,在不同分辨率的电脑上显示大小差异很大。顶点则不同,它描述的是图形本身的形状,不依赖屏幕分辨率,所以基于顶点(矢量)的网页效果,天生适配性更强,能在不同屏幕下等比呈现。这篇就从"像素是什么""顶点是什么"讲起,结合交互动画、Web 特效里常遇到的适配问题,把这两种基本单位的区别彻底讲清楚。

一、为什么要先分清像素和顶点
1. 一个"点",在图形世界里有两种完全不同的说法
日常生活里说"这个点在这儿",指向是唯一的。但一进图形处理的世界,"点"这个词就分裂成了两个完全不同的东西:
- 像素(Pixel):屏幕这张"方格纸"上,一个固定大小的格子------它是被切分出来的、数量有限的"容器",只能整个亮或者整个不亮。
- 顶点(Vertex):描述一个图形"长什么样"的坐标数据------它是数学意义上的一个点,精确到无限小数位,压根不在乎这张方格纸切得多细。
打个比方:像素像"方格纸上的格子",你只能沿着格子的边填色;顶点像"用铅笔在方格纸上点的一个坐标",这个坐标的位置可以精确到小数点后无数位,不受方格大小限制。
这两个概念如果混着用,后面所有跟"清晰度""适配"相关的问题都会变得说不清楚。
2. 混淆的代价:从"发虚"到"忽大忽小"的真实翻车现场
分不清这两者,在实际做交互动画、Web 特效的时候,会直接变成一堆让人摸不着头脑的现象:
| 现象 | 背后原因 |
|---|---|
| 图片在高分辨率屏幕上显得发虚、模糊 | 图片按"像素"存储,放大到更高分辨率的屏幕上,格子数量不够用了 |
| 同一个交互动画,在不同电脑上显示的大小不一样 | 动画尺寸如果按"像素数值"写死,不同屏幕的像素密度不同,视觉大小就会变 |
| 矢量图标随便放大缩小都不会失真 | 矢量图标本质是"顶点+路径"描述,缩放只是重新计算坐标,不涉及像素格子 |
| 位图放大后出现明显的锯齿、马赛克块 | 放大就是把有限的像素格子拉大显示,格子边界自然就露出来了 |
这些现象看似风马牛不相及,根子上都是同一件事:你的内容,到底是按"像素"存的,还是按"顶点"存的。
3. 两条技术路线的分野:一张图讲清楚

上面那张对比图里,左边是像素路线 ------不管形状多复杂,最终都要被摊开成一格一格填色,格子数量(也就是分辨率)决定了这个形状能有多精细,边缘的锯齿也是这么来的;右边是顶点路线------形状由几个坐标点(顶点)加上连接它们的规则来描述,不管屏幕分辨率是多少,渲染的时候都是重新按坐标计算,所以边缘永远是精确的直线或曲线。
用一段简单的代码,能直接看出这两条路线在"写法"上的区别:
javascript
// 像素路线:直接往画布上填色,填的是"格子"
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#1b9b9c';
ctx.fillRect(50, 50, 100, 100); // 从(50,50)开始,填一块100×100的像素区域
// 顶点路线:只描述"形状由哪几个点构成",不涉及具体填了哪些格子
const vertices = [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
{ x: 50, y: 100 },
]; // 一个三角形,只需要3个顶点坐标,渲染时按分辨率重新计算出该点亮哪些像素
fillRect 这一行,你告诉计算机的是"把这块区域的像素涂成这个颜色";而 vertices 这个数组,你告诉计算机的是"这个形状的几个关键坐标点在哪儿,至于最终要点亮哪些像素、点亮多少个,由你(渲染引擎)根据当前屏幕自己算"。前者认死了具体的格子,后者只认坐标关系------这就是后面几节要展开讲的所有适配性问题的起点。
二、什么是像素:屏幕上"点亮"的最小单位
1. 像素到底是什么:一个格子,一个颜色值
像素(Pixel,Picture Element 的缩写),是屏幕(或者一张位图图片)能显示的最小单位。你可以把整个屏幕想象成一张巨大的方格纸,每一个格子就是一个像素------它没法再被拆得更细,只能整格地显示同一个颜色。
每个像素本质上存的是一组颜色数值,最常见的是 RGBA ------红(Red)、绿(Green)、蓝(Blue)三个颜色通道,加一个透明度(Alpha)通道,每个通道通常是 0~255 的一个数字。所以一张 1920×1080 分辨率的图片,说白了就是 1920×1080 个格子,每个格子里存着一组 (R, G, B, A) 数值------分辨率越高,格子越多,能表现的细节自然也就越丰富。
2. 你以为的"像素",可能不是电脑说的"像素"
这是理解适配问题最关键的一步:"像素"其实分两种 ,一种是屏幕硬件层面真实存在的、密密麻麻的小灯珠,叫物理像素(Device Pixel) ;另一种是网页/系统里我们写代码时用的单位,叫逻辑像素 ,也就是常说的 CSS 像素。

在很长一段时间里,1 个 CSS 像素就对应 1 个物理像素,大家相安无事。但从"视网膜屏幕"(Retina Display)开始,手机、笔记本的屏幕密度越做越高,如果还是 1:1 对应,画面上的文字和图标会小到看不清------于是操作系统干脆规定:1 个 CSS 像素,用好几个物理像素一起来显示 ,这个倍数就是 devicePixelRatio(简称 dpr)。
上面那张图就是这个关系:普通屏幕 dpr = 1,一个 CSS 像素刚好对应一个物理像素;视网膜屏幕 dpr = 2,一个 CSS 像素被拆成 2×2 共 4 个物理像素来显示------同一份内容,在这两种屏幕上"看起来一样大",但背后动用的物理像素数量差了 4 倍。这也是为什么"同一份图片,在高分屏上却发虚"------如果图片本身只按 1 倍尺寸准备,放到 4 倍物理像素的区域里显示,自然就会被拉伸模糊。
| 概念 | 是什么 | 谁在用 |
|---|---|---|
| 物理像素(Device Pixel) | 屏幕硬件上真实存在的发光点数量,出厂就定死了 | 硬件、系统底层 |
| CSS 像素(逻辑像素) | 网页/应用代码里写的尺寸单位,width: 100px 里的那个 px |
前端代码、设计稿 |
| devicePixelRatio(dpr) | 1 个 CSS 像素对应几个物理像素的倍数 | 浏览器,用来做高分屏适配 |
这里要先澄清一个容易搞混的地方:"1px" 和 "1 个 CSS 像素" 不是两个概念,而是同一个东西的两种叫法 。CSS 规范里,px 这个单位本身就是"CSS 像素"的度量单位------你写 width: 100px,说的就是"100 个 CSS 像素",中间不存在换算,就像"米"本来就是给"长度"用的单位一样。
真正需要换算的,是"CSS 像素"往下,到"物理像素"这一层------也就是乘以 devicePixelRatio:
text
你写的代码: width: 100px
↓ (px 就是 CSS 像素的单位,不用换算)
CSS 像素: 100 个
↓ (乘以 devicePixelRatio)
物理像素: dpr=1 时是 100 个;dpr=2 时是 200 个
3. 用代码"摸"一下像素:每个像素里到底存了什么
在 Canvas 里,可以直接把某一块区域的像素数据读出来看看,验证一下"像素 = 一组颜色数值"这个说法:
javascript
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
// 先画一个红色方块
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 10, 10);
// 读取 (0,0) 这个像素点的颜色数据
const pixel = ctx.getImageData(0, 0, 1, 1).data;
console.log(pixel); // Uint8ClampedArray [255, 0, 0, 255] ------ 对应 R,G,B,A
// 也可以顺手看看当前屏幕的 dpr 是多少
console.log(window.devicePixelRatio); // 普通屏幕通常是 1,高分屏常见 2 或 3
getImageData 返回的这一组 [255, 0, 0, 255],就是"像素"最朴素的样子------四个数字,红满、绿无、蓝无、完全不透明。它不知道自己所在的图形"应该长什么样",只负责老老实实存这一格的颜色------这也是下一节要讲的"顶点"和它最本质的区别:像素是结果,顶点是描述结果该怎么算出来的规则。
4. 落地一步:适配不同屏幕,离不开 devicePixelRatio
理解了物理像素和 CSS 像素的关系,再往前走一步就是:真要做适配,光懂这个概念不够,代码里得真的用上 devicePixelRatio。
Canvas 默认只按 CSS 像素尺寸分配自己内部的绘图缓冲区,并不会主动感知屏幕的像素密度。如果什么都不做,在 dpr = 2 的视网膜屏幕上,画布实际只用了一半分辨率去绘制,再被浏览器拉伸显示成"看起来对"的大小------结果就是线条、文字发虚模糊,做交互动画时尤其明显。
以 three.js 为例,专门提供了对应的方法:
javascript
const renderer = new THREE.WebGLRenderer({ antialias: true });
// 关键一行:按当前设备的物理像素密度分配绘图缓冲区
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
setPixelRatio 做的事情,就是把前面那条换算链路走一遍:CSS 像素尺寸 × dpr = 需要的物理像素数量,让画布按这个更高的物理像素数量真正去渲染,再通过 CSS 缩回到写定的尺寸显示------每个物理像素都是算出来的颜色,而不是被拉伸出来的。
实际项目里,这个值通常会做个上限,比如
Math.min(window.devicePixelRatio, 2)------因为部分手机 dpr 能到 3 甚至更高,盲目按真实 dpr 渲染会让 GPU 计算量暴涨,反而卡顿。适配和性能,往往需要一个取舍。
三、什么是顶点:描述"形状"的坐标点
1. 顶点到底是什么:不是格子,是一个数学坐标
顶点(Vertex) ,是描述一个图形"关键位置"的坐标点。它不像像素那样是"屏幕上的一个格子",而是纯数学意义上的一个位置------可以是 2D 的 (x, y),也可以是 3D 的 (x, y, z)。
一个三角形,只需要 3 个顶点就能确定;一个立方体,8 个顶点就够了。顶点本身不占屏幕面积,它只是"钉"在空间里的一个精确位置,至于这个位置最终要点亮屏幕上的哪些像素、点亮多少个,那是渲染那一步才需要操心的事------和顶点本身完全无关。这也是为什么矢量图形能无限放大不失真:放大只是把顶点坐标按比例算大一点,再重新渲染一遍,顶点数据本身从没变过。
2. 光有顶点还不够:形状是靠"连接顺序"拼出来的

这一点很容易被忽略,却是理解 3D 建模、Web 三维引擎绕不开的一环:同一组顶点位置,连接的顺序不一样,拼出来的图形可能完全不同。
上面那张图就是最直接的例子:左边是 4 个顶点 v0、v1、v2、v3,按 (v0,v1,v2) 和 (v1,v3,v2) 这个顺序两两组成三角形,正好拼出一个完整的方形;右边是同样位置的 4 个顶点 ,但连接换成了对角线 (v0,v3) 和 (v1,v2),结果直接变成一个扭曲的"蝴蝶结"。
顶点位置没有变过一分一毫,变的只是"谁和谁连在一起"------这说明顶点数据从来不是单独起作用的,它必须搭配一套"连接规则"(在图形学里通常叫索引 Index 或者拓扑 Topology),才能真正定义出一个图形。
3. 顶点携带的信息,远不止"位置"
除了位置,一个顶点在实际渲染里往往还会"背"着更多信息------这些信息统称顶点属性(Vertex Attribute):
| 属性 | 作用 |
|---|---|
| Position(位置) | 最基础的属性,决定这个顶点在空间里的坐标,前面讲的都是它 |
| Color(颜色) | 给这个顶点单独指定一个颜色,渲染时会在顶点之间做颜色过渡(渐变) |
| Normal(法线) | 描述这个顶点所在表面"朝向哪个方向",决定光照怎么打在这个面上 |
| UV(纹理坐标) | 决定贴图上的哪一块区域,要贴到这个顶点所在的位置 |
也就是说,一个顶点更像是一份"档案",位置只是档案里最基本的一条信息,颜色、法线、贴图坐标都可以一起打包挂在同一个顶点上,渲染的时候一并参与计算。
4. 代码里看顶点:three.js 里的位置数组 + 索引
在 three.js 里,一个自定义几何体的顶点数据,大致长这样:
javascript
import * as THREE from 'three';
const geometry = new THREE.BufferGeometry();
// 4 个顶点的坐标,每 3 个数字一组:x, y, z
const positions = new Float32Array([
-1, 1, 0, // v0:左上
1, 1, 0, // v1:右上
-1, -1, 0, // v2:左下
1, -1, 0, // v3:右下
]);
// 连接顺序(索引):告诉引擎该怎么把这4个顶点拼成三角形
const indices = [
0, 1, 2, // 第一个三角形:v0, v1, v2
1, 3, 2, // 第二个三角形:v1, v3, v2
];
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setIndex(indices);
positions 存的就是纯坐标,indices 存的就是上面那张图里讲的"连接顺序"------两者缺一不可:只有 positions 没有 indices,引擎不知道该怎么把这些点连起来;顺序写反了(比如把 indices 改成 [0,3,1,1,2,3]),画出来的可能就是那个"蝴蝶结"。
四、像素 vs 顶点:一个管"够不够细",一个管"长什么样"
1. 先把区别摆到一张表上
前面两节分别讲了像素和顶点各自是什么,这里直接对照着看,区别会更清楚:
| 维度 | 像素(Pixel) | 顶点(Vertex) |
|---|---|---|
| 本质 | 屏幕上一个固定大小的格子 | 空间中一个精确的数学坐标 |
| 存的是什么 | 一组颜色值(RGBA) | 一组位置数值(x, y, z),还能附加颜色、法线、UV 等属性 |
| 数量 | 由分辨率决定,固定不变 | 由图形复杂度决定,和分辨率无关 |
| 缩放后会怎样 | 放大会糊、会出现锯齿 | 放大只是重新计算坐标,永远精确 |
| 谁负责"够不够细" | 像素:格子越多,画面越细腻 | ------ |
| 谁负责"长什么样" | ------ | 顶点:坐标+连接规则,决定形状本身 |
一句话总结这张表:像素决定"够不够细",顶点决定"长什么样"------这也是这一小节标题想强调的核心区别。
2. 但它们不是两个平行世界:顶点最终还是要变成像素
这里有个关键点容易被忽略:顶点和像素并不是"二选一"的关系,而是有先后顺序的------顶点在前,像素在后 。不管你的图形数据用多精确的顶点坐标描述,只要最终要显示在屏幕上,就必须经过一步转换,把"数学上精确的形状"转换成"一格一格该填什么颜色"。这一步,在图形学里叫光栅化(Rasterization)。
上面那张图就是光栅化正在发生的样子:红色轮廓是由 3 个顶点 v0、v1、v2 精确定义出来的三角形------它的每一条边在数学上都是绝对笔直的斜线。但当这个三角形要显示在像素网格上时,引擎必须挨个判断"每个格子的中心,是不是落在这个三角形里面",落在里面的格子就填色,不在的就不填------于是原本笔直的斜边,变成了阶梯状的锯齿。
3. 这也是为什么"抗锯齿"这个词会存在

理解了光栅化,就能理解一个几乎所有渲染设置里都有的选项------抗锯齿(Anti-aliasing)。既然锯齿的根源是"顶点定义的精确斜线,被迫塞进方形格子里",那抗锯齿要做的,本质上就是在锯齿的边缘格子上做一点"折中":不再是非黑即白地填色,而是根据三角形覆盖这个格子的面积比例,填一个介于两种颜色之间的过渡色,让边缘看起来更平滑。
javascript
// three.js 里最简单的开启方式:创建渲染器时加一个参数
const renderer = new THREE.WebGLRenderer({ antialias: true });
这一行代码背后做的事情,正是上面说的"光栅化时,在边缘格子做颜色折中"------顶点数据本身没有变,变的是光栅化这一步的处理方式。
4. 小结一下这一节的位置
到这里,前四节已经把两个概念的地基打完了:像素是什么(第二节)、顶点是什么(第三节)、它们各自的分工和相互关系(本节)。接下来第五、六节要讲的"适配性问题",本质上就是**"像素数量固定、但顶点坐标不固定"这个特性,在不同分辨率的屏幕上会引发的一连串连锁反应**------这也是为什么基于顶点/矢量描述的内容,天生比基于像素的内容更"扛造"。
五、适配性从哪来的:分辨率、像素密度与显示差异

1. 分辨率相同,不代表"看起来一样大"
前面几节讲的都是"一个像素是什么",这里要往前多走一步:同样是 1920×1080 分辨率,在 6 英寸的手机屏幕上和在 27 英寸的显示器上,像素的"物理大小"完全不是一回事 。分辨率只告诉你横竖各有多少个格子,并没有告诉你这些格子实际占多大的物理空间------决定这件事的,是另一个概念:PPI(Pixels Per Inch,每英寸像素数)。
PPI 的算法很直接:用分辨率除以屏幕的物理尺寸。同样多的像素,塞进越小的物理屏幕,PPI 就越高,每个像素也就越小、越密------这也是"视网膜屏幕"这个说法的由来:像素密到肉眼分辨不出格子感,看起来才会"细腻如视网膜"。
2. 同样的"200 像素",在不同屏幕上是不同的物理尺寸
上面那张图就是最直接的例子:手机屏幕 PPI 大约 460,显示器 PPI 大约 96------同样是 200 个物理像素宽的内容,在手机上实际只占大约 1.1 厘米,而在显示器上却要占到 5.3 厘米,相差将近 5 倍。
这正是"适配性问题"的根源:如果你的内容尺寸是按"固定像素数值"写死的,那它在不同 PPI 的设备上,视觉大小天生就不一样------不是代码写错了,而是像素这个单位,从来就不是一个"绝对大小"的度量衡,它的物理大小完全取决于所在设备的屏幕密度。
3. 网页适配的麻烦:三层变量叠在一起
做交互动画、Web 特效的时候,"多大算合适"这件事,实际上叠加了三层互相独立的变量:
| 变量 | 说的是什么 | 谁在决定 |
|---|---|---|
| 分辨率 | 屏幕横竖各有多少个物理像素 | 硬件出厂参数 |
| PPI(像素密度) | 每英寸塞了多少个像素 | 分辨率 ÷ 屏幕物理尺寸 |
| devicePixelRatio(dpr) | 1 个 CSS 像素对应几个物理像素 | 操作系统/浏览器,试图抹平 PPI 差异带来的视觉大小问题 |
devicePixelRatio 这一层(第二节讲过),本质上就是操作系统给出的"补丁"------它会根据屏幕 PPI 自动调整倍率,尽量让同样的 100px 在不同设备上"看起来"差不多大。但这个补丁只解决了"清晰度"问题(该用几个物理像素去画同一个 CSS 像素),并没有解决"屏幕物理尺寸本身千差万别"这个更底层的问题------一台 13 寸笔记本和一台 32 寸显示器,哪怕 dpr 都是 1,可视区域的物理大小依然天差地别。
4. 应对思路:别把尺寸焊死在"像素"上
正因为像素从来不是一个稳定的物理单位,前端在长期实践里,发展出了一套"不直接依赖固定像素值"的应对方式------用相对单位 代替绝对像素:
css
/* 写死像素:在不同屏幕上,视觉大小会随 PPI、屏幕尺寸浮动 */
.box-fixed {
width: 400px;
font-size: 16px;
}
/* 用相对单位:根据视口(viewport)尺寸动态计算,适配性更强 */
.box-responsive {
width: 40vw; /* 视口宽度的 40% */
font-size: 1rem; /* 相对于根元素字号,而不是绝对数值 */
}
vw(viewport width)、vh(viewport height)、%、rem 这些单位,算的都不是"多少个物理像素",而是"相对于当前视口或字号的比例"------这样一来,同一份代码,在小屏手机上和大屏显示器上,即便物理像素数量、PPI 完全不同,呈现出来的相对比例关系也能保持一致。这也是为什么开头提到的"网页适配性比较强"------它并不是天生免疫了像素差异,而是从设计单位上,就尽量不去直接和"固定像素数值"绑死。
下一节我们用一个具体案例------GGB 在不同电脑上的显示差异------把这套理论套进真实场景里看一遍。
六、案例:为什么同一份 GGB 图形,在不同电脑上显示大小不一样
1. GGB 是怎么画图的:一切都按"固定像素"来
GGB(GeoGebra)是一款经典的数学几何画图工具,画布、坐标轴、点的大小、线的粗细,这些设置在软件里基本都是按固定像素数值来配置的------比如"点的大小设为 5"、"窗口宽度 800px",这些数字写死之后,GGB 并不会主动去查询"当前这台电脑的屏幕密度是多少、需不需要调整"。
这正好是第五节讲的"固定像素"这条路子的典型代表------它信任的是像素这个单位本身,而没有考虑到像素在不同设备上物理大小并不相同。
2. 具体到这个案例:同一个 800px 窗口,两台电脑上不一样大
上面那张图就是这个问题的直接体现:同一份 GGB 文件,窗口固定是 800 像素宽,分别放到一台高 PPI 的视网膜笔记本和一台低 PPI 的外接显示器上------像素数值完全一样,但因为两台设备每个像素代表的物理尺寸不同,最终看到的窗口物理大小差了一大截。高 PPI 屏幕上,800 个像素被挤在很小的物理空间里,窗口看起来小;低 PPI 屏幕上,800 个像素铺开的物理面积大得多,窗口看起来大。
这和你自己反馈过的现象完全对得上:同一个交互动画/图形文件,换台电脑显示大小就不一样------根源不是文件坏了,也不是软件出 bug 了,而是这类工具从设计上就没有对"不同屏幕物理密度"做自适应,一切都以"像素数值"为准绳。
3. 换个思路:如果这是一个网页效果,会怎样
对比一下,如果同样的需求换成用网页来实现,情况会不一样:
css
/* GGB 式思路:窗口尺寸写死成固定像素 */
.ggb-style-canvas {
width: 800px;
height: 600px;
}
/* 网页常见思路:用视口相对单位,尺寸跟随屏幕自动换算 */
.web-style-canvas {
width: 80vw; /* 视口宽度的 80% */
max-width: 800px; /* 大屏幕下设置上限,避免无限放大 */
aspect-ratio: 4 / 3; /* 保持宽高比例,不用同时写死宽高像素 */
}
80vw 这种写法,画布尺寸永远是"当前视口宽度的 80%"------不管这台设备的 PPI 是 96 还是 460,占屏幕的比例是恒定的,这才是第五节说的"网页适配性比较强"背后真正的做法:不是网页天生不受像素影响,而是它从一开始就习惯用"相对视口的比例"而不是"绝对像素数值"去描述尺寸。
4. 这个案例说明了什么
这个案例其实是把前面几节的理论串起来了一遍:
- 像素本身没有固定的物理大小(第二、五节)------它的物理尺寸取决于设备的 PPI。
- 如果内容尺寸按"固定像素"配置(GGB 的做法),就必然会在不同 PPI 设备上呈现不同的视觉大小。
- 顶点/矢量思维的优势,不只是"图形不失真"那么简单,它还天然带来了一种"按比例描述,而不是按绝对数值描述"的习惯------这也是下一节要展开讲的,为什么基于顶点的网页效果,天生比基于像素的传统工具更"扛造"。
七、为什么基于顶点(矢量)的网页效果更"扛造"
1. "扛造"扛的到底是什么
前面几节反复提到"矢量/顶点更扛造",这一节把这句话拆开讲清楚。所谓"扛造",指的不是"画面更好看",而是不管把它放到什么屏幕、什么分辨率、放大缩小多少倍,它都能保持该有的清晰度和比例------上一节 GGB 的案例暴露的问题,本质上就是"不扛造":换台电脑,大小就变了。
而矢量/顶点路线之所以扛造,根源在第三、四节讲过的那句话:顶点存的是坐标,不是像素。坐标是数学上精确的数值,不管乘以多大的缩放系数,重新算一遍还是精确值------这才是"扛造"真正的底层原因。
2. 一个最直观的证据:放大 4 倍看边缘

上面那张图就是最直接的验证:同样一个小圆形,分别走位图路线和矢量路线放大 4 倍------位图放大后,原本圆滑的边缘变成了看得见的像素块、锯齿明显 ;矢量放大后,依然是一条光滑的曲线,肉眼看不出任何"格子感"。
原因也很好理解:位图存的是"每个格子该是什么颜色"这份结果 ,放大就是把这份已经算好的结果强行拉伸;而矢量存的是"圆心在哪、半径多少"这条公式 ,放大只是把半径这个数字乘以 4,然后重新按新半径计算一遍轮廓------结果永远是从公式现算出来的,而不是拉伸出来的。
3. 代码里验证:一行代码改分辨率,矢量图形毫发无伤
以 SVG 为例,viewBox 这个属性,就是矢量"按比例重算"这套逻辑的直接体现:
html
<!-- 不管这个 svg 标签最终显示多大(width/height 随便写),
viewBox 定义的内部坐标系统永远不变,内容永远按比例重新计算填满容器 -->
<svg viewBox="0 0 100 100" width="50" height="50">
<circle cx="50" cy="50" r="40" fill="#1b9b9c" />
</svg>
<svg viewBox="0 0 100 100" width="500" height="500">
<circle cx="50" cy="50" r="40" fill="#1b9b9c" />
</svg>
两段代码里,圆心坐标、半径数值完全没有改变 ,唯一变的是外层 width/height------不管最终显示成 50px 还是 500px,圆的轮廓永远是根据 cx=50, cy=50, r=40 这几个坐标现算出来的,不会因为放大 10 倍就出现锯齿。
three.js 的场景在这一点上更彻底:相机、几何体的顶点数据,压根不知道"画布最终会显示成多少像素",只有在监听到窗口尺寸变化时,重新告诉渲染器"现在按这个新尺寸再画一遍":
javascript
window.addEventListener('resize', () => {
// 顶点数据(camera、geometry)完全没动,只是重新告诉渲染器新的画布尺寸
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
4. 但矢量不是万能药
也要说清楚一点:"矢量更扛造"不等于"矢量能解决所有适配问题" 。矢量解决的,是"图形本身按比例缩放不失真"这一层;但像第五节讲的"这块区域到底该占多大比例"(用 vw 还是固定像素),依然需要开发者自己在 CSS/布局层面做决定------矢量只是保证了"不管缩放到多大,画面质量不崩",并不会自动帮你决定"该缩放到多大"。这两件事分别对应"渲染质量"和"布局尺寸",是两个层面的问题,下一节会具体聊到实际开发中该怎么权衡。
八、交互动画开发里:该用像素思维,还是顶点思维
1. 两种思维不是二选一,而是分工协作
写了七节,可能会给人一种错觉:"顶点更扛造,那是不是应该全部用顶点思维,尽量别碰像素?"------并不是。真正的问题从来不是"选哪个",而是"这个具体元素,它的本质更接近哪一个"。选错了,不是代码写得不好,而是从一开始就用错了工具。
2. 该用像素思维的场景
- 照片、扫描图、复杂纹理:一张风景照片本身就是一堆颜色数据拼出来的,没有"顶点"可言,天然就该按位图处理。
- 不需要缩放的固定尺寸元素:比如一张背景大图,如果设计上它本来就只在一种固定分辨率下展示,用像素思维直接了当。
- 像素级精确控制的效果:模糊、发光、颜色调整这类"后处理"特效,本质上是在一批已经算好的像素上做二次加工,这个阶段天然是像素思维的地盘(在 GPU 里,这一步通常发生在"片元着色器"阶段)。
3. 该用顶点思维的场景
- 图标、Logo、UI 形状:需要在各种分辨率、各种屏幕下都保持清晰,天生该用矢量/顶点描述(SVG 图标就是最常见的做法)。
- 需要交互变形、动画的图形:拖拽缩放、旋转、路径动画这些操作,本质上是在"改坐标",顶点思维处理起来又快又干净;如果是位图,每次变形都要重新计算并重新光栅化整张图,成本高得多。
- 3D 场景里的几何体:第三到第八节讲的所有坐标系统知识,归根结底都是为"顶点思维"服务的------3D 内容天生就该用顶点描述。
4. 真实项目里,两者经常"合体":纹理贴图
实际做交互动画、Web 3D 特效,很少是"纯像素"或"纯顶点"的极端情况------最常见的做法,是用顶点定义形状的边界,再往这个边界表面上"贴"一张像素图片 ,这就是纹理贴图(Texture Mapping)。

上面那张图就是这个概念:红色的 4 个顶点 v0~v3 定义出一个方形的边界 ,而边界内部铺的那些彩色小方块,代表的是一张像素图片 ------顶点负责"这个面该是什么形状、多大",像素纹理负责"这个面上具体显示什么内容"。这也是为什么一个"用顶点画出来的立方体",贴上一张木纹图片纹理之后,看起来就像一块真实的木箱------几何是顶点算的,细节是像素画的。
代码里,这个过程大致是这样:
javascript
import * as THREE from 'three';
// 顶点思维:定义一个矩形平面(4个顶点 + 连接规则)
const geometry = new THREE.PlaneGeometry(2, 2);
// 像素思维:加载一张位图作为纹理
const texture = new THREE.TextureLoader().load('wood-texture.jpg');
// 把像素纹理"贴"到顶点定义的表面上
const material = new THREE.MeshBasicMaterial({ map: texture });
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);
PlaneGeometry 这一行,是纯粹的顶点思维------只关心"这块面有多大、由哪几个点构成";TextureLoader 加载的 wood-texture.jpg,是纯粹的像素数据;而 MeshBasicMaterial({ map: texture }) 这一步,就是把两者绑在一起的"胶水"。
5. 一张决策速查表
| 场景 | 优先用哪种思维 | 理由 |
|---|---|---|
| 照片、复杂纹理素材 | 像素 | 内容本身就是颜色数据,没有几何结构可言 |
| 图标、Logo、简单图形 | 顶点(矢量) | 需要跨分辨率保持清晰 |
| 需要拖拽、缩放、旋转的元素 | 顶点 | 改坐标比重新光栅化整张图便宜得多 |
| 模糊、发光等后处理特效 | 像素 | 特效本身作用在"已经算好的画面"上 |
| 3D 模型、场景 | 顶点 | 3D 空间的一切都建立在坐标之上 |
| 给模型贴材质、贴图 | 顶点 + 像素(纹理贴图) | 顶点定义形状,像素填充表面细节 |
到这里,前八节已经把"是什么、为什么、怎么选"都讲完了。最后两节,回到最初的出发点------把实际开发里最容易踩的坑系统梳理一遍,再收个尾。
九、常见的坑:模糊、拉伸、错位都是怎么来的
1. 三类坑,背后是三种"想当然"
写代码时踩的这些坑,拆开看都能归到一句"想当然"上:
- 模糊:想当然地认为"像素就是像素",忽略了物理像素和 CSS 像素、devicePixelRatio 之间那层换算(第二、五节)。
- 拉伸变形:想当然地把尺寸写死成固定像素数值,换了个容器、换了台屏幕,比例就崩了(第五、六节)。
- 位置/交互错位:想当然地认为"我写的坐标"和"最终显示的位置"是严格 1:1 对应的,却忽略了中间还藏着一层坐标空间转换。
2. 症状对照表:先看现象,再定位问题
| 症状 | 常见原因 | 对应章节 |
|---|---|---|
| 图片、文字在手机上发虚、模糊 | 只准备了 1 倍图,没有针对 devicePixelRatio 做适配 |
第二、五节 |
| 同一个交互动画,在不同电脑上显示大小不一样 | 尺寸写死成固定像素,没用相对单位(vw/%)做适配 | 第五、六节 |
| Canvas 画出来的内容比例不对、被拉伸变形 | canvas.width/height(内部绘图尺寸)和 CSS 显示尺寸(style.width/height)不一致,被浏览器强行拉伸 |
第二节 |
| SVG 图标缩放后变形、内容跑到框外 | 没设置 viewBox,或者 viewBox 比例和内容实际比例对不上 |
第七节 |
| 鼠标点击的位置,和视觉上看到的元素对不上 | 没有把"鼠标事件返回的 CSS 像素坐标"换算成"canvas 内部像素坐标" | 第二、五节 |
第三条("Canvas 被拉伸变形")是很多人容易忽视的一个坑,专门展开说一下:Canvas 元素其实有两套独立的尺寸 ------width/height 属性决定的是"内部实际有多少个像素可以画",而 CSS 的 width/height 决定的是"这块画布在页面上显示多大"。这两者如果不一致,浏览器会把内部画好的位图强行拉伸/压缩到 CSS 尺寸------这就是很多人明明代码没写错,画面却是"糊的"或者"变形的"根本原因:
javascript
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
// CSS 显示尺寸:这块画布在页面上占多大
const cssWidth = 400;
const cssHeight = 300;
// 内部绘图尺寸:按 dpr 放大,保证有足够的物理像素可用
canvas.width = cssWidth * dpr;
canvas.height = cssHeight * dpr;
// CSS 尺寸单独设置,保证页面布局大小不受影响
canvas.style.width = cssWidth + 'px';
canvas.style.height = cssHeight + 'px';
// 因为内部像素变多了,画的时候要把坐标系统也放大对应的倍数
ctx.scale(dpr, dpr);
这几行代码,本质上就是把第五节讲的理论,套进 Canvas 这个具体 API 里落地了一遍。
3. 标准排查流程
4. 实用建议:把这几条列成开发清单
- 图片资源准备多倍图 (1x/2x/3x),配合
devicePixelRatio选用合适的版本,而不是只准备一份图硬拉伸。 - Canvas 项目,一律区分"内部尺寸"和"CSS 尺寸" ,养成写
canvas.width = cssWidth * dpr这一步的习惯。 - 布局尺寸优先用相对单位 (
vw/vh/%/rem),固定像素只用在"确实不希望随屏幕变化"的场景。 - SVG 图标一律带上
viewBox,并保证viewBox的宽高比和内容实际比例一致。 - 涉及鼠标/触摸交互的场景,记得做坐标系换算,不要直接拿事件里的 CSS 像素坐标去比对 canvas 内部的绘制坐标。
十、小结:记住这几条就不会晕
绕了一大圈,回到最开始那句话------"像素和顶点,是图形处理里最基础的两种基本单位"。把全篇压缩成几条,以后遇到问题翻出来对一下就够了:
- 像素是结果,顶点是描述结果该怎么算出来的规则:像素存的是"这一格该是什么颜色",顶点存的是"这个形状长什么样"。
- 像素的物理大小从不固定:同样数值的像素,在不同 PPI 的屏幕上,占据的实际物理空间完全不同------这是几乎所有适配问题的根源。
- 顶点/矢量之所以"扛造",是因为它存的是坐标公式,不是算好的结果,缩放多少倍都能重新精确计算,不会像位图那样出现锯齿。
- 两种思维不是二选一,而是分工:照片、纹理、后处理特效交给像素;图标、UI、需要变形交互的图形交给顶点;做贴图这类效果,本身就是两者的结合。
- Canvas 的"内部尺寸"和"CSS 显示尺寸"是两回事,这是最容易被忽视、但又最常导致"画面糊了/变形了"的一个技术细节。
- 排查坑,先分类再动手:发虚查 dpr,变形查 canvas 内外尺寸是否一致,交互错位查坐标换算,矢量跑框查 viewBox------分类越准,改起来越快。
和"坐标系统"那篇一样,这篇真正想留下的,也不是几条具体结论,而是一套追问习惯:这个视觉元素,它的内容本质上是"格子里的颜色",还是"数学上的坐标"?只要先想清楚这个问题,后面该用什么工具、该往哪个方向排查问题,基本都能自己推出来。
十一、关于八荒启
八荒启是一家专注于交互体验产品与解决方案的品牌,持续探索交互技术在教育教学、产品展示、过程模拟、操作训练和数据可视化等场景中的应用。
我们不仅分享技术实现,也持续创作和沉淀交互动画、数字作品、开发教程、项目案例与行业解决方案,希望通过交互技术,让复杂事物变得更加容易理解、探索、操作和创造。
八荒启,专为交互动画而生
让复杂事物可探索、可操作、可反馈
如果你正在寻找交互作品、学习相关技术,或者希望把一个想法转化为可实际操作的交互项目,欢迎访问八荒启官网了解更多案例与服务。
- 官方网站:bahuangqi.com
- 主要内容:交互动画、3D 可视化、教育互动、仿真模拟与技术教程
- 定制服务:可通过官网提交需求或联系人工客服进行评估
感谢阅读。如果本文对你有所帮助,欢迎点赞、收藏和关注,我们会继续分享更多交互作品与项目实践。