vue+canvas实现手写签字画板

简介: vue+canvas实现手写签字画板

vue+canvas实现手写签字画板

效果

预览

体验地址

用途

  • 涂鸦画板
  • 签名板

实现

使用canvas,通过监听鼠标动作来进行绘图。

关键代码如下:

drawing(event){
    if(!this.penClick) return;//鼠标按下状态
  const canvas = document.getElementById('canvas'); //获取canvas标签
  const ctx = canvas.getContext("2d");//创建 contextconst canvas = document.getElementById('canvas');  对象
    const stopAxisX = event.pageX;//当前鼠标x坐标
    const stopAxisY = event.pageY;//当前鼠标y坐标
  const left = document.getElementById('leftMenu');//可忽略
  const lw = left && left.offsetWidth ? (left.offsetWidth || 0) / 2 : 0;
    ctx.beginPath();//是 Canvas 2D API 通过清空子路径列表开始一个新路径的方法。 当你想创建一个新的路径时,调用此方法。
  const wsaW = window.screen.availWidth;
  const cl = canvas.offsetLeft;
  const ct = canvas.offsetTop;
    ctx.moveTo(this.startAxisX-cl,this.startAxisY - ct);//moveTo(x,y) 定义线条开始坐标
    ctx.lineTo(stopAxisX-cl,stopAxisY - ct  );//lineTo(x,y) 定义线条结束坐标
    ctx.strokeStyle = this.penColor;
    ctx.lineWidth = this.penWidth;
    ctx.lineCap = "round";
    ctx.stroke();// stroke() 方法来绘制线条
    this.startAxisX = stopAxisX;
    this.startAxisY = stopAxisY;
}

代码

JCanvasBroad.vue
<template>
  <div id="canvas-broad">
      <canvas id="canvas" :width="width" :height="height">浏览器不支持canvas<!-- 如果不支持会显示这段文字 --></canvas>
      <j-tab-bar v-if="toolsTabList"
        :tabList="tabList"
        :showTab="showTab">
      <template v-slot:back-ground-color>
        <div class="section">
            <span class="info">设置背景颜色:</span>
          <input class="btn input-color" type="color" v-model="brackGroudColor" />
        </div>
      </template>
      <template v-slot:pen-color>
        <div class="section">
            <span class="info">选择画笔颜色:</span>
          <input class="btn input-color" type="color" v-model="penColor" />
        </div>
      </template>
      <template v-slot:eraser>
        <div class="section">
            <span class="info">选择橡皮擦:</span>
            <button class="btn colorBtn" :style="'background-color:' + brackGroudColor + ';'" @click='setPenColor();'>{{brackGroudColor}}</button>
        </div>
        <div class="section">
          <button class="btn" @click="setBackGround()">清空画布</button>
        </div>
      </template>
      <template v-slot:pen-size>
        <div class="section">
          <span class="info">选择画笔大小:</span>
          <progress :value="progressValue" 
              style="cursor: pointer;"
              id="progress"
              max="1" 
              :title="progressValue * 100 +'%'"
              @click="setPenWidth">
          </progress>
          <span style="margin-left: 0.3125rem;">{{20 * progressValue}}px</span>
        </div>
      </template>
      <template v-slot:export>
        <div class="section">
          <span class="info">输出画板内容到下面的图片:</span>
          <button class="btn" @click="createImage();">EXPORT</button>
        </div>
        <img id="image_png">
      </template>
    </j-tab-bar>
    <template v-if="!toolsTabList">
      <div class="section">
        <button class="btn" @click="setBackGround()">清空画布</button>
      </div>
      <div class="section">
        <span class="info">选择画笔颜色:</span>
        <input class="input-color" type="color" v-model="penColor" />
      </div>
      <div class="section">
        <span class="info">设置背景颜色:</span>
        <input class="input-color" type="color" v-model="brackGroudColor" />
      </div>
      <div class="section">
        <span class="info">选择橡皮擦:</span>
        <button class="btn colorBtn" :style="'background-color:' + brackGroudColor + ';'" @click='setPenColor();'>{{brackGroudColor}}</button>
      </div>
      <div class="section">
        <span class="info">选择画笔大小:</span>
        <progress :value="progressValue" 
            style="cursor: pointer;"
            id="progress"
            max="1" 
            :title="progressValue * 100 +'%'"
            @click="setPenWidth">
        </progress>
        <span style="margin-left: 0.3125rem;">{{20 * progressValue}}px</span>
      </div>
      <div class="section">
        <span class="info">输出画板内容到下面的图片:</span>
        <button class="btn" @click="createImage();">EXPORT</button>
      </div>
      <img id="image_png">
    </template>
  </div>
</template>
<script>
  import JTabBar from '../../pagesTools/JTabBar.vue';
  export default{
    name:'JCanvasBroad',
    props:{
      height:{
        type:Number,
        default:-1
      },
      width:{
        type:Number,
        default:-1
      },
      defaultPenColor:{
        type:String,
        default:'#000000'
      },
      defaultPenSize:{
        type:Number,
        default:4
      },
      defaultBackGroundColor:{
        type:String,
        default:"#ffffff"
      },
      toolsTabList:{
        type:Boolean,
        default:false
      }
    },
    components:{
      JTabBar
    },
    watch:{
      brackGroudColor:{
        handler(newVal,oldVal){
          this.setBackGround();
        }
      }
    },
    data() {
      return{
        penColor:"#000000",
        penWidth:4,
        penClick:false,
        startAxisX:0,
        startAxisY:0,
        brackGroudColor:"#ffffff",
        progressValue:0.2,
        tabList:[{
          label:'背景颜色',
          id:'back-ground-color'
        },{
          label:'画笔颜色',
          id:'pen-color'
        },{
          label:'橡皮擦',
          id:'eraser'
        },{
          label:'画笔大小',
          id:'pen-size'
        },{
          label:'导出图片',
          id:'export'
        }],
        showTab:0
      }
    },
    created(){
    },
    mounted() {
      this.init();
    },
    methods:{
      //页面初始化
      init(){
        let height = this.height;
        let width = this.width;
        if(width == -1){
          const cbw = document.getElementById('canvas-broad');
          width = cbw.offsetWidth * 0.9;
          height = cbw.offsetHeight * 0.6;
          this.width = width;
          this.height = height;
        }
        this.penColor = this.defaultPenColor;
        this.brackGroudColor = this.defaultBackGroundColor;
        this.penWidth = this.defaultPenSize;
        let canvas = document.getElementById('canvas'); //获取canvas标签
        let ctx = canvas.getContext("2d");//创建 context 对象
        ctx.fillStyle = this.brackGroudColor;//画布背景色
        ctx.fillRect(0,0,width,height);//在画布上绘制 width * height 的矩形,从左上角开始 (0,0)
        canvas.addEventListener("mousemove",this.drawing); //鼠标移动事件
        canvas.addEventListener("mousedown",this.penDown); //鼠标按下事件
        canvas.addEventListener("mouseup",this.penUp); //鼠标弹起事件
      },
      getWidthSelect(width){
        if(width == this.penWidth){
          return "btn bg penBtn fw"
        }
        return "btn bg penBtn"
      },
      getColorSelect(color){
        if(color == this.penColor){
          return 'btn colorBtn fw'
        }
        return 'btn colorBtn';
      },
      setBackGround(){
        const canvas = document.getElementById('canvas'); //获取canvas标签
        const ctx = canvas.getContext("2d");//创建 context 对象
        ctx.fillStyle = this.brackGroudColor;//画布背景色
        ctx.fillRect(0,0,this.width,this.height);//在画布上绘制 600x300 的矩形,从左上角开始 (0,0)
      },
      setPenWidth(event){
        const progress = document.getElementById('progress');
        this.progressValue = (event.pageX - progress.offsetLeft) / progress.offsetWidth;
        this.penWidth = 20 * this.progressValue;
      },
      //设置画笔颜色
      setPenColor(color = ''){
        if(color == '') this.penColor = this.brackGroudColor;
          else this.penColor = color;
      },
      penDown(event){
          this.penClick = true;
          this.startAxisX = event.pageX;
          this.startAxisY = event.pageY;
      },
      penUp(){
          this.penClick = false;
      },
      drawing(event){
          if(!this.penClick) return;
        const canvas = document.getElementById('canvas'); //获取canvas标签
        const ctx = canvas.getContext("2d");//创建 contextconst canvas = document.getElementById('canvas');  对象
          const stopAxisX = event.pageX;
          const stopAxisY = event.pageY;
        const left = document.getElementById('leftMenu');
        const lw = left && left.offsetWidth ? (left.offsetWidth || 0) / 2 : 0;
          ctx.beginPath();
        const wsaW = window.screen.availWidth;
        const cl = canvas.offsetLeft;
        const ct = canvas.offsetTop;
          ctx.moveTo(this.startAxisX-cl,this.startAxisY - ct);//moveTo(x,y) 定义线条开始坐标
          ctx.lineTo(stopAxisX-cl,stopAxisY - ct  );//lineTo(x,y) 定义线条结束坐标
          ctx.strokeStyle = this.penColor;
          ctx.lineWidth = this.penWidth;
          ctx.lineCap = "round";
          ctx.stroke();// stroke() 方法来绘制线条
          this.startAxisX = stopAxisX;
          this.startAxisY = stopAxisY;
      },
      createImage() {
        const canvas = document.getElementById('canvas'); //获取canvas标签
          const img_png_src = canvas.toDataURL("image/png"); //将画板保存为图片格式的函数
          document.getElementById("image_png").src = img_png_src;
      }
    }
  }
</script>
<style lang="scss" scoped="scoped">
  *{
      margin: 0;
      padding: 0;
  }
  #canvas-broad{
      margin: 0 auto;
      /*text-align: center;*/
  }
  #canvas{
      border: 2px solid #ff6700;
      cursor:crosshair;
      /*不能用这种方式给canvas设置宽高*/
      /*width: 600px;*/
      /*height: 300px;*/
  }
  .btn{
      width:70px;
      height: 40px;
      border-radius: 10px;
      border: 1px solid #aaa;/*去掉<button>默认边框*/
      outline:none;/*去掉<button>选中时的默认边框*/
    cursor: pointer;
  }
  .input-color{
      width:70px;
      height: 40px;
      border-radius: 10px;
      border: 0;/*去掉<button>默认边框*/
      outline:none;/*去掉<button>选中时的默认边框*/
  }
  #image_png{
      width: 300px;
      height: 150px;
      border:  2px solid #ff6700;
      display: block;
      margin: 10px auto;
   }
  .section{
      margin-top: 10px;
  }
  .info{
      color: #f0f;
      font-size: 14px;
      line-height: 40px;
  }
  .bg{
      background: #ff6700;
  }
  .fw{
      font-weight: 700;
  }
</style>
JTabBar.vue
<template>
  <div class="j-tab-bar">
    <div class="bar-items">
      <div v-for="(item,index) in tabList"
        :key="item.id"
        :class="getBarClass(index,'bar-item')"
        :title="item.label"
        @click="barClick(index)">
        {{item.label}}
      </div>
    </div>
    <div v-for="(item,index) in tabList" 
      :key="item.id"
      >
      <slot v-if="item.id == tabList[showTab].id" 
        :name="item.id">
      </slot>
    </div>
  </div>
</template>
<script>
  export default{
    name:"JTabBar",
    props:{
      tabList:{
        type:Array,
        default:[
          {
            label:'tab',
            id:'tab'
          },
          {
            label:'tab1',
            id:'tab1'
          }
        ]
      },
      defaultShowTab:{
        type:Number,
        default:0
      }
    },
    data() {
      return{
        showTab:0
      }
    },
    created() {
      this.init();
    },
    methods:{
      init(){
        this.showTab = this.defaultShowTab;
      },
      barClick(index){
        this.showTab = index;
        this.$emit('clickTab',index);
      },
      getBarClass(index,val=''){
        let res = '';
        if(index == this.showTab){
          res += 'active-tab';
        }
        return res + ' ' + val;
      }
    }
  }
</script>
<style lang="scss" scoped="scoped">
  .j-tab-bar{
    .bar-items{
      display: flex;
      flex-direction: row;
      line-height: 2rem;
      .bar-item{
        flex: 1;
        color: black;
        border: 1px solid deepskyblue;
        cursor: pointer;
      }
      .active-tab{
        color: cadetblue;
      }
    }
  }
</style>

npm引入

组件以上传到npm,可以直接install使用

1.安装
npm i @jyeontu/jvuewheel -S
2.引入
//引入组件库
import jvuewheel from '@jyeontu/jvuewheel'
//引入样式
import '@jyeontu/jvuewheel/lib/jvuewhell.css'
Vue.use(jvuewheel);
3.vue中引用组件
<j-canvas-broad class = "canvas-baroad" 
        :toolsTabList = "true">
</j-canvas-broad>

资源地址

组件文档

文档地址:http://120.79.163.94/JYeontuComponents/#/homePage

代码地址

Gitee:https://gitee.com/zheng_yongtao/jyeontu-component-warehouse/tree/master/JYeontuComponentWarehouse

canvas学习

MDN地址:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D

目录
相关文章
|
11月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
799 2
|
10月前
|
缓存 JavaScript
vue中的keep-alive问题(2)
vue中的keep-alive问题(2)
674 137
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
1439 0
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
804 17
|
人工智能 JSON JavaScript
VTJ.PRO 首发 MasterGo 设计智能识别引擎,秒级生成 Vue 代码
VTJ.PRO发布「AI MasterGo设计稿识别引擎」,成为全球首个支持解析MasterGo原生JSON文件并自动生成Vue组件的AI工具。通过双引擎架构,实现设计到代码全流程自动化,效率提升300%,助力企业降本增效,引领“设计即生产”新时代。
846 1
|
JavaScript 安全
在 Vue 中,如何在回调函数中正确使用 this?
在 Vue 中,如何在回调函数中正确使用 this?
597 0
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
880 1
|
存储 JavaScript 前端开发
如何高效实现 vue 文件批量下载及相关操作技巧
在Vue项目中,实现文件批量下载是常见需求。例如文档管理系统或图片库应用中,用户可能需要一次性下载多个文件。本文介绍了三种技术方案:1) 使用`file-saver`和`jszip`插件在前端打包文件为ZIP并下载;2) 借助后端接口完成文件压缩与传输;3) 使用`StreamSaver`解决大文件下载问题。同时,通过在线教育平台的实例详细说明了前后端的具体实现步骤,帮助开发者根据项目需求选择合适方案。
1118 0
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
615 0