它在做什么

GLITCH INDEX 把文本输入当成实验材料。速度决定时间采样,扰动改变切片位移,颜色选择控制两个通道的对比关系。目标不是模拟随机故障,而是让每一个失真参数都可以被解释、复现和撤销。

关键实现

离屏 Canvas 先使用 Archivo Variable 的 wdth 宽度轴排出一份清晰字形,并上传为 GPU 纹理。fragment shader 按水平扫描线采样纹理,以时间相位、焦点和文字 seed 产生受控位移;暖色与蓝色通道朝相反方向偏移,同时保留稳定主体维持可读性。输入框本身始终是原生表单控件,因此键盘、触摸和输入法不依赖 Canvas 命中测试。

性能与降级

循环在离屏或页面隐藏时停止。reduced-motion 会释放 WebGL renderer,保留由同一文字、色彩和 wdth 轴参数驱动的静态 DOM 版本。动态效果只重绘实验区域,控制面板更新不会触发页面级布局;脚本或 WebGL 失败时仍可编辑文字并理解当前参数。

浏览器兼容

支持 WebGL 1.0 的现代浏览器运行 shader 版本;context lost 时立即切回 DOM,并在上下文恢复后重新初始化。变量字体不可用时会回退到系统无衬线字体,WebGL 不可用时保留静态字形、双色通道与全部原生控件。

KEY SOURCE / LIVE IMPLEMENTATION

运行源码节选

这不是为文档复制的示例。构建过程直接读取当前实验使用的 TypeScript 文件,并提取下方标记区段。

src/scripts/labs/glitch-index.ts FRAGMENT SHADER · TYPESCRIPT
const FRAGMENT_SHADER_SOURCE = `
  precision highp float;

  varying vec2 vUv;
  uniform sampler2D uGlyph;
  uniform vec2 uFocus;
  uniform float uPhase;
  uniform float uDistortion;
  uniform float uPulse;
  uniform float uSeed;
  uniform vec3 uAccent;

  float hash11(float value) {
    return fract(sin(value * 91.173 + uSeed * 0.013) * 43758.5453);
  }

  void main() {
    float line = floor(vUv.y * mix(96.0, 30.0, uDistortion));
    float timeSlice = floor(uPhase * 9.0);
    float burst = step(0.54, hash11(line + timeSlice));
    float focusWeight = max(0.12, 1.0 - abs(vUv.y - uFocus.y) * 2.35);
    float maximumShift = (0.003 + uDistortion * 0.072 + uPulse * 0.038) * focusWeight;
    float direction = hash11(line * 1.7 + timeSlice * 0.31) * 2.0 - 1.0;
    float shift = direction * maximumShift * mix(0.08, 1.0, burst);

    vec2 baseUv = vec2(vUv.x + shift, vUv.y);
    float body = texture2D(uGlyph, baseUv).r;
    float warm = texture2D(uGlyph, vec2(baseUv.x + maximumShift * 0.34, vUv.y)).r;
    float cool = texture2D(uGlyph, vec2(baseUv.x - maximumShift * 0.26, vUv.y)).r;

    vec3 paper = vec3(0.953, 0.941, 0.914);
    vec3 blue = vec3(0.08, 0.28, 1.0);
    vec3 color = vec3(0.063, 0.075, 0.094);
    color += body * paper * 0.92;
    color += warm * uAccent * (0.16 + uDistortion * 0.72);
    color += cool * blue * (0.12 + uDistortion * 0.58);

    float scanline = 0.965 + 0.035 * sin(vUv.y * 900.0 + uPhase * 3.0);
    gl_FragColor = vec4(clamp(color * scanline, 0.0, 1.0), 1.0);
  }
`;

生产 fragment shader 直接读取速度、扰动、焦点、文字 seed 与颜色通道,没有第二份演示实现。