CC 咖啡猫的工作空间 Coding Space

可视化

Canvas

基本用法

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// 绘图基本操作
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 100);     // 实心矩形
ctx.strokeRect(120, 10, 100, 100);  // 空心矩形
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除

// 路径
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.lineTo(100, 150);
ctx.closePath();
ctx.fillStyle = 'blue';
ctx.fill();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();

// 弧和圆形
ctx.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.fill();

// 文字
ctx.font = '24px sans-serif';
ctx.fillText('Hello Canvas', 10, 50);
ctx.textAlign = 'center';

像素操作(ImageData)

const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; // Uint8ClampedArray [R,G,B,A, R,G,B,A, ...]

// 灰度处理
for (let i = 0; i < data.length; i += 4) {
  const gray = data[i] * 0.299 + data[i+1] * 0.587 + data[i+2] * 0.114;
  data[i] = data[i+1] = data[i+2] = gray;
}

ctx.putImageData(imageData, 0, 0);

动画循环

function animate() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 更新状态
  x += vx;
  y += vy;

  // 绘制
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2);
  ctx.fill();

  requestAnimationFrame(animate);
}
animate();

Canvas 性能优化

优化手段 说明
离屏 Canvas 将静态元素提前绘制到离屏 Canvas 上,主 Canvas 直接 drawImage() 复用
分层 Canvas 静态层 + 动态层叠加,静态层只需绘制一次,避免反复重绘
脏矩形 只重绘变化的区域,而不是全屏 clearRect
避免浮点坐标 浮点坐标触发抗锯齿(sub-pixel rendering),Math.floor(x) 或 `x
// 离屏 Canvas
const offscreen = document.createElement('canvas');
const offCtx = offscreen.getContext('2d');
// 在离屏 Canvas 上预先绘制复杂静态图形
offCtx.drawImage(background, 0, 0);

// 主 Canvas 只负责组合
ctx.drawImage(offscreen, 0, 0); // 离屏内容
ctx.fillStyle = 'red';
ctx.fillRect(100, 100, 50, 50); // 动态内容
// willReadFrequently 属性(Canvas 2D)
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// 当需要频繁 getImageData / putImageData 时,开启此选项让 Canvas 使用 CPU 后备存储
// 避免 GPU → CPU 来回传输的性能损失

Canvas vs SVG

对比维度 Canvas SVG
渲染方式 像素(位图) 矢量(几何图形)
缩放 模糊(非矢量) 清晰(矢量)
性能(大量元素) 好(几千上万个元素仍流畅) 差(DOM 节点多时卡顿)
交互 需手动计算命中检测(坐标判断) 原生 DOM 事件绑定
动画 需手动 requestAnimationFrame CSS/SMIL 动画
内存 低(无 DOM 节点) 高(每个图形是一个 DOM 节点)
适用场景 游戏、像素处理、大数据图表 图标、Logo、插画、小数据量交互图表

SVG

viewBox 坐标系统

<!-- viewBox="minX minY width height" -->
<svg width="200" height="200" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="40" fill="blue" />
</svg>
  • viewBox 定义了 SVG 内部的逻辑坐标系
  • 内部的 cx="50" 对应外部宽度 200px100px(等比缩放)
  • 不设置 viewBox 时 width/height 直接映射到坐标值

基础形状和路径

<svg viewBox="0 0 200 200">
  <!-- 基础形状 -->
  <rect x="10" y="10" width="80" height="80" rx="5" fill="red" />
  <circle cx="150" cy="50" r="40" fill="green" />
  <ellipse cx="100" cy="150" rx="60" ry="30" fill="blue" />

  <!-- 路径 path -->
  <path d="M 10 10 L 50 10 L 50 50 Z" fill="orange" />
  <!-- M 移动到 / L 直线 / C 三次贝塞尔 / Q 二次贝塞尔 / A 弧线 / Z 闭合 -->
  <path d="M 100 100 C 150 20, 200 180, 250 100" stroke="black" fill="none" />
</svg>
Path 命令 含义 示例
M x y Move To(移动到) M 10 10
L x y Line To(画直线到) L 50 50
C x1 y1, x2 y2, x y 三次贝塞尔曲线 C 10 10, 40 40, 50 10
Q x1 y1, x y 二次贝塞尔曲线 Q 20 10, 50 50
A rx ry x-axis-rotation large-arc sweep x y 弧线 A 30 30 0 0 1 50 50
Z 闭合路径 Z

渐变与滤镜

<svg viewBox="0 0 200 200">
  <defs>
    <!-- 线性渐变 -->
    <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" stop-color="red" />
      <stop offset="100%" stop-color="blue" />
    </linearGradient>

    <!-- 滤镜:模糊 -->
    <filter id="blur">
      <feGaussianBlur stdDeviation="3" />
    </filter>
  </defs>

  <rect x="10" y="10" width="100" height="100" fill="url(#grad1)" />
  <rect x="80" y="60" width="100" height="100" fill="blue" filter="url(#blur)" />
</svg>

ECharts

核心配置 option

const chart = echarts.init(document.getElementById('container'));

const option = {
  // 标题
  title: { text: '标题', subtext: '副标题' },

  // 提示框
  tooltip: { trigger: 'axis', formatter: '{b}: {c}' },

  // 图例
  legend: { data: ['销量'] },

  // X 轴
  xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },

  // Y 轴
  yAxis: { type: 'value' },

  // 数据缩放
  dataZoom: [
    { type: 'inside', start: 0, end: 100 },   // 内置鼠标滚轮缩放
    { type: 'slider', start: 0, end: 100 },    // 滑动条
  ],

  // 视觉映射
  visualMap: {
    min: 0, max: 100,
    inRange: { color: ['#50a3ba', '#eac736', '#d94e5d'] },
    calculable: true,
  },

  // 系列
  series: [{
    name: '销量',
    type: 'line',                // line/bar/pie/scatter/map/heatmap/gauge
    data: [120, 200, 150],
    smooth: true,                // 平滑曲线
    areaStyle: {},               // 面积图
  }]
};

chart.setOption(option);

// 响应式
window.addEventListener('resize', () => chart.resize());

常用图表对比

图表类型 type 适用场景 核心配置
折线图 line 趋势、时间序列 xAxis.type: 'time', smooth, areaStyle
柱状图 bar 分类对比 barWidth, stack(堆叠)
饼图 pie 占比分布 roseType: 'radius'(南丁格尔玫瑰图)
散点图 scatter 双变量相关性 symbolSize 映射第三维度
热力图 heatmap 二维分布密度 visualMap 颜色映射
地图 map 地理区域分布 registerMap 注册地图 JSON
仪表盘 gauge 进度/状态指标 min/max, detail.formatter

大数据量优化

配置 说明
dataZoom 缩放窗口,每次只渲染可视范围内的数据,支持滑动手势
sampling: 'lttb' Largest-Triangle-Three-Bucket 采样算法,保留趋势特征
progressive: 500 分批渲染,每帧渲染 500 个点,避免一次性渲染卡死 UI
large: true 开启散点图大数据模式(关闭 hover 动画以换性能)
// 大数据量折线图优化
{
  series: [{
    type: 'line',
    sampling: 'lttb',       // 降采样,保留趋势
    data: hugeDataArray,    // 十万级数据点
    showSymbol: false,      // 不显示数据点
    lineStyle: { width: 1 },
  }],
  dataZoom: [{ type: 'inside' }],
  progressive: 500,         // 分批渲染
}

自定义图表

// series.renderItem — 用 Canvas 绘制自定义图形
{
  series: [{
    type: 'custom',
    renderItem: (params, api) => {
      const x = api.value(0);
      const y = api.value(1);
      return {
        type: 'group',
        children: [{
          type: 'rect',
          shape: {
            x: api.coord([x - 0.5, y])[0],
            y: api.coord([x, y])[1],
            width: 20,
            height: api.size([1, y])[1],
          },
          style: { fill: 'red' },
        }]
      };
    },
    data: [[1, 10], [2, 20], [3, 15]],
  }]
}

// graphic — 直接在 canvas 上画附加元素
chart.setOption({
  graphic: {
    type: 'text',
    left: 'center',
    top: 'center',
    style: { text: 'Hello', fill: '#333' },
  }
});

Three.js

三大要素

import * as THREE from 'three';

// 1. Scene(场景)
const scene = new THREE.Scene();

// 2. Camera(相机)
// 透视相机(fov, aspect, near, far)
const camera = new THREE.PerspectiveCamera(
  75,                                   // 视角(FOV)
  window.innerWidth / window.innerHeight, // 宽高比
  0.1,                                  // 近裁剪面
  1000                                  // 远裁剪面
);
camera.position.z = 5;

// 正交相机(用于 2D 或等距视角)
// const camera = new THREE.OrthographicCamera(-10, 10, 10, -10, 0.1, 1000);

// 3. Renderer(渲染器)
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 动画循环
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
animate();
相机类型 用途 特点
PerspectiveCamera 3D 场景 近大远小,fov/far/near 决定视锥体
OrthographicCamera 2D / 等距视图 无透视变形,大小与距离无关

几何体 + 材质 + 网格(Mesh)

// 几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);   // 盒子
// new THREE.SphereGeometry(1, 32, 32);             // 球体
// new THREE.CylinderGeometry(1, 1, 2, 32);         // 圆柱
// new THREE.PlaneGeometry(5, 5);                   // 平面

// 材质
const material = new THREE.MeshStandardMaterial({
  color: 0x44aa88,
  roughness: 0.4,
  metalness: 0.1,
});

// 网格 = 几何体 + 材质
const mesh = new THREE.Mesh(geometry, material);
mesh.position.x = 2;
mesh.rotation.y = Math.PI / 4;
scene.add(mesh);

光照模型

// 环境光 — 均匀照亮所有表面,无方向
const ambientLight = new THREE.AmbientLight(0x404060);
scene.add(ambientLight);

// 平行光 — 模拟太阳(来自某方向,产生阴影)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7);
directionalLight.castShadow = true;  // 产生阴影
scene.add(directionalLight);

// 点光 — 从一点向四周发射
const pointLight = new THREE.PointLight(0xff0040, 2, 50);
pointLight.position.set(10, 5, 10);
scene.add(pointLight);

// 聚光 — 锥形光
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(0, 10, 0);
spotLight.angle = Math.PI / 6;       // 锥角
spotLight.penumbra = 0.2;            // 边缘模糊
spotLight.decay = 1;                 // 衰减
scene.add(spotLight);
光照类型 特点 性能
AmbientLight 无方向,均匀照亮 无开销
DirectionalLight 平行光线,无限远 中(阴影开销大)
PointLight 向四周发光,有衰减
SpotLight 锥形方向光,聚光效果 较高

纹理贴图

const textureLoader = new THREE.TextureLoader();

const material = new THREE.MeshStandardMaterial({
  map: textureLoader.load('diffuse.jpg'),          // 颜色贴图
  roughnessMap: textureLoader.load('roughness.jpg'), // 粗糙度贴图
  bumpMap: textureLoader.load('bump.jpg'),          // 凹凸贴图
  normalMap: textureLoader.load('normal.jpg'),       // 法线贴图
  envMap: textureLoader.load('environment.hdr'),     // 环境贴图(反射)
});

// CubeTextureLoader 用于 HDR 环境贴图(天空盒)
const cubeTextureLoader = new THREE.CubeTextureLoader();
scene.background = cubeTextureLoader.load([
  'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'
]);

交互(Raycaster)

const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();

// 点击检测
renderer.domElement.addEventListener('click', (event) => {
  // 将鼠标坐标映射到 NDC(-1 到 1)
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;

  raycaster.setFromCamera(pointer, camera);

  // 检测与哪些物体相交
  const intersects = raycaster.intersectObjects(scene.children);

  if (intersects.length > 0) {
    const clickedObject = intersects[0].object;
    console.log('点击了:', clickedObject);
    clickedObject.material.color.setHex(0xff0000); // 改变颜色
  }
});

动画循环与性能

import Stats from 'three/examples/jsm/libs/stats.module.js';

const stats = new Stats();
document.body.appendChild(stats.dom);

function animate() {
  requestAnimationFrame(animate);

  stats.begin();
  // 更新逻辑
  mesh.rotation.y += 0.01;
  renderer.render(scene, camera);
  stats.end();
}
性能优化 说明
stats.js FPS / 帧时间 / 内存实时监控
LOD(Level of Detail) 远处用小面数模型,近处用高精度模型
合批(Batching / Instancing) 相同几何体用 InstancedMesh,合并 draw calls
MergeGeometry 将多个静态几何体合并为一个,减少 draw calls
减少材质切换 按材质排序渲染,避免频繁切换 shader
限制 shadow map 大小 shadow.mapSize.width = 1024(默认 2048)