canvas 元素绑定事件_canvas 进阶 —— 如何实现 canvas 的事件系统

本文探讨如何在canvas上实现事件系统,解决图形元素的事件绑定和鼠标命中问题。通过构建Shape和Stage,设计API,利用canvas特性解决复杂图形的鼠标选择判断,从而实现交互式canvas应用。
摘要由CSDN通过智能技术生成

538bafbb8c159021382b5a25fca381a6.png

众所周知,canvas 是前端进军可视化领域的一大利器,借助 canvas 画布我们不仅可以实现许多 dom 和 css 难以实现的、各种绚丽多彩的视觉效果,而且在渲染数量繁多、内容复杂的场景下,其性能表现及优化空间也占据一定优势。

然而 canvas 却存在一个缺陷:由于 canvas 是作为一个整体画布存在,所有的内容只不过是其内部渲染的结果,我们不能像在 dom 元素上监听事件一样,在 canvas 所渲染的图形内绑定各种事件,因此基于 canvas 画布开发出一套交互式应用是件复杂的事情。虽然 gayhub 上很多 canvas 框架自带了事件系统,但如果想深入学习 canvas,笔者认为还是有必要了解其实现原理,因此本篇文章将实现一个简易版的 canvas 事件系统。

正文开始前,先贴下仓库地址,各位按需取用 canvas-event-system

环境搭建

要在 canvas 上实现事件系统,我们必须先做些准备工作 —— 首先我们得往 canvas 上填充些“内容”,没有内容,谈何事件监听,下文我们将这些可绑定事件的内容称之为元素。同时,为简明扼要,笔者这里仅实现了形状(Shape) 这一类元素;当我们有了一个个元素后,我们还需要一个容器去管理它们,这个容器则是 —— 舞台(Stage),舞台如同上帝一般,负责元素们的渲染、事件管理及事件触发,接下来我们先初始化这两大类

API 设计

在实现细节前,笔者是这样设想事件系统的:我们可以通过 new 操作符生成一个个的 Shape 实例,并可在实例上监听各类事件,然后再将它们addStage即可,就像这样:

const stage = new Stage(myCanvas);

// 生成形状
const rect = new Rect(props); // 矩形
const circle = new Rect(props); // 圆形

// 监听点击事件
rect.on('click', () => console.log('click rect!'));
circle.on('click', () => console.log('click circle!'));

// 将形状添加至舞台,即可渲染到画布上
stage.add(rect);
stage.add(circle);

构建 Shape

由于不同形状间有许多相似的逻辑,因此我们先实现一个Base基类,然后让诸如RectCircle等形状继承此类:

import { Listener, EventName, Shape } from './types';

export default class Base implements Shape {
  private listeners: { [eventName: string]: Listener[] };

  constructor() {
    this.listeners = {};
  }

  draw(ctx: CanvasRenderingContext2D): void {
    throw new Error('Method not implemented.');
  }

  on(eventName: EventNames, listener: Listener): void {
    if (this.listeners[eventName]) {
      this.listeners[eventName].push(listener);
    } else {
      this.listeners[eventName] = [listener];
    }
  }

  getListeners(): { [name: string]: Listener[] } {
    return this.listeners;
  }
}

Base有三个对外暴露的 api:

  • draw 用于绘制内容,需要将 canvas 上下文 CanvasRenderingContext2D 传入
  • on 用于事件监听,收集到的事件回调会以事件名eventName为 key,回调函数数组为 value 的形式存放在一个对象当中,此外我们还用了枚举类型定义了所有事件

typescript export enum EventNames { click = 'click', mousedown = 'mousedown', mousemove = 'mousemove', mouseup = 'mouseup', mouseenter = 'mouseenter', mouseleave = 'mouseleave', }

  • getListeners 获取此形状上所有的监听事件

有了Base基类,我们就可以轻松定义其他具体的形状:

比如Rect

import Base from './Base';

interface RectProps {
  x: number;
  y: number;
  width: number;
  height: number;
  strokeWidth?: number;
  strokeColor?: string;
  fillColor?: string;
}

export default class Rect extends Base {
  constructor(private props: RectProps) {
    super();
    this.props.fillColor = this.props.fillColor || '#fff';
    this.props.strokeColor = this.props.strokeColor || '#000';
    this.props.strokeWidth = this.props.strokeWidth || 1;
  }

  draw(ctx: CanvasRenderingContext2D) {
    const { x, y, width, height, strokeColor, strokeWidth, fillColor } =
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值