You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas绘图报错:Cannot read property 'beginPath' of null 求助

修复Canvas绘图时的TypeError: Cannot read property 'beginPath' of null错误

我来帮你解决这个问题!你遇到的错误提示本质是**context对象还没初始化就被调用了**,同时代码里还有几个细节需要调整,咱们一步步来修复:

错误根源分析

  1. Context初始化时机太晚:目前你只在dessin方法里初始化this.context = this.canvas.getContext("2d"),但触发触摸/鼠标按下事件时,activationDessin会先执行,这时候context还是null,调用this.context.beginPath()自然报错。
  2. 全局变量污染问题:你在activationDessin和deplacementSouris里用的positionX/positionY是全局变量,不是Signature对象的属性,容易导致作用域混乱和取值错误。
  3. 触摸事件的this指向错误:触摸事件绑定未使用bind(Signature),导致convertTouchEvent里的this指向canvas元素而非Signature对象,后续事件触发逻辑会出问题。

修复步骤

1. 提前初始化Canvas Context

把context的初始化移到对象创建阶段,确保任何绘图方法执行前context已存在:

var Signature = {
 // Attributs
 ecriture : false,
 canvas : document.getElementById("signature"),
 // 直接初始化context
 context : document.getElementById("signature").getContext("2d"),
 signatureImg : null,
 // 改为对象属性,避免全局变量
 positionX : null,
 positionY: null,
 // 新增初始化方法,统一设置绘图参数
 init: function() {
  this.context.lineWidth = 5;
 },
 // ... 其他方法
}

然后在代码末尾调用初始化:

Signature.init();

2. 修复变量作用域问题

把activationDessin和deplacementSouris里的全局变量改为对象自身属性:
修改activationDessin方法:

activationDessin : function() {
 this.ecriture = true;
 this.context.beginPath();
 // 使用对象自身的position属性
 this.context.moveTo(this.positionX, this.positionY);
},

修改deplacementSouris方法:

deplacementSouris : function(event) {
 const sourisPosition = this.getMousePos(event);
 // 赋值给对象属性
 this.positionX = sourisPosition.x;
 this.positionY = sourisPosition.y;
 this.dessin(this.positionX, this.positionY);
},

3. 修复触摸事件的this指向

绑定触摸事件时用bind(Signature)确保this指向Signature对象,同时修正dispatchEvent的目标:

// 触摸事件绑定修改为
Signature.canvas.addEventListener("touchstart", Signature.convertTouchEvent.bind(Signature));
Signature.canvas.addEventListener("touchmove", Signature.convertTouchEvent.bind(Signature));
Signature.canvas.addEventListener("touchend", Signature.convertTouchEvent.bind(Signature));

然后修改convertTouchEvent里的事件触发行:

// 替换原来的this.dispatchEvent(mouse_ev)
ev.target.dispatchEvent(mouse_ev);

完整修复后的代码

var Signature = {
 // Attributs
 ecriture : false,
 canvas : document.getElementById("signature"),
 context : document.getElementById("signature").getContext("2d"),
 signatureImg : null,
 positionX : null,
 positionY: null,
 // 初始化方法
 init: function() {
  this.context.lineWidth = 5;
 },
 // 绘图方法
 dessin : function(positionX, positionY) {
  if(this.ecriture){
   this.context.lineTo(positionX, positionY);
   this.context.stroke();
  }
 },
 activationDessin : function() {
  this.ecriture = true;
  this.context.beginPath();
  this.context.moveTo(this.positionX, this.positionY);
 },
 // 获取鼠标/触摸位置
 getMousePos : function(event) {
  const rect = this.canvas.getBoundingClientRect();
  return{
   x:event.clientX - rect.left,
   y:event.clientY - rect.top
  };
 },
 // 鼠标移动监听
 deplacementSouris : function(event) {
  const sourisPosition = this.getMousePos(event);
  this.positionX = sourisPosition.x;
  this.positionY = sourisPosition.y;
  this.dessin(this.positionX, this.positionY);
 },
 // 停止绘图
 desactivationDessin : function() {
  this.ecriture = false;
 },
 // 触摸事件转鼠标事件
 convertTouchEvent : function(ev) {
  var touch, ev_type, mouse_ev;
  touch = ev.targetTouches[0];
  ev.preventDefault();
  switch (ev.type) {
   case 'touchstart':
    if (ev.targetTouches.length != 1) {
     return;
    }
    touch = ev.targetTouches[0];
    ev_type = 'mousedown';
    break;
   case 'touchmove':
    if (ev.targetTouches.length != 1) {
     return;
    }
    touch = ev.targetTouches[0];
    ev_type = 'mousemove';
    break;
   case 'touchend':
    if (ev.changedTouches.length != 1) {
     return;
    }
    touch = ev.changedTouches[0];
    ev_type = 'mouseup';
    break;
   default:
    return;
  }
  mouse_ev = document.createEvent("MouseEvents");
  mouse_ev.initMouseEvent(
   ev_type,
   true, true, window,
   0,
   touch.screenX,
   touch.screenY,
   touch.clientX,
   touch.clientY,
   ev.ctrlKey,
   ev.altKey,
   ev.shiftKey,
   ev.metaKey,
   0,
   null
  );
  ev.target.dispatchEvent(mouse_ev);
 },
 // 清除画布
 clearCanvas : function() {
  // 使用canvas自身宽高,适配尺寸变化
  this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
 }
}

// 初始化画布
Signature.init();

// PC端事件绑定
Signature.canvas.addEventListener("mousedown", Signature.activationDessin.bind(Signature));
Signature.canvas.addEventListener("mousemove", Signature.deplacementSouris.bind(Signature));
Signature.canvas.addEventListener("mouseup", Signature.desactivationDessin.bind(Signature));

// 移动端触摸事件绑定
Signature.canvas.addEventListener("touchstart", Signature.convertTouchEvent.bind(Signature));
Signature.canvas.addEventListener("touchmove", Signature.convertTouchEvent.bind(Signature));
Signature.canvas.addEventListener("touchend", Signature.convertTouchEvent.bind(Signature));

// 清除按钮绑定
document.getElementById("boutonEffacer").addEventListener("click", function() {
 Signature.clearCanvas();
});

额外优化

我把clearCanvas里的固定宽高改成了this.canvas.width和this.canvas.height,这样画布尺寸变化时清除逻辑也能自动适配。

内容的提问来源于stack exchange,提问作者Koscielski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:46:50