WebApi Canvas API + RxJS 基础快速学习/回顾

一、简介

文本主要回顾 Canvas API 基础,同时借助 ES6 class 与 RxJS 能力快速的完成 Canvas 基础。为什么选择 RxJS 因为 RxJS? 原因考虑到可以在订阅时行可以轻松的绘制和清除操作,重复进行可递归的进行操作,其他的内容都封装在 class (面向对象)或者 函数内部。学习 Canvas 和 RxJS 可以双向提高编程技巧。

二、创建 canvas 元素

  • 使用脚本创建 cavans 元素
ts 复制代码
class MyCanvas {
  constructor(width, height) {
    this.canvas = document.createElement('canvas');
    this.canvas.width = width;
    this.canvas.height = height;
    document.body.appendChild(this.canvas);
  }
}

const canvasInstance = new MyCanvas(400, 200);
  • 使用 html 创建 canvas 元素
html 复制代码
<canvas id="myCanvas" width="400" height="200"></canvas>

三、获取 canvas 元素

ts 复制代码
var canvas = document.getElementById('myCanvas');
var canvas = document.querySelector('canvas'); // 获取第一个匹配的Canvas元素

四、获取 canvas 元素中的上下文

ts 复制代码
var canvas = document.getElementById('myCanvas');

// 获取2D绘图上下文
var context = canvas.getContext('2d');

五、绘制基本形状

  • fillRect(x, y, width, height)填充矩形。
  • strokeRect(x, y, width, height)绘制矩形边框。
  • clearRect(x, y, width, height)清除矩形区域。
html 复制代码
<!DOCTYPE html>
<html>
<head>
  <title>Canvas 示例</title>
</head>
<body>
  <canvas id="myCanvas" width="400" height="200"></canvas>
  <script src="your_script.js"></script>
</body>
</html>
ts 复制代码
import { fromEvent } from 'rxjs';
import { filter, take } from 'rxjs/operators';

class MyCanvas {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext('2d');
  }
  // 绘制填充
  drawFilledRectangle() {
    this.context.fillStyle = 'blue';
    this.context.fillRect(50, 50, 200, 100);
  }
  // 绘制描边(边框)
  drawStrokedRectangle() {
    this.context.strokeStyle = 'red';
    this.context.lineWidth = 3;
    this.context.strokeRect(50, 50, 200, 100);
  }

  clearRectangle() {
    this.context.clearRect(100, 100, 100, 50);
  }

  draw() {
    fromEvent(document, 'DOMContentLoaded')
      .pipe(
        take(1),
        filter(() => this.canvas) // 在 this.canvas 只存在一次的时候触发
      )
      .subscribe(() => {
        this.drawFilledRectangle(); // 绘制填充
        this.drawStrokedRectangle(); // 绘制描边
        this.clearRectangle();
      });
  }
}

const MyCanvas = new MyCanvas('myCanvas');
MyCanvas.draw(); // 实例化开始绘制

六、绘制路径

  • beginPath():开始路径。
  • moveTo(x, y):将路径移动到指定点。
  • lineTo(x, y):绘制直线。
  • arc(x, y, radius, startAngle, endAngle, anticlockwise):绘制弧线。
  • closePath():关闭路径。
  • fill():填充路径区域。
  • stroke():绘制路径边框。
ts 复制代码
import { fromEvent } from 'rxjs';
import { filter, take } from 'rxjs/operators';

class MyCanvas {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext('2d');
  }

  drawPath() {
    this.context.beginPath();
    this.context.moveTo(100, 100);
    this.context.lineTo(200, 100);
    this.context.arc(150, 100, 50, 0, Math.PI, false);
    this.context.closePath();
  }

  fillPath() {
    this.context.fillStyle = 'blue';
    this.context.fill();
  }

  strokePath() {
    this.context.strokeStyle = 'red';
    this.context.stroke();
  }

  draw() {
    fromEvent(document, 'DOMContentLoaded')
      .pipe(
        take(1),
        filter(() => this.canvas)
      )
      .subscribe(() => {
        this.drawPath();
        this.fillPath();
        this.strokePath();
      });
  }
}

const MyCanvas = new MyCanvas('myCanvas');
MyCanvas.draw();

七、文本绘制

  • fillText(text, x, y):填充文本。
  • strokeText(text, x, y):绘制文本边框。
  • font:设置文本字体和大小。
  • textAlign:文本水平对齐方式。
  • textBaseline:文本垂直对齐方式。
ts 复制代码
import { fromEvent } from 'rxjs';
import { filter, take } from 'rxjs/operators';

class MyCanvas {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext('2d');
  }

  drawText() {
    this.context.font = '24px Arial'; // 设置字体和大小
    this.context.textAlign = 'center'; // 设置水平对齐方式
    this.context.textBaseline = 'middle'; // 设置垂直对齐方式
    this.context.fillText('Hello Canvas and RxJS', this.canvas.width / 2, this.canvas.height / 2);
    this.context.strokeText('Hello Canvas and RxJS', this.canvas.width / 2, this.canvas.height / 2);
  }

  initializeCanvas() {
    fromEvent(document, 'DOMContentLoaded')
      .pipe(
        take(1),
        filter(() => this.canvas)
      )
      .subscribe(() => {
        this.drawText();
      });
  }
}

const MyCanvas = new MyCanvas('myCanvas');
MyCanvas.initializeCanvas();

八、样式和颜色

  • fillStyle:设置填充样式。
  • strokeStyle:设置边框样式。
  • lineWidth:设置线条宽度。
  • globalAlpha:设置全局透明度。
ts 复制代码
import { fromEvent } from 'rxjs';
import { filter, take } from 'rxjs/operators';

class MyCanvas {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext('2d');
  }

  drawText() {
    this.context.font = '24px Arial'; // 设置字体和大小
    this.context.textAlign = 'center'; // 设置水平对齐方式
    this.context.textBaseline = 'middle'; // 设置垂直对齐方式
    this.context.fillStyle = 'blue'; // 设置填充颜色
    this.context.fillText('Hello, Canvas and RxJS!', this.canvas.width / 2, this.canvas.height / 2);
    this.context.strokeStyle = 'red'; // 设置边框颜色
    this.context.strokeText('Hello, Canvas and RxJS!', this.canvas.width / 2, this.canvas.height / 1.2);
  }

  draw() {
    fromEvent(document, 'DOMContentLoaded')
      .pipe(
        take(1),
        filter(() => this.canvas)
      )
      .subscribe(() => {
        this.drawText();
      });
  }
}

const MyCanvas = new MyCanvas('myCanvas');
MyCanvas.draw();

九、渐变和阴影

  • createLinearGradient(x0, y0, x1, y1):创建线性渐变对象。
  • createRadialGradient(x0, y0, r0, x1, y1, r1):创建径向渐变对象。
  • createPattern(image, repeat):创建图案。
  • shadowColorshadowBlurshadowOffsetX/Y:设置阴影效果。
ts 复制代码
import { fromEvent } from 'rxjs';
import { filter, take, switchMap } from 'rxjs/operators';

class MyCanvas {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext('2d');
  }

  createLinearGradient() {
    return this.context.createLinearGradient(50, 50, 250, 150);
  }

  createRadialGradient() {
    return this.context.createRadialGradient(300, 100, 10, 350, 100, 60);
  }

  createPattern() {
    return new Promise((resolve) => {
      const img = new Image();
      img.src = 'your_images.png'; // tips: 替换为实际图案的路径
      img.onload = () => {
        resolve(this.context.createPattern(img, 'repeat'));
      };
    });
  }

  drawLinearGradient(linearGradient) {
    linearGradient.addColorStop(0, 'red');
    linearGradient.addColorStop(1, 'blue');
    this.context.fillStyle = linearGradient;
    this.context.fillRect(50, 50, 200, 100);
  }

  drawRadialGradient(radialGradient) {
    radialGradient.addColorStop(0, 'green');
    radialGradient.addColorStop(1, 'yellow');
    this.context.fillStyle = radialGradient;
    this.context.beginPath();
    this.context.arc(300, 100, 50, 0, Math.PI * 2);
    this.context.fill();
  }

  async drawPattern() {
    const pattern = await this.createPattern();
    this.context.fillStyle = pattern;
    this.context.fillRect(50, 150, 300, 50);
  }

  drawShadow() {
    this.context.shadowColor = 'rgba(0, 0, 0, 0.5)';
    this.context.shadowBlur = 5;
    this.context.shadowOffsetX = 5;
    this.context.shadowOffsetY = 5;
    this.context.fillStyle = 'purple';
    this.context.fillRect(50, 50, 100, 100);
  }

  initializeCanvas() {
    fromEvent(document, 'DOMContentLoaded')
      .pipe(
        take(1),
        filter(() => this.canvas),
        switchMap(() => {
          this.drawLinearGradient(this.createLinearGradient());
          this.drawRadialGradient(this.createRadialGradient());
          return this.drawPattern();
        })
      )
      .subscribe(() => {
        this.drawShadow();
      });
  }
}

const MyCanvas = new MyCanvas('myCanvas');
MyCanvas.initializeCanvas();

十、图像操作

  • drawImage(image, x, y):绘制图像。
  • getImageData(x, y, width, height):获取像素数据。
  • putImageData(imageData, x, y):放置像素数据。
ts 复制代码
import { fromEvent } from 'rxjs';
import { filter, take, switchMap } from 'rxjs/operators';

class MyCanvas {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext('2d');
  }

  loadAndDrawImage(imagePath) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.src = imagePath;

      img.onload = () => {
        this.context.drawImage(img, 50, 50);
        resolve();
      };

      img.onerror = (error) => {
        reject(error);
      };
    });
  }

  getImageData(x, y, width, height) {
    return this.context.getImageData(x, y, width, height);
  }

  putImageData(imageData, x, y) {
    this.context.putImageData(imageData, x, y);
  }

  async initializeCanvas() {
    fromEvent(document, 'DOMContentLoaded')
      .pipe(
        take(1),
        filter(() => this.canvas),
        switchMap(async () => {
          await this.loadAndDrawImage('example-image.jpg'); // 替换为实际图像的路径
          const imageData = this.getImageData(50, 50, 200, 100);
          this.putImageData(imageData, 200, 100);
        })
      )
      .subscribe(() => {
        console.log('Canvas操作完成');
      });
  }
}

const MyCanvas = new MyCanvas('myCanvas');
MyCanvas.initializeCanvas();

十一、转换操作

  • scale(x, y):缩放。
  • rotate(angle):旋转。
  • translate(x, y):平移。
  • transform(a, b, c, d, e, f):自定义变换矩阵。
ts 复制代码
class CanvasTransform {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext('2d');
  }

  applyTransform(transformFn) {
    this.context.save();
    transformFn(this.context);
  }

  resetTransform() {
    this.context.restore();
  }

  drawRectangle(color, x, y, width, height) {
    this.context.fillStyle = color;
    this.context.fillRect(x, y, width, height);
  }

  async initializeCanvas() {
    this.drawRectangle('blue', 50, 50, 100, 50);

    this.applyTransform((context) => {
      context.scale(2, 1);
    });
    this.drawRectangle('red', 50, 50, 100, 50);

    this.applyTransform((context) => {
      context.rotate(Math.PI / 4);
    });
    this.drawRectangle('green', 50, 50, 100, 50);

    this.applyTransform((context) => {
      context.translate(100, 0);
    });
    this.drawRectangle('orange', 50, 50, 100, 50);

    this.context.setTransform(1, 0.5, 0.5, 1, 50, 50);
    this.drawRectangle('purple', 50, 50, 100, 50);

    this.resetTransform();
  }
}

const MyCanvas = new CanvasTransform('myCanvas');
MyCanvas.initializeCanvas();

十三、清除 canvas

使用 clearRect()fillRect() 可以清除整个 <canvas> 或特定区域。

13.1) 整个 canvas

ts 复制代码
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

// 清除整个 canvas
context.clearRect(0, 0, canvas.width, canvas.height);

13.2) 特定区域

ts 复制代码
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

// 清除特定区域,例如清除一个矩形区域
context.clearRect(50, 50, 100, 50);

十四、事件处理

Canvas 允许添加事件监听器来处理用户交互,如点击、鼠标移动等。

14.1) 点击事件

ts 复制代码
import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

const canvas = document.getElementById('myCanvas');

const canvasClick$ = fromEvent(canvas, 'click').pipe(
  map((event) => {
    const { left, top } = canvas.getBoundingClientRect();
    const x = event.clientX - left;
    const y = event.clientY - top;
    return { x, y };
  })
);

canvasClick$.subscribe(({ x, y }) => {
  console.log(`Clicked at x: ${x}, y: ${y}`);
  // 在这里执行您的操作,例如根据坐标执行Canvas绘制操作
});

14.2) 鼠标移动事件

ts 复制代码
import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

const canvas = document.getElementById('myCanvas');

const canvasMouseMove$ = fromEvent(canvas, 'mousemove').pipe(
  map((event) => {
    const { left, top } = canvas.getBoundingClientRect();
    const x = event.clientX - left;
    const y = event.clientY - top;
    return { x, y };
  })
);

canvasMouseMove$.subscribe(({ x, y }) => {
  console.log(`Mouse moved to x: ${x}, y: ${y}`);
  // 在这里执行您的操作,例如根据坐标执行Canvas绘制操作
});

14.3) 键盘事件

ts 复制代码
import { fromEvent } from 'rxjs';

const keyDown$ = fromEvent(document, 'keydown');
const keyUp$ = fromEvent(document, 'keyup');

keyDown$.subscribe((event) => {
  console.log(`Key down: ${event.key}`);
});

keyUp$.subscribe((event) => {
  console.log(`Key up: ${event.key}`);
});

14.4) 触摸事件

ts 复制代码
import { fromEvent } from 'rxjs';

const touchStart$ = fromEvent(document, 'touchstart');
const touchMove$ = fromEvent(document, 'touchmove');
const touchEnd$ = fromEvent(document, 'touchend');

touchStart$.subscribe((event) => {
  console.log('Touch start');
});

touchMove$.subscribe((event) => {
  console.log('Touch move');
});

touchEnd$.subscribe((event) => {
  console.log('Touch end');
});

十五、动画

通过定时器或 requestAnimationFrame 可以实现动画效果。

ts 复制代码
import { interval } from 'rxjs';
import { map, takeWhile, startWith } from 'rxjs/operators';

class CanvasAnimation {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext('2d');
    this.x = 50;
    this.y = 50;
    this.speed = 2;
    this.animationFrameId = null;
  }

  animate() {
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.context.fillStyle = 'blue';
    this.context.fillRect(this.x, this.y, 50, 50);
    this.x += this.speed;

    if (this.x + 50 > this.canvas.width || this.x < 0) {
      this.speed = -this.speed;
    }

    this.animationFrameId = requestAnimationFrame(() => this.animate());
  }

  start() {
    const animation$ = interval(16).pipe( // 每 16 ms 触发一次
      map(() => {
        this.animate();
        return this.x;
      }),
      startWith(this.x),
      takeWhile(() => this.animationFrameId !== null)
    );

    this.subscription = animation$.subscribe();
  }

  stop() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }

    if (this.animationFrameId) {
      cancelAnimationFrame(this.animationFrameId);
    }
  }
}

const canvasAnimation = new CanvasAnimation('myCanvas');
canvasAnimation.start();

// 如果需要停止动画,可以调用 canvasAnimation.stop();

十六、像素处理

可以直接访问和修改 <canvas> 上的像素数据,用于图像处理、滤镜等。

ts 复制代码
import { interval, fromEvent } from 'rxjs';
import { map, startWith, switchMap } from 'rxjs/operators';

class CanvasAnimation {
  constructor() {
    this.canvas = document.getElementById('myCanvas');
    this.context = this.canvas.getContext('2d');
    this.x = 50;
    this.speed = 2;
  }

  start() {
    const canvasMouseMove$ = fromEvent(this.canvas, 'mousemove').pipe(
      map((event) => {
        const { left, top } = this.canvas.getBoundingClientRect();
        return event.clientX - left;
      })
    );

    // 使用 RxJS 来创建一个动画
    const animation$ = canvasMouseMove$.pipe(
      switchMap((mouseX) => {
        return interval(16).pipe(
          map(() => {
            // 清除画布
            this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

            // 绘制方块
            this.context.fillStyle = 'blue';
            this.context.fillRect(this.x, 50, 50, 50);

            // 更新方块位置
            if (this.x < mouseX) {
              this.x += this.speed;
            } else if (this.x > mouseX) {
              this.x -= this.speed;
            }

            // 通过 requestAnimationFrame 请求下一帧动画
            requestAnimationFrame(() => this.animate());

            return this.x;
          }),
          startWith(this.x)
        );
      })
    );

    animation$.subscribe();
  }

  animate() {
    // 空函数,用于 requestAnimationFrame 的回调
  }
}

const canvasAnimation = new CanvasAnimation();
canvasAnimation.start();

十七、Canvas 库类推荐

库/框架名称 主要特点和用途
Fabric.js Javascript Canvas 库、SVG 到 Canvas(和 canvas 到 SVG)解析器。
Konva.js Konva.js 是一个 HTML5 Canvas JavaScript 框架,它通过为桌面和移动应用程序启用画布交互性来扩展 2d 上下文。
p5.js p5.js 是一个客户端 JS 平台,使艺术家、设计师、学生和任何人都能学习编码并在网络上创造性地表达自己。
Three.js 用于创建3D的库
EaselJS 用于创建交互性和动画的库,提供易于理解的显示列表模型。
Phaser 流行的HTML5游戏引擎,包括Canvas支持和游戏开发功能。

十八、nodejs canvas

  • node-canvas Node canvas 是 Cairo 支持的 NodeJS Canvas 实现。

十九、知名项目

  • canvas-confetti 🎉 浏览器中的高性能五彩纸屑动画
  • excalidraw 用于绘制手绘图的虚拟白板。
  • Chart.js 使用 <canvas> 标签的简单 HTML5 图表。
  • echarts Apache ECharts 是一个强大的、交互式的浏览器图表和数据可视化库。
  • d3 使用 SVG、 Canvas*和 HTML让数据栩栩如生。

二十、小结

本文主要讲解的 Web API Canvas API, 为了避免枯燥的学习,配合 RxJS 与 ES Class 来组合成 API 适用示例。学习本文除了有 Canvas 基础外还需要有 RxJS 基础和 ES class 面向对象基础。

相关推荐
we19a0sen1 小时前
npm 常用命令及示例和解析
前端·npm·node.js
倒霉男孩3 小时前
HTML视频和音频
前端·html·音视频
喜欢便码3 小时前
JS小练习0.1——弹出姓名
java·前端·javascript
暗暗那3 小时前
【面试】什么是回流和重绘
前端·css·html
小宁爱Python3 小时前
用HTML和CSS绘制佩奇:我不是佩奇
前端·css·html
Asthenia04124 小时前
为什么说MVCC无法彻底解决幻读的问题?
后端
Asthenia04124 小时前
面试官问我:三级缓存可以解决循环依赖的问题,那两级缓存可以解决Spring的循环依赖问题么?是不是无法解决代理对象的问题?
后端
Asthenia04124 小时前
面试复盘:使用 perf top 和火焰图分析程序 CPU 占用率过高
后端
Asthenia04124 小时前
面试复盘:varchar vs char 以及 InnoDB 表大小的性能分析
后端
weifexie4 小时前
ruby可变参数
开发语言·前端·ruby