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 面向对象基础。

相关推荐
星就前端叭42 分钟前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234521 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成1 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript
小林coding2 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者2 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu
jwensh2 小时前
【Jenkins】Declarative和Scripted两种脚本模式有什么具体的区别
运维·前端·jenkins
关你西红柿子2 小时前
小程序app封装公用顶部筛选区uv-drop-down
前端·javascript·vue.js·小程序·uv
益达是我2 小时前
【Chrome】浏览器提示警告Chrome is moving towards a new experience
前端·chrome
济南小草根2 小时前
把一个Vue项目的页面打包后再另一个项目中使用
前端·javascript·vue.js
从善若水2 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust