82 lines
8.5 KiB
Markdown
82 lines
8.5 KiB
Markdown
# 点缀与特效:SVG 和着色器
|
||||
|
|
|
|||
|
|
这里讲用代码画出来的视觉效果:小到一条手绘下划线、一层颗粒,大到首屏整片流动的渐变、扫过按钮的一道流光、由点阵组成的主视觉。它们能让方向从“配色对了”变成“有生命”;做得粗糙时,就是又一层模型默认的装饰。
|
|||
|
|
|
|||
|
|
## 先问要不要
|
|||
|
|
|
|||
|
|
- **从生成引擎里来。** 效果要能说出属于这个方向的理由:方向的关键词是“信号”,点阵和扫描线才成立;是“流体”,流动渐变才成立。删掉它,方向会少一点自己的东西;删掉后没有区别,就不加。
|
|||
|
|
- **一个主效果,几处回响。** 一页选一个主效果,其余地方只用它的颜色、形状或节奏小声呼应,比如首屏是流动渐变,按钮的流光就用同一组颜色。几种特效各自为政,页面就成了效果展示。
|
|||
|
|
- **不碰内容。** 效果不压在正文、数据和控件上,也不画得像按钮;在它上面的文字,按最差的一帧检查对比度。
|
|||
|
|
- **用代码画。** SVG 和着色器体积小,跟着主题色走,放大也清楚。摄影和复杂插画才用图片,见 [素材](media.md)。
|
|||
|
|
- **质感先来自材质。** 顶尖的产品界面很少用流光、渐变团和着色器,让人记住的多是写实的材质、准确的结果和手感。特效是给少数方向的,不是提升质感的默认手段。
|
|||
|
|
- **产品界面收着用。** 后台和工具的工作区不放持续运动的效果;登录、空状态、完成反馈和品牌页可以放开。
|
|||
|
|
|
|||
|
|
## SVG 能做的几类
|
|||
|
|
|
|||
|
|
| 效果 | 做法 | 要注意 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| 颗粒质感 | `feTurbulence type="fractalNoise"` 生成噪声,`feColorMatrix` 把它染成页面的颜色并控制透明度,写成 data URI 平铺成 `background-image`。细颗粒 `baseFrequency` 约 0.8–0.9、透明度 0.05–0.08;再叠一层约 0.004 的大块起伏,表面就不是均匀色块 | 加 `stitchTiles="stitch"` 避免拼缝。做成固定尺寸的平铺图,不把 `filter` 挂在整页或滚动容器上,否则每次重绘都要重算。只在方向本身有实物材质时加,不当作提升质感的通用补丁 |
|
|||
|
|
| 手绘框、粗糙边缘、大号数字的毛边 | `feTurbulence` 接 `feDisplacementMap`,`scale` 约 1.5–3。同类图形各换一个 `seed`,不要一模一样 | 正文不加;`scale` 太大像坏了。滤镜区域默认只向外留 10%,位移、光晕和投影会被裁掉,给 `filter` 设足够的 `x`、`y`、`width`、`height` |
|
|||
|
|
| 沿弧线或环形排字 | `<textPath>` 配 `startOffset="50%"` 和 `text-anchor="middle"` 居中 | 字放在弧的外侧;放在内侧,字距被挤压,字形会相撞。手机换一套 `viewBox` 重新取景,不只是等比缩小 |
|
|||
|
|
| 下划线、轨迹、连接两处内容的引线 | `path` 加 `stroke-linecap="round"`;缩放时线宽不变用 `vector-effect="non-scaling-stroke"`;描线动画设 `pathLength="1"` 和 `stroke-dasharray="1"`,让 `stroke-dashoffset` 从 1 走到 0 | 引线两端要真的指向内容。让光点沿线流动:用一段短虚线加长间隔,动画 `stroke-dashoffset`,再叠一份模糊的副本当光晕 |
|
|||
|
|
|
|||
|
|
颜色统一用 `currentColor` 或设计变量,让效果跟着主题和深色模式变。
|
|||
|
|
|
|||
|
|
## 着色器能做什么
|
|||
|
|
|
|||
|
|
| 效果 | 核心做法 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| 流动渐变 | 3–5 个颜色点沿各自的慢速轨迹移动,按距离混合;要更像液体,用噪声扭曲坐标再取噪声(domain warping),纹路会像大理石或油彩那样流 |
|
|||
|
|
| 极光和光带 | 在一个方向上拉长的噪声,叠加在深色底上,边缘用渐变遮罩收掉 |
|
|||
|
|
| 虹彩和镭射 | 用余弦调色板 `a + b·cos(2π(c·t + d))` 按角度或指针位置取色,四组参数就能调出整条连续的色带 |
|
|||
|
|
| 流光 | 一条两边柔化的亮带,按 20°–30° 斜角扫过形状,只在形状内部可见 |
|
|||
|
|
| 点阵和半调 | 把画面切成网格,每格按下面的亮度画一个圆点;动起来可以是波纹、指针附近的隆起,或者从点阵慢慢显出一张图 |
|
|||
|
|
| 像素和抖动 | 用 Bayer 矩阵做有序抖动,把颜色压成少数几种,得到一位色屏幕和早期游戏的质感 |
|
|||
|
|
| 跟随指针 | 指针附近的亮斑、扭曲或点阵隆起;指针位置先做惯性平滑,再传给着色器 |
|
|||
|
|
|
|||
|
|
不少效果不需要 WebGL:边框上转圈的流光,用 `@property` 注册一个角度变量,驱动 `conic-gradient`,再用 `mask` 只露出边框;文字上的流光,用 `background-clip: text` 配移动的渐变;跟随指针的卡片光斑,把指针坐标写进 CSS 变量,驱动 `radial-gradient`。只有连续变化的噪声、逐像素计算或大量点阵时,才用着色器。
|
|||
|
|
|
|||
|
|
## 做得高级的关键
|
|||
|
|
|
|||
|
|
**渐变**
|
|||
|
|
|
|||
|
|
- **在 OKLab 或 OKLCH 里混色。** 在 sRGB 里混两种对比色,中间会经过发灰发脏的一段;在 OKLab 里混,中间段仍然饱满。CSS 可以写 `linear-gradient(in oklch, …)`,着色器里先转到 OKLab 再插值。
|
|||
|
|
- **加抖动消色带。** 大面积平滑渐变在 8 位显示上会出现一圈圈色带,暗部最明显。输出前叠一层正负半个色阶的噪声,色带就消失了;把噪声加大到 3–8%,就是颗粒渐变的质感。
|
|||
|
|
- **配色从品牌来。** 紫蓝色的柔光团是最典型的模型默认。从设计变量里取两三个色相,挑一组不常见的搭配,再加结构:光带、点阵、颗粒,而不是再加一团光。
|
|||
|
|
- **光晕不要糊成白块。** 几层光叠加后会超过 1,直接截断会变成一片死白。用 `1 - exp(-x)` 这类曲线把亮部压回来,高光才有层次。
|
|||
|
|
|
|||
|
|
**流光**
|
|||
|
|
|
|||
|
|
- **亮带要窄、边要软。** 宽度约为元素的 15–25%,两侧用 `smoothstep` 柔化。
|
|||
|
|
- **扫过要快,间隔要长。** 单次 600–900ms、缓入缓出;在悬停、出现时播放,或每隔 4 秒以上扫一次。一刻不停地扫,就显得廉价。
|
|||
|
|
- **用提亮的混合方式。** `screen` 或 `plus-lighter` 让流光提亮底色,而不是刷上一层白。浅色底上几乎看不见流光,所以它更适合深色、饱和色或金属质感的表面。
|
|||
|
|
- **只在一个地方扫。** 通常是主按钮或一张主卡片。每张卡都在闪,就没有重点。
|
|||
|
|
|
|||
|
|
**点阵**
|
|||
|
|
|
|||
|
|
- **圆点面积跟明暗走。** 半径按亮度的平方根变化,面积才和明暗成正比,画面读起来才像原图。
|
|||
|
|
- **边缘要抗锯齿。** 用 `fwidth` 和 `smoothstep` 在一个像素的宽度内过渡(WebGL2 直接可用)。硬边的点动起来会闪烁、爬行。
|
|||
|
|
- **防摩尔纹。** 网格尺寸取设备像素的整数倍,或把网格旋转 15°、45°;彩色半调的每个通道用不同角度。
|
|||
|
|
- **没亮的点也画出来。** 屏幕式的点阵,熄灭的点保留很淡的一层,亮的点加一圈柔光,才像真的屏幕。
|
|||
|
|
- **格子大小决定读法。** 每格 6–12 CSS 像素读起来是质感,16 像素以上读起来是图形。
|
|||
|
|
|
|||
|
|
**动起来**
|
|||
|
|
|
|||
|
|
- **速度跟角色走。** 背景的循环慢到十秒以上,读正文时注意不到;交互反馈要在 150–300ms 内响应。
|
|||
|
|
- **指针要有惯性。** 每帧让当前位置向目标靠近一小步(例如 `current += (target - current) * 0.1`),效果才像有重量,不会一抖一抖地跟着鼠标。
|
|||
|
|
- **可以跟滚动绑定。** 把滚动进度作为参数传进去,让效果随阅读推进变化,比独立循环更有叙事感。
|
|||
|
|
|
|||
|
|
## 成本与兜底
|
|||
|
|
|
|||
|
|
- **分辨率按效果选。** 模糊的颜色场按 1 倍或一半分辨率渲染,再用 CSS 放大;点阵、像素和细线要按实际设备像素渲染,否则会糊。
|
|||
|
|
- **不引入 3D 引擎。** 只画一个全屏四边形时,直接写 WebGL 或用几 KB 的小工具库,不为它加载 three.js 这类完整引擎。
|
|||
|
|
- **看不见就停。** 滚出视口时用 `IntersectionObserver` 暂停,标签页隐藏时停掉 `requestAnimationFrame`;`prefers-reduced-motion` 时只画一帧静止画面。
|
|||
|
|
- **先写兜底。** 拿不到 WebGL、上下文丢失(`webglcontextlost`)或设备太弱时,显示同样配色的 CSS 渐变或静态图,页面照常可读。
|
|||
|
|
|
|||
|
|
## 验收
|
|||
|
|
|
|||
|
|
- 截几个不同时刻的画面,不只看第一帧;效果上的文字按最差的一帧检查对比度。
|
|||
|
|
- 100% 和 200% 下看点阵、颗粒和边缘;缩略图里看不出颗粒是正常的。
|
|||
|
|
- 浅色、深色和减少动效三种设置各看一次,再看一次兜底画面。
|
|||
|
|
- 在目标手机上看滚动是否掉帧、机身是否发热。
|
|||
|
|
- 装饰性 SVG 和画布加 `aria-hidden="true"`;承担意思的效果,例如由点阵拼出的标题,另给读屏能读到的真实文字。
|