使用perfect-freehand插件实现canvas签名&画板

插件文档地址:https://www.npmjs.com/package/perfect-freehand

插件体验地址:https://perfect-freehand-example.vercel.app/

这个包导出一个名为getStroke的函数,该函数将根据鼠标点数组转变为多边形生成点.。

文末贴有完整组件的代码

1.数据准备

<template>
  <div>
    <canvas
      ref="canvas"
      @pointerdown="handlePointerDown"
      @pointermove="handlePointerMove"
      @pointerup="handlePointerUp"
    />
    <div>
      颜色:
      <el-input
        v-model="color"
        style="width: 100px"
        type="color"
        @change="changeColor"
      />
      粗细:<el-select
        v-model="size"
        style="width: 100px"
        placeholder="请选择"
        @change="changeSize"
      >
        <el-option
          v-for="item in [4, 8, 16, 24]"
          :key="item"
          :label="item"
          :value="item"
        />
      </el-select>
      <el-button @click="reset">重置</el-button>
      <el-button @click="save">保存</el-button>
      <el-button @click="revoke">撤销</el-button>
    </div>
  </div>
</template>

<script>
import { getStroke } from "perfect-freehand";
import getSvgPathFromStroke from "./util";
const SIZE = 8; //画笔大小
const COLOR = "#000"; //画笔颜色
export default {
  data() {
    return {
      size: SIZE,
      color: COLOR,
      isDrawing: false,
      points: [], //当前绘制中的坐标点
      path: null, // 当前绘制中的线条路径
      pathAll: [], //所有绘制的坐标点
      canvas: null,
      ctx: null,
      options: {
        size: SIZE,
        thinning: 0.5,
        smoothing: 0.5,
        streamline: 0.5,
      },
    };
  },
  mounted() {
    this.canvas = this.$refs.canvas;
    this.canvas.width = 500;
    this.canvas.height = 300;
    this.ctx = this.canvas.getContext("2d");
  },
  methods: {
    handlePointerDown(e) {
     
    },
    handlePointerMove(e) {
    
    },
    handlePointerUp() {
     
    },
    // 重新渲染画布
    reRender() {
     
    },
    // 重置
    reset() {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.pathAll = [];
      this.points = [];
      this.size = SIZE;
      this.color = COLOR;
      this.options.size = this.size;
    },
    // 撤销
    revoke() {
      
    },
    // 保存
    save() {
      const dataURL = this.canvas.toDataURL("image/png");
      console.log("dataURL: ", dataURL);
    },
    changeColor(value) {
      this.color = value;
    },
    changeSize(value) {
      this.options.size = value;
    },
  },
};
</script>

<style>
canvas {
  border: 1px solid #000;
}
</style>

2.获取鼠标点,转换为路径点

2-1.鼠标移入点击设置初始点

 handlePointerDown(e) {
      this.isDrawing = true;
      e.target.setPointerCapture(e.pointerId);
      this.points = [[e.offsetX, e.offsetY, e.pressure]];
    },

2-2.鼠标开始滑动时,绘制路径 [核心操作]

思路:获取鼠标在canvas的点,调用插件提供getStroke方法获取画笔坐标点,调用getSvgPathFromStroke方法转换成path数据绘制 (getSvgPathFromStroke方法在插件官网有提供)

 handlePointerMove(e) {
      if (e.buttons !== 1) return;
      this.points.push([e.offsetX, e.offsetY, e.pressure]);
      this.ctx.fillStyle = this.color; //设置当前画笔颜色
      const stroke = getStroke(this.points, this.options);
      const pathData = getSvgPathFromStroke(stroke);
      const myPath = new Path2D(pathData);
      this.ctx.fill(myPath);
    },

2-3.鼠标抬起

   handlePointerUp() {
      this.isDrawing = false
    },

以上方法可以进行简单的绘画操作

如果不在乎锯齿的,以上的方法可以凑合当一个电子签名来用啦!

3.修复锯齿

就是在handlePointerMove中绘制的时候,清除上一次的操作

methods:{
     handlePointerMove(e) {
      if (e.buttons !== 1) return;
      this.points.push([e.offsetX, e.offsetY, e.pressure]);
      // 防止锯齿==start 1.清除画布 2.重新渲染直接的路径
      this.reRender();
      // ==end
      this.ctx.fillStyle = this.color; //设置当前画笔颜色
      const stroke = getStroke(this.points, this.options);
      const pathData = getSvgPathFromStroke(stroke);
      const myPath = new Path2D(pathData);
      this.ctx.fill(myPath);
    },
    // 重新渲染画布
    reRender() {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
}

这时候你会发现,锯齿是没有了,但是你上一次的记录也同样在下一次绘画的时候,也被清除了,所以我们需要保存一下之前的绘画path数据

4.保存之前绘画数据

methods:{
    handlePointerMove(e){
        //...之前的代码
        // 保存一下path
       this.path = myPath;
    },
    handlePointerUp() {
       //...之前的代码
      this.pathAll.push({
        path: this.path,
        color: this.color,
      });
    },
}

4-1.在绘制的时候,重新渲染一下pathAll的数据

   // 重新渲染画布
    reRender() {
     //...之前代码
      this.pathAll.forEach((item) => {
        this.ctx.fillStyle = item.color; //设置画笔颜色
        this.ctx.fill(item.path); //填充路径
      });
    },

到目前为止,就可以渲染一个流畅的电子签字板啦!

5.更改画笔颜色

其实前面写的时候有加入color的字段,只需要我们更改一下color的变量即可

    changeColor(value) {
      this.color = value;
    },

6.更改画笔大小

这个其实插件给出了变量option中的size,option里面还有还多字段,具体可以看官网

   changeSize(value) {
      this.options.size = value;
    },

7.绘制内容转为图片数据

    // 保存
    save() {
      const dataURL = this.canvas.toDataURL("image/png");
      console.log("dataURL: ", dataURL);
    },

8.撤销

前面我们有存储过页面所有绘制path的数据,撤销只需要讲path数组的最后一个去掉,然后重新渲染一次画布

    // 撤销
    revoke() {
      if (this.pathAll.length == 0) return;
      this.pathAll.pop();
      this.reRender();
    },

9.重置

数据的全部初始化

    // 重置
    reset() {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.pathAll = [];
      this.points = [];
      this.size = SIZE;
      this.color = COLOR;
      this.options.size = this.size;
    },

以上就是一个完整的绘制面板代码思路,

10.完整版代码

<template>
  <div>
    <canvas
      ref="canvas"
      @pointerdown="handlePointerDown"
      @pointermove="handlePointerMove"
      @pointerup="handlePointerUp"
    />
    <div>
      颜色:
      <el-input
        v-model="color"
        style="width: 100px"
        type="color"
        @change="changeColor"
      />
      粗细:<el-select
        v-model="size"
        style="width: 100px"
        placeholder="请选择"
        @change="changeSize"
      >
        <el-option
          v-for="item in [4, 8, 16, 24]"
          :key="item"
          :label="item"
          :value="item"
        />
      </el-select>
      <el-button @click="reset">重置</el-button>
      <el-button @click="save">保存</el-button>
      <el-button @click="revoke">撤销</el-button>
    </div>
  </div>
</template>

<script>
import { getStroke } from "perfect-freehand";
import getSvgPathFromStroke from "./util";
const SIZE = 8; //画笔大小
const COLOR = "#000"; //画笔颜色
export default {
  data() {
    return {
      size: SIZE,
      color: COLOR,
      isDrawing: false,
      points: [], //当前绘制中的坐标点
      path: null, // 当前绘制中的线条路径
      pathAll: [], //所有绘制的坐标点
      canvas: null,
      ctx: null,
      options: {
        size: SIZE,
        thinning: 0.5,
        smoothing: 0.5,
        streamline: 0.5,
      },
    };
  },
  mounted() {
    this.canvas = this.$refs.canvas;
    this.canvas.width = 500;
    this.canvas.height = 300;
    this.ctx = this.canvas.getContext("2d");
  },
  methods: {
    handlePointerDown(e) {
      this.isDrawing = true;
      e.target.setPointerCapture(e.pointerId);
      this.points = [[e.offsetX, e.offsetY, e.pressure]];
    },
    handlePointerMove(e) {
      if (e.buttons !== 1) return;
      this.points.push([e.offsetX, e.offsetY, e.pressure]);
      // 防止锯齿==start 1.清除画布 2.重新渲染直接的路径
      this.reRender();
      // ==end
      this.ctx.fillStyle = this.color; //设置当前画笔颜色
      const stroke = getStroke(this.points, this.options);
      const pathData = getSvgPathFromStroke(stroke);
      const myPath = new Path2D(pathData);
      this.ctx.fill(myPath);
      // 保存一下path
      this.path = myPath;
    },
    handlePointerUp() {
      this.isDrawing = false;
      this.pathAll.push({
        path: this.path,
        color: this.color,
      });
    },
    // 重新渲染画布
    reRender() {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.pathAll.forEach((item) => {
        this.ctx.fillStyle = item.color; //设置画笔颜色
        this.ctx.fill(item.path); //填充路径
      });
    },
    // 重置
    reset() {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.pathAll = [];
      this.points = [];
      this.size = SIZE;
      this.color = COLOR;
      this.options.size = this.size;
    },
    // 撤销
    revoke() {
      if (this.pathAll.length == 0) return;
      this.pathAll.pop();
      this.reRender();
    },
    // 保存
    save() {
      const dataURL = this.canvas.toDataURL("image/png");
      console.log("dataURL: ", dataURL);
    },
    changeColor(value) {
      this.color = value;
    },
    changeSize(value) {
      this.options.size = value;
    },
  },
};
</script>

<style>
canvas {
  border: 1px solid #000;
}
</style>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值